diff --git a/package.json b/package.json index 548cfb7e..f22c6ce3 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@babel/preset-react": "^7.18.6", "@mdx-js/mdx": "^2.1.3", "@resvg/resvg-js": "^2.6.2", + "@shuding/opentype.js": "1.4.0-beta.0", "@types/body-scroll-lock": "^2.6.1", "@types/classnames": "^2.2.10", "@types/debounce": "^1.2.1", diff --git a/scripts/generateOgImages.mjs b/scripts/generateOgImages.mjs index 937727a1..bc805d38 100644 --- a/scripts/generateOgImages.mjs +++ b/scripts/generateOgImages.mjs @@ -14,6 +14,7 @@ import path from 'path'; import satori from 'satori'; import {Resvg} from '@resvg/resvg-js'; import matter from 'gray-matter'; +import opentype from '@shuding/opentype.js'; const ROOT = process.cwd(); const CONTENT_DIR = path.join(ROOT, 'src', 'content'); @@ -34,6 +35,33 @@ const medium = fs.readFileSync( path.join(ROOT, 'public', 'fonts', 'Optimistic_Display_W_Md.ttf') ); +// Title area width: card width minus the horizontal padding (80px per side). +const TITLE_MAX_WIDTH = 1200 - 80 * 2; +const TITLE_MAX_FONT = 96; +const TITLE_MIN_FONT = 56; +// Small margin so borderline titles don't wrap and orphan a single trailing +// character (e.g. the "p" in "renderToStaticMarkup", which is 1px too wide +// to fit on one line at 96px). +const TITLE_SAFETY = 8; + +const boldFont = opentype.parse( + bold.buffer.slice(bold.byteOffset, bold.byteOffset + bold.byteLength) +); + +// Multi-word titles wrap cleanly at spaces, so a length bucket is fine for +// them. Single-word titles (most API names) can only break mid-word, which +// leaves an orphaned letter on its own line, so instead shrink them just +// enough to fit on a single line. +function titleFontSize(title) { + const trimmed = title.trim(); + if (/\s/.test(trimmed)) { + return trimmed.length > 24 ? 72 : 96; + } + const widthPerPx = boldFont.getAdvanceWidth(trimmed, 1); + const fit = Math.floor((TITLE_MAX_WIDTH - TITLE_SAFETY) / widthPerPx); + return Math.max(TITLE_MIN_FONT, Math.min(TITLE_MAX_FONT, fit)); +} + function el(type, style, children) { return {type, props: {style, children}}; } @@ -103,7 +131,7 @@ function card(title, pagePath) { flexGrow: 1, display: 'flex', alignItems: 'center', - fontSize: title.length > 24 ? 72 : 96, + fontSize: titleFontSize(title), fontFamily: 'Optimistic Display Bold', color: '#f6f7f9', lineHeight: 1.1, diff --git a/src/content/learn/editor-setup.md b/src/content/learn/editor-setup.md index 024207dd..6db6f58d 100644 --- a/src/content/learn/editor-setup.md +++ b/src/content/learn/editor-setup.md @@ -31,7 +31,11 @@ Baadhi ya vihariri huja na maumbile haya yakiwa yamejengwa ndani, lakini vingine ### Linting {/*linting*/} +<<<<<<< HEAD Code linters hupata matatizo katika kodi yako unapoandika, zikikusaidia kuyarekebisha mapema. [ESLint](https://eslint.org/) ni linter maarufu ya chanzo wazi (open source) kwa ajili ya JavaScript. +======= +Code linters find problems in your code as you write, helping you fix them early. [ESLint](https://eslint.org/) is a popular, open source linter for JavaScript. +>>>>>>> 7b6c3ceb9dd97249e9dce4a8a94e61aed6424698 * [Sakinisha ESLint kwa mpangilio uliopendekezwa kwa React](https://www.npmjs.com/package/eslint-config-react-app) (hakikisha una [Node imesakinishwa!](https://nodejs.org/en/download/current/)) * [Unganisha ESLint katika VSCode na kiendelezi rasmi](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) diff --git a/src/content/learn/thinking-in-react.md b/src/content/learn/thinking-in-react.md index 7019c18f..4b353c9a 100644 --- a/src/content/learn/thinking-in-react.md +++ b/src/content/learn/thinking-in-react.md @@ -37,9 +37,15 @@ Anza kwa kuchora visanduku kuzunguka kila kijenzi na kijenzi kidogo kwenye kiigi Kulingana na msingi wako, unaweza kufikiria kugawanya muundo katika vijenzi kwa njia tofauti: +<<<<<<< HEAD * **Programming**--tumia mbinu zile zile za kuamua ikiwa unapaswa kuunda kitendaji kipya au object. Mbinu moja kama hiyo ni [kanuni ya uwajibikaji mmoja (single responsibility principle)](https://en.wikipedia.org/wiki/Single_responsibility_principle), yaani, kijenzi kinapaswa kufanya jambo moja tu. Ikiwa kitaishia kukua, inapaswa kigawanywe kiwe vijenzi vidogo. * **CSS**--fikiria ni nini ungetengeneza viteule vya darasa. (Hatahivyo, vijenzi ni vidogo kwa kiasi fulani.) * **Design**--fikiria jinsi unavyoweza kupanga safu za muundo. +======= +* **Programming**--use the same techniques for deciding if you should create a new function or object. One such technique is the [separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns), that is, a component should ideally only be concerned with one thing. If it ends up growing, it should be decomposed into smaller subcomponents. +* **CSS**--consider what you would make class selectors for. (However, components are a bit less granular.) +* **Design**--consider how you would organize the design's layers. +>>>>>>> 7b6c3ceb9dd97249e9dce4a8a94e61aed6424698 Ikiwa JSON yako imeundwa vizuri, mara nyingi utapata kwamba inaelekeza kwa muundo wa vijenzi vya UI yako. Hiyo ni kwa sababu UI na miundo ya data mara nyingi huwa na usanifu sawa wa habari--yaani, umbo sawa. Tenganisha UI yako katika vijenzi, ambapo kila kijenzi kinalingana na kipande kimoja cha muundo yako wa data. @@ -226,10 +232,19 @@ Kilichobaki labda ni hali. Wacha tupitie moja baada ya nyingine tena: +<<<<<<< HEAD 1. Orodha asili ya bidhaa **imepitishwa kama vifaa, kwa hivyo haijabainishwa.** 2. Maandishi ya utafutaji yanaonekana kuwa ya hali kwani yanabadilika kwa wakati na hayawezi kukokotwa kutoka kwa chochote. 3. Thamani ya kisanduku cha kuteua inaonekana kuwa hali kwani inabadilika kwa wakati na haiwezi kukokotwa kutoka kwa chochote. 4. Orodha iliyochujwa ya bidhaa **haijabainishwa kwa sababu inaweza kukokotwa** kwa kuchukua orodha asili ya bidhaa na kuichuja kulingana na maandishi ya utafutaji na thamani ya kisanduku cha kuteua. +======= +1. The original list of products is **passed in as props, so it's not state.** +2. The search text seems to be state since it changes over time and can't be computed from anything. +3. The value of the checkbox seems to be state since it changes over time and can't be computed from anything. +4. The filtered list of products **isn't state because it can be computed** by taking the original list of products and filtering it according to the search text and value of the checkbox. + +This means only the search text and the value of the checkbox are state! Nicely done! +>>>>>>> 7b6c3ceb9dd97249e9dce4a8a94e61aed6424698 Hii ina maana kwamba ni maandishi ya utafutaji pekee na thamani ya kisanduku cha kuteua ndizo zilizotajwa! Imefanywa vizuri! @@ -262,6 +277,7 @@ In the previous step, you found two pieces of state in this application: the sea Katika hatua ya awali, ulipata vipande viwili vya hali katika programu hii: maandishi ya ingizo ya utafutaji, na thamani ya kisanduku cha kuteua. Katika mfano huu, daima huonekana pamoja, kwa hiyo ni mantiki kuviweka katika sehemu moja. +<<<<<<< HEAD Sasa wacha tupitie mkakati wetu kwao: 1. **Tambua vijenzi vinavyotumia hali:** @@ -269,6 +285,15 @@ Sasa wacha tupitie mkakati wetu kwao: * `SearchBar` inahitaji kuonyesha hali hiyo (maandishi ya utafutaji na thamani ya kisanduku cha kuteua). 1. **Tafuta mzazi wao wa kawaida:** Kijenzi mzazi cha kwanza ambacho vijenzi vyote viwili hushiriki ni `FilterableProductTable`. 2. **Amua mahali pa kuishi**: Tutaweka maandishi ya kichujio na thamani za hali zilizochaguliwa katika `FilterableProductTable`. +======= +1. **Identify components that use state:** + * `ProductTable` needs to filter the product list based on that state (search text and checkbox value). + * `SearchBar` needs to display that state (search text and checkbox value). +2. **Find their common parent:** The first parent component both components share is `FilterableProductTable`. +3. **Decide where the state lives**: We'll keep the filter text and checked state values in `FilterableProductTable`. + +So the state values will live in `FilterableProductTable`. +>>>>>>> 7b6c3ceb9dd97249e9dce4a8a94e61aed6424698 Kwa hivyo state thamani zitaishi katika `FilterableProductTable`. @@ -456,7 +481,16 @@ function SearchBar({ filterText, inStockOnly }) { Hata hivyo, bado hujaongeza msimbo wowote ili kujibu vitendo vya mtumiaji kama vile kuandika. Hii itakuwa hatua yako ya mwisho. +<<<<<<< HEAD ## Hatua ya 5: Ongeza mtiririko wa data kinyume {/*step-5-add-inverse-data-flow*/} +======= + +## Step 5: Add inverse data flow {/*step-5-add-inverse-data-flow*/} + +Currently your app renders correctly with props and state flowing down the hierarchy. But to change the state according to user input, you will need to support data flowing the other way: the form components deep in the hierarchy need to update the state in `FilterableProductTable`. + +React makes this data flow explicit, but it requires a little more typing than two-way data binding. If you try to type or check the box in the example above, you'll see that React ignores your input. This is intentional. By writing ``, you've set the `value` prop of the `input` to always be equal to the `filterText` state passed in from `FilterableProductTable`. Since `filterText` state is never set, the input never changes. +>>>>>>> 7b6c3ceb9dd97249e9dce4a8a94e61aed6424698 You want to make it so whenever the user changes the form inputs, the state updates to reflect those changes. The state is owned by `FilterableProductTable`, so only it can call `setFilterText` and `setInStockOnly`. To let `SearchBar` update the `FilterableProductTable`'s state, you need to pass these functions down to `SearchBar`: diff --git a/src/content/learn/tutorial-tic-tac-toe.md b/src/content/learn/tutorial-tic-tac-toe.md index f70f277d..8e369b79 100644 --- a/src/content/learn/tutorial-tic-tac-toe.md +++ b/src/content/learn/tutorial-tic-tac-toe.md @@ -906,11 +906,6 @@ React DevTools hukuruhusu kuangalia *props* na *state* za vipengele vyako vya Re Ili kuchunguza kipengele fulani kwenye skrini, tumia kitufe kilicho kwenye kona ya juu kushoto ya React DevTools: ![Kuchagua vipengele kwenye ukurasa na React DevTools](../images/tutorial/devtools-select.gif) -After you install it, a new *Components* tab will appear in your browser Developer Tools for sites using React. If you're following along in CodeSandbox, you'd need to first open your sandbox preview in a new tab: - -![opening in new tab](../images/tutorial/sandbox-new-tab.png) - -Then, on the preview page, open your browser's DevTools and find the *Components* tab: ![components tab](../images/tutorial/components-tab.png) diff --git a/src/content/reference/react-dom/server/renderToPipeableStream.md b/src/content/reference/react-dom/server/renderToPipeableStream.md index 0c1412f1..9668e01b 100644 --- a/src/content/reference/react-dom/server/renderToPipeableStream.md +++ b/src/content/reference/react-dom/server/renderToPipeableStream.md @@ -284,17 +284,7 @@ Streaming does not need to wait for React itself to load in the browser, or for -**Only Suspense-enabled data sources will activate the Suspense component.** They include: - -- Data fetching with Suspense-enabled frameworks like [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) and [Next.js](https://nextjs.org/docs/getting-started/react-essentials) -- Lazy-loading component code with [`lazy`](/reference/react/lazy) -- Reading the value of a Promise with [`use`](/reference/react/use) - -Suspense **does not** detect when data is fetched inside an Effect or event handler. - -The exact way you would load data in the `Posts` component above depends on your framework. If you use a Suspense-enabled framework, you'll find the details in its data fetching documentation. - -Suspense-enabled data fetching without the use of an opinionated framework is not yet supported. The requirements for implementing a Suspense-enabled data source are unstable and undocumented. An official API for integrating data sources with Suspense will be released in a future version of React. +Only data read from a source that [activates a Suspense boundary](/reference/react/Suspense#what-activates-a-suspense-boundary), such as a Promise read with [`use`](/reference/react/use), will suspend during rendering. Suspense does not detect data fetched inside an Effect or event handler. diff --git a/src/content/reference/react-dom/server/renderToReadableStream.md b/src/content/reference/react-dom/server/renderToReadableStream.md index 22d872ee..7ee0a0bc 100644 --- a/src/content/reference/react-dom/server/renderToReadableStream.md +++ b/src/content/reference/react-dom/server/renderToReadableStream.md @@ -283,17 +283,7 @@ Streaming does not need to wait for React itself to load in the browser, or for -**Only Suspense-enabled data sources will activate the Suspense component.** They include: - -- Data fetching with Suspense-enabled frameworks like [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) and [Next.js](https://nextjs.org/docs/getting-started/react-essentials) -- Lazy-loading component code with [`lazy`](/reference/react/lazy) -- Reading the value of a Promise with [`use`](/reference/react/use) - -Suspense **does not** detect when data is fetched inside an Effect or event handler. - -The exact way you would load data in the `Posts` component above depends on your framework. If you use a Suspense-enabled framework, you'll find the details in its data fetching documentation. - -Suspense-enabled data fetching without the use of an opinionated framework is not yet supported. The requirements for implementing a Suspense-enabled data source are unstable and undocumented. An official API for integrating data sources with Suspense will be released in a future version of React. +Only data read from a source that [activates a Suspense boundary](/reference/react/Suspense#what-activates-a-suspense-boundary), such as a Promise read with [`use`](/reference/react/use), will suspend during rendering. Suspense does not detect data fetched inside an Effect or event handler. diff --git a/src/content/reference/react/Activity.md b/src/content/reference/react/Activity.md index 127a4b8d..b521970b 100644 --- a/src/content/reference/react/Activity.md +++ b/src/content/reference/react/Activity.md @@ -755,17 +755,7 @@ Pre-rendering components with hidden Activity boundaries is a powerful way to re -**Only Suspense-enabled data sources will be fetched during pre-rendering.** They include: - -- Data fetching with Suspense-enabled frameworks like [Relay](https://relay.dev/docs/guided-tour/rendering/loading-states/) and [Next.js](https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming#streaming-with-suspense) -- Lazy-loading component code with [`lazy`](/reference/react/lazy) -- Reading the value of a cached Promise with [`use`](/reference/react/use) - -Activity **does not** detect data that is fetched inside an Effect. - -The exact way you would load data in the `Posts` component above depends on your framework. If you use a Suspense-enabled framework, you'll find the details in its data fetching documentation. - -Suspense-enabled data fetching without the use of an opinionated framework is not yet supported. The requirements for implementing a Suspense-enabled data source are unstable and undocumented. An official API for integrating data sources with Suspense will be released in a future version of React. +Only data read from a source that [activates a Suspense boundary](/reference/react/Suspense#what-activates-a-suspense-boundary), such as a Promise read with [`use`](/reference/react/use), is fetched during pre-rendering. Activity does not detect data fetched inside an Effect. diff --git a/src/content/reference/react/Fragment.md b/src/content/reference/react/Fragment.md index 127476f5..14a534bb 100644 --- a/src/content/reference/react/Fragment.md +++ b/src/content/reference/react/Fragment.md @@ -38,13 +38,7 @@ Wrap elements in `` to group them together in situations where you nee * React does not [reset state](/learn/preserving-and-resetting-state) when you go from rendering `<>` to `[]` or back, or when you go from rendering `<>` to `` and back. This only works a single level deep: for example, going from `<><>` to `` resets the state. See the precise semantics [here.](https://gist.github.com/clemmy/b3ef00f9507909429d8aa0d3ee4f986b) -**Layout methods:** -- `compareDocumentPosition(otherNode)`: Compares the document position of the Fragment with another node. - - If the Fragment has children, the native `compareDocumentPosition` value is returned. - - Empty Fragments will attempt to compare positioning within the React tree and include `Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC`. - - Elements that have a different relationship in the React tree and DOM tree due to portaling or other insertions are `Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC`. -- `getClientRects()`: Returns a flat array of `DOMRect` objects representing the bounding rectangles of all children. -- `getRootNode()`: Returns the root node containing the Fragment's parent DOM node. +* If you want to pass `ref` to a Fragment, you can't use the `<>...` syntax. You have to explicitly import `Fragment` from `'react'` and render `...`. --- @@ -765,10 +759,12 @@ function VisibleGroup({ onVisibilityChange, children }) { onVisibilityChange(visibleElements.size > 0); } ); - - fragmentRef.current.observeUsing(observer); - return () => fragmentRef.current.unobserveUsing(observer); - }, [threshold, onVisibilityChange]); + const fragmentInstance = fragmentRef.current; + fragmentInstance.observeUsing(observer); + return () => { + fragmentInstance.unobserveUsing(observer); + }; + }, [onVisibilityChange]); return ( diff --git a/src/content/reference/react/Suspense.md b/src/content/reference/react/Suspense.md index 9cdcf8b3..8051ca01 100644 --- a/src/content/reference/react/Suspense.md +++ b/src/content/reference/react/Suspense.md @@ -286,7 +286,17 @@ import { fetchData } from './data.js'; export default function EffectAlbums({ artistId }) { const [albums, setAlbums] = useState([]); -Suspense-enabled data fetching without the use of an opinionated framework is not yet supported. The requirements for implementing a Suspense-enabled data source are unstable and undocumented. An official API for integrating data sources with Suspense will be released in a future version of React. + useEffect(() => { + let active = true; + fetchData(`/${artistId}/albums`).then(result => { + if (active) { + setAlbums(result); + } + }); + return () => { + active = false; + }; + }, [artistId]); // Suspense can't see this fetch, so its fallback never // shows. The list stays empty until the data arrives. diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md index 41ec4143..bc9d8dd3 100644 --- a/src/content/reference/react/use.md +++ b/src/content/reference/react/use.md @@ -472,7 +472,7 @@ function Albums() { } ``` -Instead, pass a Promise from a cache, a Suspense-enabled framework, or a Server Component: +Instead, pass a Promise from a cache, a [Suspense-enabled framework](/reference/react/Suspense#suspense-enabled-frameworks), or a Server Component: ```js // ✅ fetchData reads the Promise from a cache. diff --git a/yarn.lock b/yarn.lock index a694e82e..f3a72d48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2449,6 +2449,7 @@ camelcase-css@^2.0.1: resolved "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== +caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001286, caniuse-lite@^1.0.30001297, caniuse-lite@^1.0.30001370, caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001688: camelize@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.1.tgz#89b7e16884056331a35d6b5ad064332c91daa6c3"