diff --git a/.chromatic/preview-head.html b/.chromatic/preview-head.html index 02e30d149e6..53c9a34cedb 100644 --- a/.chromatic/preview-head.html +++ b/.chromatic/preview-head.html @@ -138,8 +138,7 @@ - - + +`)}

Overriding the color scheme

-

By default, the page follows the user’s operating system color scheme setting, supporting both light and dark mode. The page background is set to the base Spectrum background layer by default. This can be configured by setting the data-color-scheme and data-background attributes on the {''} element. For example, to force the application to only render in light mode, set data-color-scheme="light".

+

By default, React Spectrum follows the operating system color scheme setting, supporting both light and dark mode. The colorScheme prop can be set on {''} to force the app to always render in a certain color scheme.

+
{highlight(`import {Provider} from '@react-spectrum/s2';
+
+
+  {/* your app */}
+`)}
+

When using page.css, set the data-color-scheme attribute on the {''} element.

{highlight(`
   
 `)}

Overriding the locale

-

By default, React Spectrum uses the browser/operating system language setting for localized strings, date and number formatting, and to determine the layout direction (left-to-right or right-to-left). This can be overridden by rendering a {''} component at the root of your app, and setting the locale prop.

+

By default, React Spectrum uses the browser/operating system language setting for localized strings, date and number formatting, and to determine the layout direction (left-to-right or right-to-left). This can be overridden by rendering setting the locale prop on the {''}.

{highlight(`import {Provider} from '@react-spectrum/s2';
 
 
   {/* your app */}
 `)}
-

Embedded sections

-

If you’re building an embedded section of a larger page using Spectrum 2, use the {''} component to set the background instead of importing page.css. The background prop should be set to the Spectrum background layer appropriate for your app, and the colorScheme can be overridden as well.

-
{highlight(`import {Provider} from '@react-spectrum/s2';
-
-
-  {/* your app */}
+        

Server-side rendering

+

When using SSR, the {''} component can be rendered as the root {''} element. The locale prop should always be specified to avoid hydration errors. page.css is not needed in this case.

+
{highlight(`
+  
+    {/* ... */}
+  
 `)}

Usage with older React Spectrum versions

See Adobe internal documentation.

