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 ? (