diff --git a/.storybook/decorators/index.js b/.storybook/decorators/index.js index 059919173ba..2303747236e 100644 --- a/.storybook/decorators/index.js +++ b/.storybook/decorators/index.js @@ -17,7 +17,6 @@ export { withIconSpriteSheet } from "./icon-sprites.js"; export { withLanguageWrapper } from "./language.js"; export { withReducedMotionWrapper } from "./reduce-motion.js"; export { withTestingPreviewWrapper } from "./testing-preview.js"; -export { withTextDirectionWrapper } from "./text-direction.js"; /* This is exported but must be opted-into on a component-by-component basis */ export { withUnderlayWrapper } from "./underlay.js"; diff --git a/.storybook/decorators/language.js b/.storybook/decorators/language.js index a49621d0563..3ae9a83db9c 100644 --- a/.storybook/decorators/language.js +++ b/.storybook/decorators/language.js @@ -1,5 +1,5 @@ -import { makeDecorator, useEffect } from "@storybook/preview-api"; -import { fetchContainers } from "./helpers.js"; +import { makeDecorator, useEffect } from '@storybook/preview-api'; +import { fetchContainers } from "./helpers"; /* global Typekit */ /** @@ -17,9 +17,14 @@ export const withLanguageWrapper = makeDecorator({ viewMode, } = context; - useEffect(() => { - const isNotEnglish = lang && lang !== "en-US"; + const isNotEnglish = lang && lang !== "en-US"; + const isRTL = ["ar", "fa", "he"].includes(lang); + + // Add a textDirection property to the globals for use in the story + context.globals.textDirection = isRTL ? "rtl" : "ltr"; + + useEffect(() => { // If it is US-language or unset use the rok6rmo Adobe font web project id (smaller size), // otherwise use the mge7bvf kit with all the language settings (larger size) const kitId = isNotEnglish ? "mge7bvf" : "rok6rmo"; @@ -55,10 +60,12 @@ export const withLanguageWrapper = makeDecorator({ } catch (e) {/* empty */} } + // Set the language and direction on the relevant containers for (const container of fetchContainers(id, viewMode === "docs")) { container.lang = lang; + container.dir = !isRTL ? "ltr" : "rtl"; } - }, [lang]); + }, [lang, isNotEnglish, isRTL, viewMode, id]); return StoryFn(context); }, diff --git a/.storybook/decorators/text-direction.js b/.storybook/decorators/text-direction.js deleted file mode 100644 index 8f4168a109c..00000000000 --- a/.storybook/decorators/text-direction.js +++ /dev/null @@ -1,30 +0,0 @@ -import { makeDecorator, useEffect } from "@storybook/preview-api"; -import { fetchContainers } from "./helpers.js"; - -/** - * @type import('@storybook/csf').DecoratorFunction - * @description Sets the text direction of the document, using the global set with a toolbar control. These properties are assigned to the document root element. - **/ -export const withTextDirectionWrapper = makeDecorator({ - name: "withTextDirectionWrapper", - parameterName: "textDecoration", - wrapper: (StoryFn, context) => { - const { - globals: { - textDirection = "ltr", - } = {}, - id, - viewMode, - } = context; - - useEffect(() => { - if (!textDirection) return; - - for (const container of fetchContainers(id, viewMode === "docs")) { - container.dir = textDirection; - } - }, [textDirection]); - - return StoryFn(context); - }, -}); diff --git a/.storybook/intl/translations.json b/.storybook/intl/translations.json new file mode 100644 index 00000000000..939580a4a90 --- /dev/null +++ b/.storybook/intl/translations.json @@ -0,0 +1,154 @@ +{ + "ar": { + "accordion.text": "", + "actionbutton.text": "", + "assetcard.name": "", + "assetlist.file": "", + "badge.label": "", + "button.label": "", + "combobox.text": "", + "fieldlabel.label": "اسم المستخدم", + "helptext.text": "", + "logicbutton.button": "", + "menu.menuItem": "", + "millercolumns.file": "", + "picker.placeholder": "", + "search.value": "", + "tag.label": "", + "textfield.value": "محمد_أحمد", + "treeview.value": "" + }, + "en": { + "accordion.text": "", + "actionbutton.text": "", + "assetcard.name": "", + "assetlist.file": "", + "badge.label": "", + "button.label": "", + "combobox.text": "", + "fieldlabel.label": "Username", + "helptext.text": "", + "logicbutton.button": "", + "menu.menuItem": "", + "millercolumns.file": "", + "picker.placeholder": "", + "search.value": "", + "tag.label": "", + "textfield.value": "john_doe", + "treeview.value": "" + }, + "fa": { + "accordion.text": "", + "actionbutton.text": "", + "assetcard.name": "", + "assetlist.file": "", + "badge.label": "", + "button.label": "", + "combobox.text": "", + "fieldlabel.label": "نام کاربری", + "helptext.text": "", + "logicbutton.button": "", + "menu.menuItem": "", + "millercolumns.file": "", + "picker.placeholder": "", + "search.value": "", + "tag.label": "", + "textfield.value": "علی_رضا", + "treeview.value": "" + }, + "he": { + "accordion.text": "", + "actionbutton.text": "", + "assetcard.name": "", + "assetlist.file": "", + "badge.label": "", + "button.label": "", + "combobox.text": "", + "fieldlabel.label": "שם משתמש", + "helptext.text": "", + "logicbutton.button": "", + "menu.menuItem": "", + "millercolumns.file": "", + "picker.placeholder": "", + "search.value": "", + "tag.label": "", + "textfield.value": "דני123", + "treeview.value": "" + }, + "ja": { + "accordion.text": "", + "actionbutton.text": "", + "assetcard.name": "", + "assetlist.file": "", + "badge.label": "", + "button.label": "", + "combobox.text": "", + "fieldlabel.label": "ユーザー名", + "helptext.text": "", + "logicbutton.button": "", + "menu.menuItem": "", + "millercolumns.file": "", + "picker.placeholder": "", + "search.value": "", + "tag.label": "", + "textfield.value": "山田太郎", + "treeview.value": "" + }, + "ko": { + "accordion.text": "", + "actionbutton.text": "", + "assetcard.name": "", + "assetlist.file": "", + "badge.label": "", + "button.label": "", + "combobox.text": "", + "fieldlabel.label": "사용자 이름", + "helptext.text": "", + "logicbutton.button": "", + "menu.menuItem": "", + "millercolumns.file": "", + "picker.placeholder": "", + "search.value": "", + "tag.label": "", + "textfield.value": "김철수", + "treeview.value": "" + }, + "th": { + "accordion.text": "", + "actionbutton.text": "", + "assetcard.name": "", + "assetlist.file": "", + "badge.label": "", + "button.label": "", + "combobox.text": "", + "fieldlabel.label": "ชื่อผู้ใช้", + "helptext.text": "", + "logicbutton.button": "", + "menu.menuItem": "", + "millercolumns.file": "", + "picker.placeholder": "", + "search.value": "", + "tag.label": "", + "textfield.value": "สมชาย", + "treeview.value": "" + }, + "zh": { + "accordion.text": "", + "actionbutton.text": "", + "assetcard.name": "", + "assetlist.file": "", + "badge.label": "", + "button.label": "", + "combobox.text": "", + "fieldlabel.label": "用户名", + "helptext.text": "", + "logicbutton.button": "", + "menu.menuItem": "", + "millercolumns.file": "", + "picker.placeholder": "", + "search.value": "", + "tag.label": "", + "textfield.value": "张伟", + "treeview.value": "" + } +} diff --git a/.storybook/modes/index.js b/.storybook/modes/index.js index 1b570d6c2e1..2c0a46275b9 100644 --- a/.storybook/modes/index.js +++ b/.storybook/modes/index.js @@ -13,12 +13,19 @@ const modes = { "Light | LTR": { + color: "light", + lang: "en_US", + context: "legacy", + }, + "Context: Express": { + scale: "medium", color: "light", textDirection: "ltr", + context: "express", }, "Dark | RTL": { color: "dark", - textDirection: "rtl", + lang: "ar", }, "S1 | Light | LTR": { context: "legacy", @@ -40,3 +47,34 @@ export const disableDefaultModes = { return acc; }, {}), }; + +export const mobile = { + "Mobile": { + scale: "large", + }, +}; + +export const viewports = { + small: { + width: 480, + }, +}; + +export const i18n = { + // This is the default language, so we don't need to specify it here + // "English": { + // lang: "en_US", + // }, + "Hebrew": { + lang: "he", + }, + "Japanese": { + lang: "ja", + }, + "Korean": { + lang: "ko", + }, + "Arabic": { + lang: "ar", + }, +}; diff --git a/.storybook/package.json b/.storybook/package.json index d37b6a81b1c..d2858e5089a 100644 --- a/.storybook/package.json +++ b/.storybook/package.json @@ -9,6 +9,7 @@ "exports": { ".": "./preview.js", "./blocks": "./blocks/index.js", + "./blocks/*": "./blocks/*", "./decorators": "./decorators/index.js", "./decorators/*": "./decorators/*", "./deprecated/*": "./deprecated/*", @@ -20,6 +21,8 @@ "./modes/*": "./modes/*", "./package.json": "./package.json", "./preview": "./preview.js", + "./templates/*": "./templates/*", + "./translations": "./intl/translations.json", "./types": "./types/index.js", "./types/*": "./types/*" }, diff --git a/.storybook/preview.js b/.storybook/preview.js index 3c92dcfe186..75337f3c5e7 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -7,8 +7,8 @@ import { withLanguageWrapper, withReducedMotionWrapper, withTestingPreviewWrapper, - withTextDirectionWrapper } from "./decorators"; +import translations from "./intl/translations.json"; import { FontLoader, IconLoader, @@ -35,6 +35,8 @@ setConsoleOptions({ ], }); +export { translations }; + /** @type import('@storybook/types').StorybookParameters & import('@storybook/types').API_Layout */ export const parameters = { layout: "centered", @@ -124,7 +126,6 @@ export default { decorators: [ withLanguageWrapper, withReducedMotionWrapper, - withTextDirectionWrapper, withContextWrapper, withTestingPreviewWrapper, withArgEvents, diff --git a/.storybook/types/global.js b/.storybook/types/global.js index aceccb44c4c..deddda0c24e 100644 --- a/.storybook/types/global.js +++ b/.storybook/types/global.js @@ -47,19 +47,6 @@ export default { dynamicTitle: true, }, }, - textDirection: { - title: "Text direction", - description: "Direction of the content flow", - defaultValue: "ltr", - type: "string", - toolbar: { - items: [ - { value: "ltr", title: "Left to right" }, - { value: "rtl", title: "Right to left" }, - ], - dynamicTitle: true, - }, - }, // @todo https://jira.corp.adobe.com/browse/CSS-314 reducedMotion: { title: "Reduce motion", @@ -77,14 +64,18 @@ export default { lang: { title: "Language", description: "Language of the content", - defaultValue: "en-US", + defaultValue: "en_US", type: "string", toolbar: { items: [ - { value: "en-US", title: "🇺🇸", right: "English (US)" }, - { value: "ja", title: "🇯🇵", right: "日本語" }, - { value: "ko", title: "🇰🇷", right: "한국어" }, - { value: "zh", title: "🇨🇳", right: "中文" }, + { value: "en_US", title: "English", right: "English (US)" }, + { value: "he", title: "Hebrew", right: "עִברִית" }, + { value: "ja", title: "Japanese", right: "日本語" }, + { value: "ko", title: "Korean", right: "한국어" }, + { value: "ar", title: "Arabic", right: "عربي" }, + { value: "zh", title: "Chinese", right: "中文" }, + { value: "fa", title: "Persian", right: "فارسی" }, + { value: "th", title: "Thai", right: "ไทย" }, ], dynamicTitle: true, }, diff --git a/components/textfield/stories/template.js b/components/textfield/stories/template.js index cdb23da0a80..3eda08b34b3 100644 --- a/components/textfield/stories/template.js +++ b/components/textfield/stories/template.js @@ -2,6 +2,7 @@ import { Template as FieldLabel } from "@spectrum-css/fieldlabel/stories/templat import { Template as HelpText } from "@spectrum-css/helptext/stories/template.js"; import { Template as Icon } from "@spectrum-css/icon/stories/template.js"; import { Container, getRandomId } from "@spectrum-css/preview/decorators"; +import translations from "@spectrum-css/preview/translations"; import { Template as ProgressCircle } from "@spectrum-css/progresscircle/stories/template.js"; import { html } from "lit"; import { classMap } from "lit/directives/class-map.js"; @@ -298,3 +299,17 @@ export const KeyboardFocusTemplate = (args, context) => Container({ }, context)} ` }, context); + +export const LocaleWrapper = (args, context) => { + const { lang: contextLang } = context.globals; + const lang = args.lang || contextLang; + const langTranslations = translations[lang] ?? translations.en; + const labelText = langTranslations["fieldlabel.label"]; + const value = langTranslations["textfield.value"]; + + return html` +
+ ${Template({ ...args, labelText, value }, context)} +
+ `; +}; diff --git a/components/textfield/stories/textfield.stories.js b/components/textfield/stories/textfield.stories.js index e86ce3a5d4f..37b80f734c6 100644 --- a/components/textfield/stories/textfield.stories.js +++ b/components/textfield/stories/textfield.stories.js @@ -4,7 +4,7 @@ import { isDisabled, isFocused, isInvalid, isKeyboardFocused, isLoading, isQuiet import metadata from "../metadata/metadata.json"; import packageJson from "../package.json"; import { HelpTextOptions, KeyboardFocusTemplate, Template, TextFieldOptions } from "./template.js"; -import { TextFieldGroup } from "./textfield.test.js"; +import { TextFieldGroup, TextFieldLocaleGroup } from "./textfield.test.js"; /** * Text fields are text boxes that allow users to input custom text entries with a keyboard. Various decorations can be displayed around the field to communicate the entry requirements. @@ -149,6 +149,9 @@ export const Default = TextFieldGroup.bind({}); Default.tags = ["!autodocs"]; Default.args = {}; +export const WithLocaleText = TextFieldLocaleGroup.bind({}); +WithLocaleText.tags = ["!autodocs"]; + // ********* DOCS ONLY ********* // export const Standard = TextFieldOptions.bind({}); diff --git a/components/textfield/stories/textfield.test.js b/components/textfield/stories/textfield.test.js index 43d5614e8c0..de37f759fa7 100644 --- a/components/textfield/stories/textfield.test.js +++ b/components/textfield/stories/textfield.test.js @@ -1,5 +1,5 @@ import { Variants } from "@spectrum-css/preview/decorators"; -import { Template } from "./template.js"; +import { LocaleWrapper, Template } from "./template.js"; export const TextFieldGroup = Variants({ Template, @@ -63,3 +63,32 @@ export const TextFieldGroup = Variants({ isReadOnly: true, }] }); + +export const TextFieldLocaleGroup = Variants({ + Template: LocaleWrapper, + withSizes: false, + testData: [{ + testHeading: "English", + }, { + testHeading: "Hebrew", + lang: "he", + }, { + testHeading: "Japanese", + lang: "ja", + }, { + testHeading: "Korean", + lang: "ko", + }, { + testHeading: "Arabic", + lang: "ar", + }, { + testHeading: "Chinese", + lang: "zh", + }, { + testHeading: "Persian", + lang: "fa", + }, { + testHeading: "Thai", + lang: "th", + }] +});