diff --git a/.storybook-s2/preview-head.html b/.storybook-s2/preview-head.html index 050321f834d..e69de29bb2d 100644 --- a/.storybook-s2/preview-head.html +++ b/.storybook-s2/preview-head.html @@ -1,24 +0,0 @@ - - - diff --git a/packages/@react-spectrum/s2/chromatic/Fonts.stories.tsx b/packages/@react-spectrum/s2/chromatic/Fonts.stories.tsx new file mode 100644 index 00000000000..0c5c8941f44 --- /dev/null +++ b/packages/@react-spectrum/s2/chromatic/Fonts.stories.tsx @@ -0,0 +1,198 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import type {Meta} from '@storybook/react'; +import {style} from '../style' with {type: 'macro'}; +import {useLocale} from 'react-aria'; + +const meta: Meta = { + title: 'S2 Chromatic/Fonts' +}; + +export default meta; + +const messages = { + 'en': { + 'button': 'Edit', + 'copy': 'Copy', + 'cut': 'Cut', + 'paste': 'Paste' + }, + 'ar': { + 'button': 'يحرر', + 'copy': 'ينسخ', + 'cut': 'يقطع', + 'paste': 'معجون' + }, + 'he': { + 'button': 'לַעֲרוֹך', + 'copy': 'עותק', + 'cut': 'גזירה', + 'paste': 'לְהַדבִּיק' + }, + 'ja': { + 'button': '編集', + 'copy': 'コピー', + 'cut': '切る', + 'paste': 'ペースト' + }, + 'ko': { + 'button': '편집하다', + 'copy': '복사', + 'cut': '자르다', + 'paste': '반죽' + }, + 'zh-Hans': { + 'button': '编辑', + 'copy': '复制', + 'cut': '切', + 'paste': '粘贴' + }, + 'zh-HK': { + 'button': '編輯', + 'copy': '複製', + 'cut': '切', + 'paste': '粘貼' + }, + 'zh-Hant': { + 'button': '編輯', + 'copy': '複製', + 'cut': '切', + 'paste': '粘貼' + } +}; + +function Example() { + let {locale} = useLocale(); + let m = messages[locale] || messages.en; + return ( +
+
    +
  • {m.button}
  • +
  • {m.button}
  • +
  • {m.button}
  • +
  • {m.button}
  • +
  • {m.button}
  • +
  • {m.button}
  • +
  • {m.button}
  • +
+
    +
  • {m.copy}
  • +
  • {m.copy}
  • +
  • {m.copy}
  • +
  • {m.copy}
  • +
  • {m.copy}
  • +
  • {m.copy}
  • +
  • {m.copy}
  • +
  • {m.copy}
  • +
+
    +
  • {m.cut}
  • +
  • {m.cut}
  • +
  • {m.cut}
  • +
  • {m.cut}
  • +
  • {m.cut}
  • +
  • {m.cut}
  • +
  • {m.cut}
  • +
  • {m.cut}
  • +
+
    +
  • {m.paste}
  • +
  • {m.paste}
  • +
  • {m.paste}
  • +
  • {m.paste}
  • +
  • {m.paste}
  • +
  • {m.paste}
  • +
  • {m.paste}
  • +
+
    +
  • {m.button}
  • +
  • {m.button}
  • +
  • {m.button}
  • +
  • {m.button}
  • +
+
    +
  • {m.cut}
  • +
  • {m.cut}
  • +
  • {m.cut}
  • +
  • {m.cut}
  • +
+
+ ); +} + +function SerifExample() { + return ( +
+
    +
  • Heading
  • +
  • Heading
  • +
  • Heading
  • +
  • Heading
  • +
  • Heading
  • +
  • Heading
  • +
  • Heading
  • +
+
    +
  • Body
  • +
  • Body
  • +
  • Body
  • +
  • Body
  • +
  • Body
  • +
  • Body
  • +
  • Body
  • +
  • Body
  • +
+
+ ); +} + +export const Default = { + render: () => , + parameters: { + chromaticProvider: { + colorSchemes: ['light'], + locales: ['en', 'ar', 'he', 'ja', 'ko', 'zh-Hans', 'zh-HK', 'zh-Hant'] + } + } +}; + +export const Serif = { + render: () => , + parameters: { + chromaticProvider: { + colorSchemes: ['light'], + // Only en because other locales don't have a serif font. + locales: ['en'] + } + } +}; diff --git a/packages/@react-spectrum/s2/src/CoachMark.tsx b/packages/@react-spectrum/s2/src/CoachMark.tsx index 3eeaf8d9543..a4d6cc0a70d 100644 --- a/packages/@react-spectrum/s2/src/CoachMark.tsx +++ b/packages/@react-spectrum/s2/src/CoachMark.tsx @@ -243,7 +243,6 @@ let description = style({ let keyboard = style({ gridArea: 'keyboard', font: 'ui', - fontWeight: 'light', color: 'gray-600', background: 'gray-25', unicodeBidi: 'plaintext' diff --git a/packages/@react-spectrum/s2/src/Fonts.tsx b/packages/@react-spectrum/s2/src/Fonts.tsx new file mode 100644 index 00000000000..e8b02c1beee --- /dev/null +++ b/packages/@react-spectrum/s2/src/Fonts.tsx @@ -0,0 +1,99 @@ +import ReactDOM from 'react-dom'; +import {ReactNode, version as ReactVersion, useEffect, useMemo, useRef} from 'react'; +import {useLocale} from 'react-aria'; +import './fonts.css'; + +// Typekit ids for each CJK locale. These use dynamic subsetting, so cannot be loaded as CSS. +// Because of the large size of the JS, we only download the script for the locale that is used. +// Each of these kits includes regular, bold, extra bold, and black weights. Spectrum 2 does not use any lighter weights. +let scripts = { + ja: 'pyb4hbv', + ko: 'pux4zom', + 'zh-Hans': 'qwy0qqd', + 'zh-CN': 'qwy0qqd', + 'zh-SG': 'qwy0qqd', + 'zh-HK': 'wrf0dsa', + 'zh-Hant': 'twr1oiz', + zh: 'twr1oiz' +}; + +// Font URLs from fonts.css to preload for each locale. +let preloads = { + // Currently no preloads for arabic and hebrew, because we don't know which weights will be used ahead of time. + // Browsers will emit warnings for unused preloads. + // ar: [ + // 'https://use.typekit.net/af/dfb464/00000000000000007735a2f9/31/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n7&v=3', + // 'https://use.typekit.net/af/560a53/00000000000000007735a300/31/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n4&v=3', + // 'https://use.typekit.net/af/0f9162/00000000000000007735a307/31/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n6&v=3', + // 'https://use.typekit.net/af/ab2792/00000000000000007735a309/31/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n9&v=3' + // 'https://use.typekit.net/af/560a53/00000000000000007735a300/31/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n4&v=3' + // ], + // he: [ + // 'https://use.typekit.net/af/ffca46/00000000000000007735a30a/31/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n7&v=3', + // 'https://use.typekit.net/af/e90860/00000000000000007735a313/31/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n4&v=3', + // 'https://use.typekit.net/af/619974/00000000000000007735a31f/31/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n6&v=3' + // 'https://use.typekit.net/af/e90860/00000000000000007735a313/31/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n4&v=3' + // ], + en: [ + // Preload Adobe Clean Spectrum VF. + 'https://use.typekit.net/af/ca4cba/0000000000000000775c55a1/31/l?primer=f592e0a4b9356877842506ce344308576437e4f677d7c9b78ca2162e6cad991a&fvd=n1&v=3' + ] +}; + +export function Fonts(): ReactNode { + let {locale: localeString} = useLocale(); + let locale = useMemo(() => new Intl.Locale(localeString), [localeString]); + let languageAndRegion = locale.language + (locale.region ? '-' + locale.region : ''); + let languageAndScript = locale.language + (locale.script ? '-' + locale.script : ''); + + // Load script tag for CJK font + let typekitId = scripts[locale.baseName] || scripts[languageAndRegion] || scripts[languageAndScript] || scripts[locale.language]; + let script = typekitId ? `https://use.typekit.net/${typekitId}.js` : null; + + let scriptRef = useRef(null); + useEffect(() => { + let scriptEl = scriptRef.current; + + // In older React versions, we must manually insert scripts into the . + // Scripts rendered by React are never executed. + if (script && parseInt(ReactVersion, 10) < 19) { + scriptEl = Array.from(document.scripts).find(s => s.src === script) || null; + if (!scriptEl) { + scriptEl = document.createElement('script'); + scriptEl.async = true; + scriptEl.src = script; + document.head.appendChild(scriptEl); + } + } + + if (scriptEl) { + scriptEl.onload = () => { + if (typeof window['Typekit'] !== 'undefined' && typeof window['Typekit'].load === 'function') { + window['Typekit'].load(); + } + }; + } + }, [scriptRef, script]); + + // When using React 19, async scripts are automatically deduped and hoisted into the . + // This also works during SSR. + if (script && parseInt(ReactVersion, 10) >= 19) { + return