diff --git a/.changeset/giant-needles-travel.md b/.changeset/giant-needles-travel.md new file mode 100644 index 0000000000..1434c05a09 --- /dev/null +++ b/.changeset/giant-needles-travel.md @@ -0,0 +1,6 @@ +--- +"@frontify/fondue-components": patch +"@frontify/fondue": patch +--- + +feat(lang): expose lang prop in appropriate components diff --git a/packages/components/.storybook/components/StoryWithTheme.tsx b/packages/components/.storybook/components/StoryWithTheme.tsx index 032f03bbd9..46a655ab87 100644 --- a/packages/components/.storybook/components/StoryWithTheme.tsx +++ b/packages/components/.storybook/components/StoryWithTheme.tsx @@ -3,7 +3,7 @@ import { type ComponentProps, type ComponentType } from 'react'; import { deCH, deDE, enUS, esES, frCH, frFR, itCH, itIT, nlNL, plPL, ptPT } from '#/locales'; -import { type LocaleIdentifier } from '#/locales/types'; +import { type LocaleConfig, type LocaleIdentifier } from '#/locales/types'; import { ThemeProvider } from '../../src/components/ThemeProvider/ThemeProvider'; @@ -15,7 +15,7 @@ type WithThemeOptions = { }; export const withTheme = (Story: ComponentType, options: WithThemeOptions) => { - let locale = enUS; + let locale: LocaleConfig = enUS; switch (options.locale) { case 'de-DE': diff --git a/packages/components/src/components/Badge/Badge.tsx b/packages/components/src/components/Badge/Badge.tsx index 48fc4a9c4f..a106ee5a6a 100644 --- a/packages/components/src/components/Badge/Badge.tsx +++ b/packages/components/src/components/Badge/Badge.tsx @@ -3,6 +3,7 @@ import { IconCross } from '@frontify/fondue-icons'; import { type MouseEvent, type ReactNode } from 'react'; +import { type CommonGlobalProps } from '#/helpers/aria'; import { useTranslation } from '#/hooks/useTranslation'; import { BadgeStatus, type BadgeStatusProps } from './BadgeStatus'; @@ -14,7 +15,7 @@ type BadgeEmphasis = 'strong' | 'weak'; type BadgeSize = 'default' | 'small'; -type BadgeProps = { +type BadgeProps = CommonGlobalProps & { /** * @default 'strong' */ @@ -55,6 +56,7 @@ export const Badge = ({ children, disabled = false, emphasis = 'strong', + lang, onClick, onDismiss, size = 'default', @@ -74,6 +76,7 @@ export const Badge = ({ 'data-test-id': dataTestId, 'data-variant': variant, className: styles.root, + lang, title, }; diff --git a/packages/components/src/components/Badge/tests/Badge.ct.tsx b/packages/components/src/components/Badge/tests/Badge.ct.tsx index c3a65f20b3..3389f5325f 100644 --- a/packages/components/src/components/Badge/tests/Badge.ct.tsx +++ b/packages/components/src/components/Badge/tests/Badge.ct.tsx @@ -124,3 +124,12 @@ test('should keep regular font weight when wrapped in a bold ancestor', async ({ const fontWeight = await component.evaluate((element) => getComputedStyle(element).fontWeight); expect(fontWeight).toBe('400'); }); + +test('should render lang on the badge element', async ({ mount }) => { + const wrapper = await mount( + + {BADGE_TEXT} + , + ); + await expect(wrapper.getByTestId('badge-root')).toHaveAttribute('lang', 'fr-CH'); +}); diff --git a/packages/components/src/components/Box/Box.tsx b/packages/components/src/components/Box/Box.tsx index 13c2bdfa6d..9b25282da0 100644 --- a/packages/components/src/components/Box/Box.tsx +++ b/packages/components/src/components/Box/Box.tsx @@ -2,7 +2,7 @@ import { forwardRef, type ReactNode } from 'react'; -import { type CommonAriaProps } from '#/helpers/aria'; +import { type CommonAriaProps, type CommonGlobalProps } from '#/helpers/aria'; import { type Responsive, type LayoutComponentProps } from '#/helpers/layout'; import { propsToCssVariables } from '#/helpers/propsToCssVariables'; @@ -22,7 +22,8 @@ export type BoxProps = LayoutComponentProps & { children?: ReactNode; 'data-test-id'?: string; -} & CommonAriaProps; +} & CommonAriaProps & + CommonGlobalProps; export const Box = forwardRef( ( @@ -37,6 +38,7 @@ export const Box = forwardRef( 'aria-labelledby': ariaLabelledBy, 'aria-expanded': ariaExpanded, 'aria-haspopup': ariaHasPopup, + lang, ...props }, ref, @@ -53,6 +55,7 @@ export const Box = forwardRef( aria-labelledby={ariaLabelledBy} aria-expanded={ariaExpanded} aria-haspopup={ariaHasPopup} + lang={lang} ref={ref} > {children} diff --git a/packages/components/src/components/Box/__tests__/Box.ct.tsx b/packages/components/src/components/Box/__tests__/Box.ct.tsx index 399d26a983..9314bb1e6f 100644 --- a/packages/components/src/components/Box/__tests__/Box.ct.tsx +++ b/packages/components/src/components/Box/__tests__/Box.ct.tsx @@ -105,3 +105,12 @@ test.describe('Responsiveness (lg)', () => { await expect(component).toHaveCSS('margin', '120px'); }); }); + +test('should render lang on the root element', async ({ mount }) => { + const wrapper = await mount( + + {BOX_TEXT} + , + ); + await expect(wrapper.getByTestId('box-root')).toHaveAttribute('lang', 'fr-CH'); +}); diff --git a/packages/components/src/components/Button/Button.tsx b/packages/components/src/components/Button/Button.tsx index 9dc54825b8..210c0d0cd9 100644 --- a/packages/components/src/components/Button/Button.tsx +++ b/packages/components/src/components/Button/Button.tsx @@ -2,6 +2,8 @@ import { forwardRef, type ForwardedRef, type MouseEvent, type ReactNode } from 'react'; +import { type CommonGlobalProps } from '#/helpers/aria'; + import styles from './styles/button.module.scss'; type ButtonRounding = 'medium' | 'full'; @@ -16,7 +18,7 @@ type ButtonEmphasis = 'default' | 'weak' | 'strong'; type ButtonAspect = 'default' | 'square'; -export type ButtonProps = { +export type ButtonProps = CommonGlobalProps & { /** * @default "button" */ diff --git a/packages/components/src/components/Button/tests/Button.ct.tsx b/packages/components/src/components/Button/tests/Button.ct.tsx index fd2591f58b..66ead69f5b 100644 --- a/packages/components/src/components/Button/tests/Button.ct.tsx +++ b/packages/components/src/components/Button/tests/Button.ct.tsx @@ -70,3 +70,12 @@ test('should render in positive medium with an icon and text.', async ({ mount } await expect(component).toHaveText(BUTTON_TEXT); await expect(component.locator('data-test-id=fondue-icon-icon')).toBeVisible(); }); + +test('should render lang on the button element', async ({ mount }) => { + const wrapper = await mount( + , + ); + await expect(wrapper.getByTestId('button-root')).toHaveAttribute('lang', 'fr-CH'); +}); diff --git a/packages/components/src/components/Card/CardRoot.tsx b/packages/components/src/components/Card/CardRoot.tsx index 21edc078c6..7f13fa0793 100644 --- a/packages/components/src/components/Card/CardRoot.tsx +++ b/packages/components/src/components/Card/CardRoot.tsx @@ -13,6 +13,7 @@ import { useMemo, } from 'react'; +import { type CommonGlobalProps } from '#/helpers/aria'; import { useTranslation } from '#/hooks/useTranslation'; import { useFondueRouter } from '../RouterProvider/RouterProvider'; @@ -21,7 +22,7 @@ import { ForwardedRefCardAction } from './CardAction'; import { CardContext } from './CardContext'; import styles from './styles/card.module.scss'; -type CardRootBaseProps = { +type CardRootBaseProps = CommonGlobalProps & { 'data-test-id'?: string; /** * Additional class name(s) merged onto the card's root element. Useful for @@ -114,6 +115,7 @@ export const CardRoot = ( 'aria-label': ariaLabel, 'aria-describedby': ariaDescribedby, className = '', + lang, selected = false, href, target, @@ -184,6 +186,7 @@ export const CardRoot = ( ref={ref} className={[styles.root, className].filter(Boolean).join(' ')} data-test-id={dataTestId} + lang={lang} data-interactive={isClickable} data-selectable={isSelectable} data-selected={isSelectable && selected} diff --git a/packages/components/src/components/Card/__tests__/Card.ct.tsx b/packages/components/src/components/Card/__tests__/Card.ct.tsx index 25d212c496..3c77410be5 100644 --- a/packages/components/src/components/Card/__tests__/Card.ct.tsx +++ b/packages/components/src/components/Card/__tests__/Card.ct.tsx @@ -173,3 +173,12 @@ test('should still shift an un-toned banner background on hover', async ({ mount .poll(() => banner.evaluate((element) => getComputedStyle(element).backgroundColor)) .toBe(expectedHover); }); + +test('should render lang on the card root', async ({ mount }) => { + const wrapper = await mount( + + Card title + , + ); + await expect(wrapper.getByTestId(CARD_TEST_ID)).toHaveAttribute('lang', 'fr-CH'); +}); diff --git a/packages/components/src/components/EditableText/EditableText.tsx b/packages/components/src/components/EditableText/EditableText.tsx index aeb64f6eb9..279329ef49 100644 --- a/packages/components/src/components/EditableText/EditableText.tsx +++ b/packages/components/src/components/EditableText/EditableText.tsx @@ -3,9 +3,11 @@ import { Slot } from '@radix-ui/react-slot'; import { type ReactNode, forwardRef, useRef, useState } from 'react'; +import { type CommonGlobalProps } from '#/helpers/aria'; + import styles from './styles/editable-text.module.scss'; -export type EditableTextProps = { +export type EditableTextProps = CommonGlobalProps & { /** * Callback fired with the plain text value when editing is confirmed (on blur or Enter). * Only fires if the value has actually changed. @@ -37,6 +39,7 @@ export const EditableText = forwardRef( hugWidth = true, 'aria-label': ariaLabel, children, + lang, 'data-test-id': dataTestId = 'fondue-editable-text', }, forwardedRef, @@ -94,6 +97,7 @@ export const EditableText = forwardRef( { + const component = await mount({EDITABLE_TEXT_TEXT}); + await expect(component.getByText(EDITABLE_TEXT_TEXT)).toHaveAttribute('lang', 'fr-CH'); +}); diff --git a/packages/components/src/components/Flex/Flex.tsx b/packages/components/src/components/Flex/Flex.tsx index c444175d2d..b2ea512d87 100644 --- a/packages/components/src/components/Flex/Flex.tsx +++ b/packages/components/src/components/Flex/Flex.tsx @@ -2,7 +2,7 @@ import { forwardRef, type ReactNode } from 'react'; -import { type CommonAriaProps } from '#/helpers/aria'; +import { type CommonAriaProps, type CommonGlobalProps } from '#/helpers/aria'; import { type Responsive, type SizeValue, type LayoutComponentProps } from '#/helpers/layout'; import { propsToCssVariables } from '#/helpers/propsToCssVariables'; @@ -51,7 +51,8 @@ export type FlexProps = LayoutComponentProps & { children?: ReactNode; 'data-test-id'?: string; -} & CommonAriaProps; +} & CommonAriaProps & + CommonGlobalProps; export const Flex = forwardRef( ( @@ -66,6 +67,7 @@ export const Flex = forwardRef( 'aria-labelledby': ariaLabelledBy, 'aria-expanded': ariaExpanded, 'aria-haspopup': ariaHasPopup, + lang, ...props }, ref, @@ -82,6 +84,7 @@ export const Flex = forwardRef( aria-labelledby={ariaLabelledBy} aria-expanded={ariaExpanded} aria-haspopup={ariaHasPopup} + lang={lang} ref={ref} > {children} diff --git a/packages/components/src/components/Flex/__tests__/Flex.ct.tsx b/packages/components/src/components/Flex/__tests__/Flex.ct.tsx index b1c49613a0..e4a3ce0516 100644 --- a/packages/components/src/components/Flex/__tests__/Flex.ct.tsx +++ b/packages/components/src/components/Flex/__tests__/Flex.ct.tsx @@ -98,3 +98,12 @@ test.describe('Responsiveness (lg)', () => { await expect(component).toHaveCSS('padding', '100px'); }); }); + +test('should render lang on the root element', async ({ mount }) => { + const wrapper = await mount( + + {FLEX_TEXT} + , + ); + await expect(wrapper.getByTestId('flex-root')).toHaveAttribute('lang', 'fr-CH'); +}); diff --git a/packages/components/src/components/Grid/Grid.tsx b/packages/components/src/components/Grid/Grid.tsx index ea074421ab..c72a44e7f4 100644 --- a/packages/components/src/components/Grid/Grid.tsx +++ b/packages/components/src/components/Grid/Grid.tsx @@ -2,7 +2,7 @@ import { forwardRef, type ReactNode } from 'react'; -import { type CommonAriaProps } from '#/helpers/aria'; +import { type CommonAriaProps, type CommonGlobalProps } from '#/helpers/aria'; import { type Responsive, type SizeValue, type LayoutComponentProps } from '#/helpers/layout'; import { propsToCssVariables } from '#/helpers/propsToCssVariables'; @@ -55,7 +55,8 @@ export type GridProps = LayoutComponentProps & { children?: ReactNode; 'data-test-id'?: string; -} & CommonAriaProps; +} & CommonAriaProps & + CommonGlobalProps; export const Grid = forwardRef( ( @@ -70,6 +71,7 @@ export const Grid = forwardRef( 'aria-labelledby': ariaLabelledBy, 'aria-expanded': ariaExpanded, 'aria-haspopup': ariaHasPopup, + lang, ...props }, ref, @@ -86,6 +88,7 @@ export const Grid = forwardRef( aria-labelledby={ariaLabelledBy} aria-expanded={ariaExpanded} aria-haspopup={ariaHasPopup} + lang={lang} ref={ref} > {children} diff --git a/packages/components/src/components/Grid/__tests__/Grid.ct.tsx b/packages/components/src/components/Grid/__tests__/Grid.ct.tsx index c960c41a91..7a4df3d0b0 100644 --- a/packages/components/src/components/Grid/__tests__/Grid.ct.tsx +++ b/packages/components/src/components/Grid/__tests__/Grid.ct.tsx @@ -155,3 +155,12 @@ test.describe('Responsiveness (lg)', () => { await expect(component).toHaveCSS('grid-template-rows', '24px 24px 24px'); }); }); + +test('should render lang on the root element', async ({ mount }) => { + const wrapper = await mount( + + {GRID_TEXT} + , + ); + await expect(wrapper.getByTestId('grid-root')).toHaveAttribute('lang', 'fr-CH'); +}); diff --git a/packages/components/src/components/Heading/Heading.tsx b/packages/components/src/components/Heading/Heading.tsx index 510d1d0401..2b1a5b1085 100644 --- a/packages/components/src/components/Heading/Heading.tsx +++ b/packages/components/src/components/Heading/Heading.tsx @@ -2,7 +2,7 @@ import { type ForwardedRef, forwardRef, type ReactElement, type ReactNode } from 'react'; -import { type CommonAriaProps } from '#/helpers/aria'; +import { type CommonAriaProps, type CommonGlobalProps } from '#/helpers/aria'; import styles from './styles/heading.module.scss'; @@ -12,7 +12,7 @@ type HeadingColor = 'default' | 'weak' | 'x-weak' | 'disabled' | 'negative' | 'p type TagType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'span' | 'p'; -export type HeadingProps = CommonAriaProps & { +export type HeadingProps = CommonAriaProps & CommonGlobalProps & { 'data-test-id'?: string; as?: TTag; children?: ReactNode; diff --git a/packages/components/src/components/Label/Label.tsx b/packages/components/src/components/Label/Label.tsx index d6c47a08fb..0ec7b10f25 100644 --- a/packages/components/src/components/Label/Label.tsx +++ b/packages/components/src/components/Label/Label.tsx @@ -3,11 +3,13 @@ import * as LabelPrimitive from '@radix-ui/react-label'; import { type ForwardedRef, forwardRef, type MouseEventHandler, type ReactNode, useRef, useLayoutEffect } from 'react'; +import { type CommonGlobalProps } from '#/helpers/aria'; + import styles from './styles/label.module.scss'; type LabelVariant = 'default' | 'strong'; -export type LabelProps = { +export type LabelProps = CommonGlobalProps & { id?: string; children: ReactNode; /** diff --git a/packages/components/src/components/Link/Link.tsx b/packages/components/src/components/Link/Link.tsx index 8df0a96417..624211d880 100644 --- a/packages/components/src/components/Link/Link.tsx +++ b/packages/components/src/components/Link/Link.tsx @@ -2,6 +2,8 @@ import { forwardRef, type ReactNode, type MouseEvent, useCallback } from 'react'; +import { type CommonGlobalProps } from '#/helpers/aria'; + import { useFondueRouter } from '../RouterProvider/RouterProvider'; import styles from './styles/link.module.scss'; @@ -13,7 +15,7 @@ type LinkOnContainerColor = 'secondary' | 'disabled' | 'error' | 'success' | 'wa type LinkWrap = 'wrap' | 'nowrap'; type LinkUnderline = 'auto' | 'always' | 'hover' | 'none'; -export type LinkProps = { +export type LinkProps = CommonGlobalProps & { children?: ReactNode; /** * The link to navigate to when clicked diff --git a/packages/components/src/components/Notice/Notice.tsx b/packages/components/src/components/Notice/Notice.tsx index 6adce2e81f..e171225218 100644 --- a/packages/components/src/components/Notice/Notice.tsx +++ b/packages/components/src/components/Notice/Notice.tsx @@ -3,6 +3,7 @@ import { IconCross } from '@frontify/fondue-icons'; import { type MouseEvent, type ReactNode } from 'react'; +import { type CommonGlobalProps } from '#/helpers/aria'; import { useTranslation } from '#/hooks/useTranslation'; import styles from './styles/notice.module.scss'; @@ -15,7 +16,7 @@ type NoticeSize = 'medium' | 'large'; type NoticeAlignContent = 'center' | 'top'; -export type NoticeProps = { +export type NoticeProps = CommonGlobalProps & { /** * @default 'default' */ @@ -72,6 +73,7 @@ export const Notice = ({ alignContent = 'center', icon, action, + lang, onDismiss, className = '', children, @@ -91,7 +93,9 @@ export const Notice = ({ aria-live="polite" > {icon ?
{icon}
: null} -
{children}
+
+ {children} +
{action ?
{action}
: null} {onDismiss ? (