diff --git a/2nd-gen/packages/swc/.storybook/decorators/language.ts b/2nd-gen/packages/swc/.storybook/decorators/language.ts index 950a79ec485..334a1dd8484 100644 --- a/2nd-gen/packages/swc/.storybook/decorators/language.ts +++ b/2nd-gen/packages/swc/.storybook/decorators/language.ts @@ -15,16 +15,31 @@ import { addons, makeDecorator, useEffect } from '@storybook/preview-api'; import type {} from '../storybook-env'; const DEFAULT_KIT_ID = 'obc6cux'; +const RTL_LANGS = new Set(['ar', 'fa', 'he']); let languageListenerAttached = false; +function resolveTextDirection( + lang: string | false, + textDirection: string | undefined +): 'ltr' | 'rtl' { + if (textDirection === 'ltr' || textDirection === 'rtl') { + return textDirection; + } + + return RTL_LANGS.has(lang || 'en-US') ? 'rtl' : 'ltr'; +} + /** - * Applies the selected language (lang/dir) to the document and loads the corresponding + * Applies the selected language/dir to the document and loads the corresponding * Adobe Fonts kit if needed. Safe to call from the decorator or from a toolbar listener * so that font loading works even when the decorator does not re-run (e.g. on some docs pages). */ -function applyLanguageAndFontKit(lang: string | false, isRTL: boolean): void { - const langAttr = lang ? String(lang) : 'en-US'; +function applyLanguageAndFontKit( + lang: string | false, + textDirection: 'ltr' | 'rtl' +): void { + const langAttr = lang || 'en-US'; const root = document.documentElement; // Set the language on the document root and track if it has changed @@ -33,7 +48,8 @@ function applyLanguageAndFontKit(lang: string | false, isRTL: boolean): void { root.setAttribute('lang', langAttr); hasChanged = true; } - root.setAttribute('dir', isRTL ? 'rtl' : 'ltr'); + + root.setAttribute('dir', textDirection); // If the fonts are actively loading, do not re-trigger the load if (window.FontsLoading === true) { @@ -173,35 +189,39 @@ function attachLanguageChangeListener(): void { languageListenerAttached = true; try { const channel = addons.getChannel(); - channel.on('globalsUpdated', (payload: { globals?: { lang?: string } }) => { - const lang = payload?.globals?.lang ?? 'en-US'; - const isRTL = ['ar', 'fa', 'he'].includes(lang); - applyLanguageAndFontKit(lang, isRTL); - }); + channel.on( + 'globalsUpdated', + (payload: { globals?: { lang?: string; textDirection?: string } }) => { + const lang = payload?.globals?.lang ?? 'en-US'; + const textDirection = resolveTextDirection( + lang, + payload?.globals?.textDirection + ); + applyLanguageAndFontKit(lang, textDirection); + } + ); } catch { // Storybook event bus not available (e.g. in test env); decorator-only path still works. } } -/** - * @type import('@storybook/csf').DecoratorFunction - **/ export const withLanguageWrapper = makeDecorator({ name: 'withLanguageWrapper', parameterName: 'lang', wrapper: (StoryFn, context) => { - const { globals: { lang = false } = {}, id, viewMode } = context; + const { + globals: { lang = false, textDirection = 'auto' } = {}, + id, + viewMode, + } = context; - // Add a textDirection property to the globals for use in the stories - // fa/Farsi is currently not included in the storybook toolbar map - const isRTL = ['ar', 'fa', 'he'].includes(lang); - context.globals.textDirection = isRTL ? 'rtl' : 'ltr'; + const resolvedTextDirection = resolveTextDirection(lang, textDirection); attachLanguageChangeListener(); useEffect(() => { - applyLanguageAndFontKit(lang, isRTL); - }, [lang, id, viewMode]); + applyLanguageAndFontKit(lang, resolvedTextDirection); + }, [lang, resolvedTextDirection, id, viewMode]); return StoryFn(context); }, diff --git a/2nd-gen/packages/swc/.storybook/preview.ts b/2nd-gen/packages/swc/.storybook/preview.ts index f26f4005752..0af2c8519e6 100644 --- a/2nd-gen/packages/swc/.storybook/preview.ts +++ b/2nd-gen/packages/swc/.storybook/preview.ts @@ -116,11 +116,29 @@ const preview = { dynamicTitle: true, }, }, + textDirection: { + name: 'Direction', + description: + 'Text direction for stories. Auto follows language; LTR/RTL overrides it.', + defaultValue: 'auto', + type: 'string', + toolbar: { + title: 'Direction', + icon: 'transfer', + items: [ + { value: 'auto', title: 'Auto' }, + { value: 'ltr', title: 'LTR' }, + { value: 'rtl', title: 'RTL' }, + ], + dynamicTitle: true, + }, + }, }, initialGlobals: { theme: 'light', scale: 'medium', lang: 'en-US', + textDirection: 'auto', }, decorators: [ withContext,