diff --git a/.changeset/lazy-wolves-win.md b/.changeset/lazy-wolves-win.md new file mode 100644 index 0000000000..b5e008578c --- /dev/null +++ b/.changeset/lazy-wolves-win.md @@ -0,0 +1,6 @@ +--- +'@primer/react-brand': minor +'@primer/brand-primitives': patch +--- + +`ThemeProvider` now supports derived color modes. You can pass modes like `"dark_dimmed"` or `"light_high_contrast"` and the right light or dark color tokens will apply. No new themes are added; derived modes inherit from the existing `light` and `dark` token sets. diff --git a/packages/design-tokens/src/formats/color-mode-attributes.js b/packages/design-tokens/src/formats/color-mode-attributes.js index e109a57667..94c3b67ad7 100644 --- a/packages/design-tokens/src/formats/color-mode-attributes.js +++ b/packages/design-tokens/src/formats/color-mode-attributes.js @@ -32,7 +32,8 @@ function colorModeAttributes({dictionary, file, options}) { const renderLightMode = () => `:root, - [data-color-mode=${defaultMode}] { + [data-color-mode="${defaultMode}"], + [data-color-scheme="light"] { ${allTokens .map(createPropertyFormatter({outputReferences, dictionary, format: 'css', formatter: {}, themeable: false})) .filter(function (strVal) { @@ -64,8 +65,14 @@ function colorModeAttributes({dictionary, file, options}) { const {allTokens: newAllTokens} = newDictionary + let selector = `[data-color-mode="${mode}"]` + + if (mode === 'dark') { + selector = `[data-color-mode="${mode}"], [data-color-scheme="dark"]` + } + return ` - [data-color-mode="${mode}"] { + ${selector} { ${newAllTokens .map(replaceWithAlternateModeValue) @@ -86,6 +93,7 @@ function colorModeAttributes({dictionary, file, options}) { }\n` } + const template = ` ${fileHeader({file})} diff --git a/packages/design-tokens/src/formats/color-mode-attributes.test.js b/packages/design-tokens/src/formats/color-mode-attributes.test.js index 3b7eee9821..22e7b2c1fc 100644 --- a/packages/design-tokens/src/formats/color-mode-attributes.test.js +++ b/packages/design-tokens/src/formats/color-mode-attributes.test.js @@ -133,7 +133,8 @@ describe('Design tokens', () => { const expectedOutput = ` :root, -[data-color-mode="light"] { +[data-color-mode="light"], +[data-color-scheme="light"] { --brand-text-color-example: gray; } @@ -141,7 +142,8 @@ describe('Design tokens', () => { --brand-text-color-example: black; } -[data-color-mode="dark"] { +[data-color-mode="dark"], +[data-color-scheme="dark"] { --brand-text-color-example: white; } `.trim() @@ -460,13 +462,15 @@ describe('Design tokens', () => { const expectedOutput = ` :root, -[data-color-mode="light"] { +[data-color-mode="light"], +[data-color-scheme="light"] { --base-color-scale-gray-0: #f6f8fa; --base-color-scale-gray-1: #eaeef2; --base-color-scale-gray-2: #b1bac4; } -[data-color-mode="dark"] { +[data-color-mode="dark"], +[data-color-scheme="dark"] { --base-color-scale-gray-0: #f0f6fc; --base-color-scale-gray-1: #c9d1d9; --base-color-scale-gray-2: #b1bac4; @@ -476,7 +480,7 @@ describe('Design tokens', () => { --base-color-scale-gray-0: #f0f6fc; --base-color-scale-gray-1: #c9d1d9; --base-color-scale-gray-2: #b1bac4; -} +} `.trim() // remove timestamp and trim diff --git a/packages/react/src/Accordion/Accordion.module.css b/packages/react/src/Accordion/Accordion.module.css index 275ca4e44f..dc7cb44385 100644 --- a/packages/react/src/Accordion/Accordion.module.css +++ b/packages/react/src/Accordion/Accordion.module.css @@ -299,7 +299,7 @@ details[open] > .Accordion__summary .Accordion__summary-toggleIcon { display: block; } -[data-color-mode='dark'] .Accordion__content { +:is([data-color-mode='dark'], [data-color-scheme='dark']) .Accordion__content { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: auto; } diff --git a/packages/react/src/Card/Card.stories.shared.module.css b/packages/react/src/Card/Card.stories.shared.module.css index 01070f8006..434542aedc 100644 --- a/packages/react/src/Card/Card.stories.shared.module.css +++ b/packages/react/src/Card/Card.stories.shared.module.css @@ -34,7 +34,7 @@ var(--brand-color-canvas-default); } -[data-color-mode='dark'] .gridColumnArrowHover .gridItem:is(:hover, :focus-within) { +:is([data-color-mode='dark'], [data-color-scheme='dark']) .gridColumnArrowHover .gridItem:is(:hover, :focus-within) { background: linear-gradient(180deg, rgba(242, 245, 243, 0.1) 0%, rgba(242, 245, 243, 0) 100%), var(--brand-color-canvas-default); } diff --git a/packages/react/src/Card/CardSkewEffect.tsx b/packages/react/src/Card/CardSkewEffect.tsx index 43f77404e9..5d196c818a 100644 --- a/packages/react/src/Card/CardSkewEffect.tsx +++ b/packages/react/src/Card/CardSkewEffect.tsx @@ -1,7 +1,7 @@ import React, {useRef, useEffect, PropsWithChildren} from 'react' import {clsx} from 'clsx' -import {useTheme} from '../' +import {getColorScheme, useTheme} from '../' import {BaseProps} from '../component-helpers' import styles from './Card.module.css' @@ -21,11 +21,12 @@ export const CardSkewEffect = ({ ...rest }: PropsWithChildren) => { const {colorMode} = useTheme() + const colorScheme = getColorScheme(colorMode) const boundingRef = useRef(null) const torchRef = useRef(null) useEffect(() => { - if (colorMode === 'light' || disableSkew) return + if (colorScheme === 'light' || disableSkew) return const motionQuery = window.matchMedia('(prefers-reduced-motion: reduce)') @@ -70,7 +71,7 @@ export const CardSkewEffect = ({ torchElement?.removeEventListener('mousemove', handleMouseMove) motionQuery.removeEventListener('change', handleReducedMotionChange) } - }, [disableSkew, perspective, colorMode]) + }, [disableSkew, perspective, colorScheme]) return (
diff --git a/packages/react/src/Heading/Heading.module.css b/packages/react/src/Heading/Heading.module.css index 98beabb78d..532388a03a 100644 --- a/packages/react/src/Heading/Heading.module.css +++ b/packages/react/src/Heading/Heading.module.css @@ -15,7 +15,7 @@ --condensed-stretch: 90%; } -[data-color-mode='dark'] .Heading { +:is([data-color-mode='dark'], [data-color-scheme='dark']) .Heading { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } diff --git a/packages/react/src/Hero/Hero.stories.module.css b/packages/react/src/Hero/Hero.stories.module.css index f4d4246aff..8a94443aee 100644 --- a/packages/react/src/Hero/Hero.stories.module.css +++ b/packages/react/src/Hero/Hero.stories.module.css @@ -4,6 +4,22 @@ aspect-ratio: 16 / 9; } +/** +* Examples +* TODO: Replace with functional token when final color is confirmed +*/ + +.heroWithCardsExample { + --customCardsBackgroundColor: transparent; +} +:is([data-color-mode='light'], [data-color-scheme='light']) .heroWithCardsExample { + --customCardsBackgroundColor: var(--base-color-scale-gray-1); +} +:is([data-color-mode='dark'], [data-color-scheme='dark']) .heroWithCardsExample { + --customCardsBackgroundColor: var(--base-color-scale-gray-9); +} + + /* Custom input with ActionMenu and code copying */ .customInputExample { diff --git a/packages/react/src/InlineLink/InlineLink.module.css b/packages/react/src/InlineLink/InlineLink.module.css index 42224c1adb..9f0dbd77c9 100644 --- a/packages/react/src/InlineLink/InlineLink.module.css +++ b/packages/react/src/InlineLink/InlineLink.module.css @@ -24,7 +24,7 @@ color: var(--brand-InlineLink-color-pressed); } -[data-color-mode='dark'] .InlineLink { +:is([data-color-mode='dark'], [data-color-scheme='dark']) .InlineLink { -webkit-font-smoothing: subpixel-antialiased; -moz-osx-font-smoothing: auto; } diff --git a/packages/react/src/Label/Label.module.css b/packages/react/src/Label/Label.module.css index 737941a86b..8d9436e1ec 100644 --- a/packages/react/src/Label/Label.module.css +++ b/packages/react/src/Label/Label.module.css @@ -32,7 +32,7 @@ text-align: center; } -[data-color-mode='dark'] .Label { +:is([data-color-mode='dark'], [data-color-scheme='dark']) .Label { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: auto; } diff --git a/packages/react/src/MinimalFooter/MinimalFooter.module.css b/packages/react/src/MinimalFooter/MinimalFooter.module.css index aeb28308a3..6ba6613100 100644 --- a/packages/react/src/MinimalFooter/MinimalFooter.module.css +++ b/packages/react/src/MinimalFooter/MinimalFooter.module.css @@ -82,7 +82,7 @@ height: auto; } -[data-color-mode='light'] .Footer__social-icon { +:is([data-color-mode='light'], [data-color-scheme='light']) .Footer__social-icon { filter: brightness(95%) saturate(100%); } diff --git a/packages/react/src/MinimalFooter/MinimalFooter.test.tsx b/packages/react/src/MinimalFooter/MinimalFooter.test.tsx index effe343c6d..9c4e68b7e8 100644 --- a/packages/react/src/MinimalFooter/MinimalFooter.test.tsx +++ b/packages/react/src/MinimalFooter/MinimalFooter.test.tsx @@ -298,6 +298,18 @@ describe('MinimalFooter', () => { ) }) + it('uses dark logo fill for derived dark color modes', () => { + const {container} = render( + + + , + ) + + const logo = container.querySelector('a[aria-label="GitHub"] svg') + + expect(logo).toHaveAttribute('fill', 'white') + }) + it('renders social links with visually hidden text', () => { const {getByText} = render() const hiddenText = getByText('GitHub on X') diff --git a/packages/react/src/MinimalFooter/MinimalFooter.tsx b/packages/react/src/MinimalFooter/MinimalFooter.tsx index db8a7936d1..b49f409335 100644 --- a/packages/react/src/MinimalFooter/MinimalFooter.tsx +++ b/packages/react/src/MinimalFooter/MinimalFooter.tsx @@ -2,7 +2,7 @@ import React, {PropsWithChildren} from 'react' import {clsx} from 'clsx' import {LogoGithubIcon} from '@primer/octicons-react' -import {ColorModesEnum, Stack, Text, useTheme} from '../' +import {getColorScheme, Stack, Text, useTheme} from '../' import {BaseProps} from '../component-helpers' /** @@ -235,6 +235,7 @@ type SocialLogomarksProps = { function SocialLogomarks({socialLinks = socialLinkNames, logoHref}: SocialLogomarksProps) { const {colorMode} = useTheme() + const colorScheme = getColorScheme(colorMode) return (
@@ -252,7 +253,7 @@ function SocialLogomarks({socialLinks = socialLinkNames, logoHref}: SocialLogoma data-analytics-event='{"category":"Footer","action":"go to home","label":"text:home"}' aria-label="GitHub" > - +
{socialLinks ? ( diff --git a/packages/react/src/SubNav/SubNav.module.css b/packages/react/src/SubNav/SubNav.module.css index d56c82b3bd..9918d0d4e9 100644 --- a/packages/react/src/SubNav/SubNav.module.css +++ b/packages/react/src/SubNav/SubNav.module.css @@ -21,7 +21,7 @@ background-color: var(--brand-SubNav-bgColor); } -[data-color-mode='dark'] .SubNav { +:is([data-color-mode='dark'], [data-color-scheme='dark']) .SubNav { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } diff --git a/packages/react/src/Text/Text.module.css b/packages/react/src/Text/Text.module.css index f810f2e96b..22b4bafa26 100644 --- a/packages/react/src/Text/Text.module.css +++ b/packages/react/src/Text/Text.module.css @@ -2,7 +2,7 @@ font-family: var(--brand-body-fontFamily); } -[data-color-mode='dark'] .Text--antialiased { +:is([data-color-mode='dark'], [data-color-scheme='dark']) .Text--antialiased { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: auto; } diff --git a/packages/react/src/ThemeProvider/ThemeProvider.stories.tsx b/packages/react/src/ThemeProvider/ThemeProvider.stories.tsx index 573460e086..ada711fad6 100644 --- a/packages/react/src/ThemeProvider/ThemeProvider.stories.tsx +++ b/packages/react/src/ThemeProvider/ThemeProvider.stories.tsx @@ -1,10 +1,8 @@ import React from 'react' import type {Meta, StoryObj} from '@storybook/react' import {SunIcon, MoonIcon} from '@primer/octicons-react' -import {ThemeProvider} from '.' -import {useTheme} from '..' -import {Text} from '../Text' - +import {ThemeProvider, type ThemeProviderProps} from '.' +import {Box, Text, Stack, useTheme} from '..' import styles from './ThemeProvider.stories.module.css' function ActiveColorMode() { @@ -12,21 +10,45 @@ function ActiveColorMode() { return Active global color mode: {colorMode} } +const providerBoxProps = { + backgroundColor: 'default', + borderColor: 'default', + borderRadius: 'xlarge', + borderStyle: 'solid', + borderWidth: 'thin', + padding: 24, +} as const + function Container({children}: {children: React.ReactNode}) { return
{children}
} function ControlsHint() { return ( - + Hint: Use Storybook Controls to alternate between color modes. ) } +const StorybookColorModes = [ + 'light', + 'dark', + 'dark_dimmed', + 'light_high_contrast', + 'dark_high_contrast', + 'auto', +] satisfies ThemeProviderProps['colorMode'][] + const meta = { title: 'Components/ThemeProvider', component: ThemeProvider, + argTypes: { + colorMode: { + control: 'select', + options: StorybookColorModes, + }, + }, } satisfies Meta export default meta @@ -73,3 +95,44 @@ export const Nested: Story = { ), } + +export const DerivedColorModes: Story = { + args: { + colorMode: 'auto', + }, + render: args => ( + + + + + {`Parent provider: ${args.colorMode}`} + + + + Nested provider: dark_dimmed + + + + Sibling provider: light_high_contrast + + + + + Sibling provider: dark_high_contrast + + + + + Sibling provider: auto + + + + + + + + + + + ), +} diff --git a/packages/react/src/ThemeProvider/ThemeProvider.test.tsx b/packages/react/src/ThemeProvider/ThemeProvider.test.tsx index 490b2dbc17..a6565fa648 100644 --- a/packages/react/src/ThemeProvider/ThemeProvider.test.tsx +++ b/packages/react/src/ThemeProvider/ThemeProvider.test.tsx @@ -2,7 +2,7 @@ import React, {useContext} from 'react' import {render, renderHook, cleanup} from '@testing-library/react' import '@testing-library/jest-dom' -import {ColorMode, ThemeContext, ThemeProvider} from './ThemeProvider' +import {ColorMode, ThemeContext, ThemeProvider, getColorScheme} from './ThemeProvider' import {axe, toHaveNoViolations} from 'jest-axe' expect.extend(toHaveNoViolations) @@ -42,6 +42,7 @@ describe('ThemeProvider', () => { expect(rootEl).toBeInTheDocument() expect(rootEl).toHaveAttribute('data-color-mode') + expect(rootEl).toHaveAttribute('data-color-scheme', 'light') expect(childEl).toBeInTheDocument() }) @@ -53,6 +54,7 @@ describe('ThemeProvider', () => { const expectedMode = 'light' expect(rootEl).toHaveAttribute('data-color-mode', expectedMode) + expect(rootEl).toHaveAttribute('data-color-scheme', 'light') }) it('can optionally render dark mode', () => { @@ -63,6 +65,27 @@ describe('ThemeProvider', () => { const el = getByTestId(mockId) expect(el).toHaveAttribute('data-color-mode', mockMode) + expect(el).toHaveAttribute('data-color-scheme', 'dark') + }) + + it('derives dark color scheme for any mode containing "dark"', () => { + const mockId = 'theme-provider' + const {getByTestId} = render() + + const el = getByTestId(mockId) + + expect(el).toHaveAttribute('data-color-mode', 'dark_dimmed') + expect(el).toHaveAttribute('data-color-scheme', 'dark') + }) + + it('derives light color scheme for modes not containing "dark"', () => { + const mockId = 'theme-provider' + const {getByTestId} = render() + + const el = getByTestId(mockId) + + expect(el).toHaveAttribute('data-color-mode', 'light_high_contrast') + expect(el).toHaveAttribute('data-color-scheme', 'light') }) it('can optionally render dark color scheme in auto mode', () => { @@ -85,6 +108,7 @@ describe('ThemeProvider', () => { const expectedMode = 'dark' expect(el).toHaveAttribute('data-color-mode', expectedMode) + expect(el).toHaveAttribute('data-color-scheme', 'dark') matchMediaSpy.mockRestore() }) @@ -109,6 +133,7 @@ describe('ThemeProvider', () => { const expectedMode = 'light' expect(el).toHaveAttribute('data-color-mode', expectedMode) + expect(el).toHaveAttribute('data-color-scheme', 'light') }) it('supports nested ThemeProviders', () => { @@ -129,8 +154,11 @@ describe('ThemeProvider', () => { const levelTwoProviderEl = getByTestId(levelTwoProviderId) expect(rootProviderEl).toHaveAttribute('data-color-mode', 'light') + expect(rootProviderEl).toHaveAttribute('data-color-scheme', 'light') expect(levelOneProviderEl).toHaveAttribute('data-color-mode', 'dark') + expect(levelOneProviderEl).toHaveAttribute('data-color-scheme', 'dark') expect(levelTwoProviderEl).toHaveAttribute('data-color-mode', 'light') + expect(levelTwoProviderEl).toHaveAttribute('data-color-scheme', 'light') }) it('returns all available modes via context', () => { @@ -150,10 +178,12 @@ describe('ThemeProvider', () => { const {getByTestId, rerender} = render() expect(getByTestId(mockId)).toHaveAttribute('data-color-mode', 'light') + expect(getByTestId(mockId)).toHaveAttribute('data-color-scheme', 'light') rerender() expect(getByTestId(mockId)).toHaveAttribute('data-color-mode', 'dark') + expect(getByTestId(mockId)).toHaveAttribute('data-color-scheme', 'dark') }) test('no a11y violations', async () => { @@ -163,3 +193,17 @@ describe('ThemeProvider', () => { expect(results).toHaveNoViolations() }) }) + +describe('getColorScheme', () => { + it('returns "dark" for modes containing "dark"', () => { + expect(getColorScheme('dark')).toBe('dark') + expect(getColorScheme('dark_dimmed')).toBe('dark') + expect(getColorScheme('dark_high_contrast')).toBe('dark') + }) + + it('returns "light" for modes not containing "dark"', () => { + expect(getColorScheme('light')).toBe('light') + expect(getColorScheme('light_high_contrast')).toBe('light') + expect(getColorScheme('auto')).toBe('light') + }) +}) diff --git a/packages/react/src/ThemeProvider/ThemeProvider.tsx b/packages/react/src/ThemeProvider/ThemeProvider.tsx index e024f15754..8dd814e3ba 100644 --- a/packages/react/src/ThemeProvider/ThemeProvider.tsx +++ b/packages/react/src/ThemeProvider/ThemeProvider.tsx @@ -6,7 +6,15 @@ export enum ColorModesEnum { AUTO = 'auto', } -export type ColorMode = `${ColorModesEnum}` +export type ColorMode = `${ColorModesEnum}` | (string & {}) + +/** + * Returns the base color scheme ("light" or "dark") for a given color mode. + * Any mode containing "dark" maps to the dark scheme; everything else maps to light. + */ +export function getColorScheme(mode: string): 'light' | 'dark' { + return mode.includes('dark') ? 'dark' : 'light' +} export type ThemeContextProps = { /* @@ -50,7 +58,7 @@ export function ThemeProvider({colorMode = defaultMode, children, ...rest}: Prop return ( -
+
{children}
diff --git a/packages/react/src/ThemeProvider/ThemeProvider.visual.spec.ts b/packages/react/src/ThemeProvider/ThemeProvider.visual.spec.ts index 463eb867c3..25cd46e600 100644 --- a/packages/react/src/ThemeProvider/ThemeProvider.visual.spec.ts +++ b/packages/react/src/ThemeProvider/ThemeProvider.visual.spec.ts @@ -26,4 +26,15 @@ test.describe('Visual Comparison: ThemeProvider', () => { await page.waitForTimeout(500) await expect(page).toHaveScreenshot({fullPage: true}) }) + + test('ThemeProvider / Derived Color Modes', async ({page}) => { + await page.goto( + 'http://localhost:6006/iframe.html?args=&id=components-themeprovider--derived-color-modes&viewMode=story', + {waitUntil: 'networkidle'}, + ) + await page.locator('body.sb-show-main').waitFor({state: 'visible'}) + + await page.waitForTimeout(500) + await expect(page).toHaveScreenshot({fullPage: true}) + }) }) diff --git a/packages/react/src/ThemeProvider/ThemeProvider.visual.spec.ts-snapshots/Visual-Comparison-ThemeProvider-ThemeProvider-Derived-Color-Modes-1-darwin.png b/packages/react/src/ThemeProvider/ThemeProvider.visual.spec.ts-snapshots/Visual-Comparison-ThemeProvider-ThemeProvider-Derived-Color-Modes-1-darwin.png new file mode 100644 index 0000000000..a0c285a1b0 Binary files /dev/null and b/packages/react/src/ThemeProvider/ThemeProvider.visual.spec.ts-snapshots/Visual-Comparison-ThemeProvider-ThemeProvider-Derived-Color-Modes-1-darwin.png differ diff --git a/packages/react/src/Token/Token.module.css b/packages/react/src/Token/Token.module.css index c9a33427f5..d2e542eda9 100644 --- a/packages/react/src/Token/Token.module.css +++ b/packages/react/src/Token/Token.module.css @@ -26,7 +26,6 @@ outline: var(--base-size-4) solid var(--brand-color-focus); outline-offset: var(--base-size-2); } - [data-color-mode='dark'] .Token { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: auto; diff --git a/packages/react/src/recipes/Flexsuite/Overview/FlexSuiteAIOverview.module.css b/packages/react/src/recipes/Flexsuite/Overview/FlexSuiteAIOverview.module.css index 0cbcee29f3..724a4ca7df 100644 --- a/packages/react/src/recipes/Flexsuite/Overview/FlexSuiteAIOverview.module.css +++ b/packages/react/src/recipes/Flexsuite/Overview/FlexSuiteAIOverview.module.css @@ -53,7 +53,9 @@ var(--brand-color-canvas-default); } -[data-color-mode='dark'] .cardGridColumnArrowHover .cardGridItem:is(:hover, :focus-within) { +:is([data-color-mode='dark'], [data-color-scheme='dark']) + .cardGridColumnArrowHover + .cardGridItem:is(:hover, :focus-within) { background: linear-gradient(180deg, rgba(242, 245, 243, 0.1) 0%, rgba(242, 245, 243, 0) 100%), var(--brand-color-canvas-default); } diff --git a/packages/react/src/recipes/Flexsuite/Overview/FlexSuiteAIOverview.tsx b/packages/react/src/recipes/Flexsuite/Overview/FlexSuiteAIOverview.tsx index 3b87ae7d8e..984d5619f1 100644 --- a/packages/react/src/recipes/Flexsuite/Overview/FlexSuiteAIOverview.tsx +++ b/packages/react/src/recipes/Flexsuite/Overview/FlexSuiteAIOverview.tsx @@ -6,11 +6,11 @@ import { Box, Button, Card, - ColorModesEnum, CTABanner, FAQ, FAQGroup, Grid, + getColorScheme, Hero, Heading, InlineLink, @@ -58,6 +58,7 @@ export function FlexSuiteAIOverviewTemplate({content}: FlexSuiteAIOverviewTempla const heroImageRef = React.useRef(null) const riverImageRefs = React.useRef>([]) const {colorMode} = useTheme() + const colorScheme = getColorScheme(colorMode) useEffect(() => { const cleanups: Array<() => void> = [] @@ -136,7 +137,7 @@ export function FlexSuiteAIOverviewTemplate({content}: FlexSuiteAIOverviewTempla @@ -197,14 +198,8 @@ export function FlexSuiteAIOverviewTemplate({content}: FlexSuiteAIOverviewTempla }} > - - {content.rivers[0].imageAlt} + + {content.rivers[0].imageAlt} @@ -221,14 +216,8 @@ export function FlexSuiteAIOverviewTemplate({content}: FlexSuiteAIOverviewTempla }} > - - {content.rivers[1].imageAlt} + + {content.rivers[1].imageAlt} @@ -245,14 +234,8 @@ export function FlexSuiteAIOverviewTemplate({content}: FlexSuiteAIOverviewTempla }} > - - {content.rivers[2].imageAlt} + + {content.rivers[2].imageAlt}