From c706637183e7a1361b91f33f505962f3e253fc18 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Sat, 8 Nov 2025 18:33:14 +0100 Subject: [PATCH 01/13] feat: implement style api for toggle button component --- build-tools/utils/custom-css-properties.js | 5 + .../toggle-button/style-permutations.page.tsx | 162 ++++++++++++++++++ src/toggle-button/index.tsx | 2 + src/toggle-button/interfaces.ts | 47 +++++ src/toggle-button/internal.tsx | 7 +- src/toggle-button/style.tsx | 51 ++++++ src/toggle-button/styles.scss | 15 +- 7 files changed, 282 insertions(+), 7 deletions(-) create mode 100644 pages/toggle-button/style-permutations.page.tsx create mode 100644 src/toggle-button/style.tsx diff --git a/build-tools/utils/custom-css-properties.js b/build-tools/utils/custom-css-properties.js index a3ed844125..3812801cfb 100644 --- a/build-tools/utils/custom-css-properties.js +++ b/build-tools/utils/custom-css-properties.js @@ -105,6 +105,11 @@ const customCssPropertiesList = [ 'styleBorderColorFocus', 'styleBoxShadowFocus', 'styleColorFocus', + // Pressed state + 'styleBackgroundPressed', + 'styleBorderColorPressed', + 'styleBoxShadowPressed', + 'styleColorPressed', // Placeholder style properties 'stylePlaceholderColor', 'stylePlaceholderFontSize', diff --git a/pages/toggle-button/style-permutations.page.tsx b/pages/toggle-button/style-permutations.page.tsx new file mode 100644 index 0000000000..8b04a3e673 --- /dev/null +++ b/pages/toggle-button/style-permutations.page.tsx @@ -0,0 +1,162 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React from 'react'; + +import ToggleButton, { ToggleButtonProps } from '~components/toggle-button'; + +import createPermutations from '../utils/permutations'; +import PermutationsView from '../utils/permutations-view'; +import ScreenshotArea from '../utils/screenshot-area'; + +const permutations = createPermutations([ + { + variant: ['normal'], + children: ['Subscribe'], + pressed: [false, true], + disabled: [false, true], + iconName: ['thumbs-up'], + pressedIconName: ['thumbs-up-filled'], + onChange: [() => {}], + style: [ + { + root: { + background: { + default: '#2563eb', + hover: '#1d4ed8', + active: '#1e40af', + disabled: '#bfdbfe', + pressed: '#1e3a8a', + }, + borderColor: { + default: '#2563eb', + hover: '#1d4ed8', + active: '#1e3a8a', + disabled: '#60a5fa', + pressed: '#1e3a8a', + }, + borderWidth: '2px', + borderRadius: '0', + boxShadow: { + default: '0 1px 2px rgba(0, 0, 0, 0.05)', + hover: '0 2px 4px rgba(59, 130, 246, 0.15)', + active: '0 1px 2px rgba(0, 0, 0, 0.05)', + disabled: 'none', + pressed: '0 0 0 4px rgba(59, 130, 246, 0.2)', + }, + color: { + default: '#ffffff', + hover: '#ffffff', + active: '#ffffff', + disabled: '#0c4a6e', + pressed: '#ffffff', + }, + paddingBlock: '10px', + paddingInline: '16px', + focusRing: { + borderColor: '#3b82f6', + borderRadius: '2px', + borderWidth: '3px', + }, + }, + }, + { + root: { + background: { + default: '#7c3aed', + hover: '#6d28d9', + active: '#5b21b6', + disabled: '#ddd6fe', + pressed: '#4c1d95', + }, + borderColor: { + default: '#7c3aed', + hover: '#6d28d9', + active: '#5b21b6', + disabled: '#c4b5fd', + pressed: '#5b21b6', + }, + borderWidth: '2px', + borderRadius: '12px', + boxShadow: { + default: '0 1px 3px rgba(0, 0, 0, 0.1)', + hover: '0 4px 6px rgba(139, 92, 246, 0.2)', + active: '0 2px 4px rgba(0, 0, 0, 0.1)', + disabled: 'none', + pressed: '0 0 0 4px rgba(139, 92, 246, 0.3)', + }, + color: { + default: '#ffffff', + hover: '#ffffff', + active: '#ffffff', + disabled: '#2e1065', + pressed: '#ffffff', + }, + paddingBlock: '12px', + paddingInline: '20px', + focusRing: { + borderColor: '#8b5cf6', + borderRadius: '14px', + borderWidth: '3px', + }, + }, + }, + ], + }, + { + variant: ['icon'], + pressed: [false, true], + disabled: [false, true], + iconName: ['thumbs-up'], + pressedIconName: ['thumbs-up-filled'], + onChange: [() => {}], + ariaLabel: ['Toggle'], + style: [ + { + root: { + color: { + default: 'light-dark(#3b82f6, #93c5fd)', + hover: 'light-dark(#2563eb, #60a5fa)', + active: 'light-dark(#1d4ed8, #3b82f6)', + disabled: 'light-dark(#93c5fd, #94a3b8)', + pressed: 'light-dark(#1e40af, #2563eb)', + }, + focusRing: { + borderColor: '#3b82f6', + borderRadius: '50%', + borderWidth: '3px', + }, + }, + }, + { + root: { + color: { + default: 'light-dark(#8b5cf6, #c4b5fd)', + hover: 'light-dark(#7c3aed, #a78bfa)', + active: 'light-dark(#6d28d9, #8b5cf6)', + disabled: 'light-dark(#c4b5fd, #94a3b8)', + pressed: 'light-dark(#5b21b6, #7c3aed)', + }, + focusRing: { + borderColor: '#8b5cf6', + borderRadius: '10px', + borderWidth: '3px', + }, + }, + }, + ], + }, +]); + +export default function ToggleButtonStylePermutations() { + return ( + <> +

Toggle Button Style Permutations

+ + } + /> + + + ); +} diff --git a/src/toggle-button/index.tsx b/src/toggle-button/index.tsx index bdfd2bdd3d..2eae84d17a 100644 --- a/src/toggle-button/index.tsx +++ b/src/toggle-button/index.tsx @@ -33,6 +33,7 @@ const ToggleButton = React.forwardRef( pressed = false, nativeButtonAttributes, onChange, + style, ...props }: ToggleButtonProps, ref: React.Ref @@ -68,6 +69,7 @@ const ToggleButton = React.forwardRef( pressed={pressed} nativeButtonAttributes={nativeButtonAttributes} onChange={onChange} + style={style} > {children} diff --git a/src/toggle-button/interfaces.ts b/src/toggle-button/interfaces.ts index 485a9ca753..b343b0ee5d 100644 --- a/src/toggle-button/interfaces.ts +++ b/src/toggle-button/interfaces.ts @@ -72,6 +72,11 @@ export interface ToggleButtonProps extends BaseComponentProps, Omit; + + /** + * @awsuiSystem core + */ + style?: ToggleButtonProps.Style; } export namespace ToggleButtonProps { @@ -87,4 +92,46 @@ export namespace ToggleButtonProps { */ focus(options?: FocusOptions): void; } + + export interface Style { + root?: { + background?: { + active?: string; + default?: string; + disabled?: string; + hover?: string; + pressed?: string; + }; + borderColor?: { + active?: string; + default?: string; + disabled?: string; + hover?: string; + pressed?: string; + }; + borderRadius?: string; + borderWidth?: string; + boxShadow?: { + active?: string; + default?: string; + disabled?: string; + hover?: string; + pressed?: string; + }; + color?: { + active?: string; + default?: string; + disabled?: string; + hover?: string; + pressed?: string; + }; + focusRing?: { + borderColor?: string; + borderRadius?: string; + borderWidth?: string; + }; + paddingBlock?: string; + paddingInline?: string; + }; + } } diff --git a/src/toggle-button/internal.tsx b/src/toggle-button/internal.tsx index e23865bf56..e7f3903c91 100644 --- a/src/toggle-button/internal.tsx +++ b/src/toggle-button/internal.tsx @@ -9,6 +9,7 @@ import InternalButton from '../button/internal'; import { fireNonCancelableEvent } from '../internal/events'; import { isDevelopment } from '../internal/is-development'; import { ToggleButtonProps } from './interfaces'; +import { getToggleButtonStyles } from './style'; import { getToggleIcon } from './util'; import styles from './styles.css.js'; @@ -28,6 +29,7 @@ export const InternalToggleButton = React.forwardRef( onChange, className, analyticsAction = 'click', + style, ...rest }: ToggleButtonProps & { __title?: string; analyticsAction?: string }, ref: React.Ref @@ -62,7 +64,10 @@ export const InternalToggleButton = React.forwardRef( }} {...rest} ref={ref} - nativeButtonAttributes={nativeButtonAttributes} + nativeButtonAttributes={{ + ...nativeButtonAttributes, + style: { ...nativeButtonAttributes?.style, ...getToggleButtonStyles(style) } as React.CSSProperties, + }} analyticsAction={analyticsAction} /> ); diff --git a/src/toggle-button/style.tsx b/src/toggle-button/style.tsx new file mode 100644 index 0000000000..e355cb0195 --- /dev/null +++ b/src/toggle-button/style.tsx @@ -0,0 +1,51 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { SYSTEM } from '../internal/environment'; +import customCssProps from '../internal/generated/custom-css-properties'; +import { ToggleButtonProps } from './interfaces'; + +export function getToggleButtonStyles(style: ToggleButtonProps['style']) { + if (SYSTEM !== 'core' || !style?.root) { + return undefined; + } + + return { + borderRadius: style?.root?.borderRadius, + borderWidth: style?.root?.borderWidth, + paddingBlock: style?.root?.paddingBlock, + paddingInline: style?.root?.paddingInline, + ...(style?.root?.background && { + [customCssProps.styleBackgroundActive]: style.root.background?.active, + [customCssProps.styleBackgroundDefault]: style.root.background?.default, + [customCssProps.styleBackgroundDisabled]: style.root.background?.disabled, + [customCssProps.styleBackgroundHover]: style.root.background?.hover, + [customCssProps.styleBackgroundPressed]: style.root.background?.pressed, + }), + ...(style?.root?.borderColor && { + [customCssProps.styleBorderColorActive]: style.root.borderColor?.active, + [customCssProps.styleBorderColorDefault]: style.root.borderColor?.default, + [customCssProps.styleBorderColorDisabled]: style.root.borderColor?.disabled, + [customCssProps.styleBorderColorHover]: style.root.borderColor?.hover, + [customCssProps.styleBorderColorPressed]: style.root.borderColor?.pressed, + }), + ...(style?.root?.boxShadow && { + [customCssProps.styleBoxShadowActive]: style.root.boxShadow?.active, + [customCssProps.styleBoxShadowDefault]: style.root.boxShadow?.default, + [customCssProps.styleBoxShadowDisabled]: style.root.boxShadow?.disabled, + [customCssProps.styleBoxShadowHover]: style.root.boxShadow?.hover, + [customCssProps.styleBoxShadowPressed]: style.root.boxShadow?.pressed, + }), + ...(style?.root?.color && { + [customCssProps.styleColorActive]: style.root.color?.active, + [customCssProps.styleColorDefault]: style.root.color?.default, + [customCssProps.styleColorDisabled]: style.root.color?.disabled, + [customCssProps.styleColorHover]: style.root.color?.hover, + [customCssProps.styleColorPressed]: style.root.color?.pressed, + }), + ...(style?.root?.focusRing && { + [customCssProps.styleFocusRingBorderColor]: style.root.focusRing?.borderColor, + [customCssProps.styleFocusRingBorderRadius]: style.root.focusRing?.borderRadius, + [customCssProps.styleFocusRingBorderWidth]: style.root.focusRing?.borderWidth, + }), + }; +} diff --git a/src/toggle-button/styles.scss b/src/toggle-button/styles.scss index eacd937eb4..a51431e052 100644 --- a/src/toggle-button/styles.scss +++ b/src/toggle-button/styles.scss @@ -4,15 +4,18 @@ */ @use '../internal/styles/tokens' as awsui; +@use '../internal/generated/custom-css-properties/index.scss' as custom-props; .variant-normal.pressed { - background: awsui.$color-background-toggle-button-normal-pressed; - border-color: awsui.$color-border-toggle-button-normal-pressed; - color: awsui.$color-text-toggle-button-normal-pressed; + background: var(#{custom-props.$styleBackgroundPressed}, awsui.$color-background-toggle-button-normal-pressed); + border-color: var(#{custom-props.$styleBorderColorPressed}, awsui.$color-border-toggle-button-normal-pressed); + color: var(#{custom-props.$styleColorPressed}, awsui.$color-text-toggle-button-normal-pressed); + box-shadow: var(#{custom-props.$styleBoxShadowPressed}); } .variant-icon.pressed { - background: transparent; - border-color: transparent; - color: awsui.$color-text-toggle-button-icon-pressed; + background: var(#{custom-props.$styleBackgroundPressed}, transparent); + border-color: var(#{custom-props.$styleBorderColorPressed}, transparent); + color: var(#{custom-props.$styleColorPressed}, awsui.$color-text-toggle-button-icon-pressed); + box-shadow: var(#{custom-props.$styleBoxShadowPressed}); } From c8ecfb31198bb07c5260fee18d7e40a5a6bfd1a8 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Sat, 8 Nov 2025 19:13:09 +0100 Subject: [PATCH 02/13] tests: add unit test for style api --- .../__snapshots__/documenter.test.ts.snap | 215 +++++++++++++ src/toggle-button/__tests__/styles.test.tsx | 300 ++++++++++++++++++ 2 files changed, 515 insertions(+) create mode 100644 src/toggle-button/__tests__/styles.test.tsx diff --git a/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap b/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap index fb6bd7c226..5a9b0c92bc 100644 --- a/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap +++ b/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap @@ -27172,6 +27172,221 @@ We do not support using this attribute to apply custom styling.", "optional": true, "type": "string", }, + { + "inlineType": { + "name": "ToggleButtonProps.Style", + "properties": [ + { + "inlineType": { + "name": "object", + "properties": [ + { + "inlineType": { + "name": "object", + "properties": [ + { + "name": "active", + "optional": true, + "type": "string", + }, + { + "name": "default", + "optional": true, + "type": "string", + }, + { + "name": "disabled", + "optional": true, + "type": "string", + }, + { + "name": "hover", + "optional": true, + "type": "string", + }, + { + "name": "pressed", + "optional": true, + "type": "string", + }, + ], + "type": "object", + }, + "name": "background", + "optional": true, + "type": "{ active?: string | undefined; default?: string | undefined; disabled?: string | undefined; hover?: string | undefined; pressed?: string | undefined; }", + }, + { + "inlineType": { + "name": "object", + "properties": [ + { + "name": "active", + "optional": true, + "type": "string", + }, + { + "name": "default", + "optional": true, + "type": "string", + }, + { + "name": "disabled", + "optional": true, + "type": "string", + }, + { + "name": "hover", + "optional": true, + "type": "string", + }, + { + "name": "pressed", + "optional": true, + "type": "string", + }, + ], + "type": "object", + }, + "name": "borderColor", + "optional": true, + "type": "{ active?: string | undefined; default?: string | undefined; disabled?: string | undefined; hover?: string | undefined; pressed?: string | undefined; }", + }, + { + "name": "borderRadius", + "optional": true, + "type": "string", + }, + { + "name": "borderWidth", + "optional": true, + "type": "string", + }, + { + "inlineType": { + "name": "object", + "properties": [ + { + "name": "active", + "optional": true, + "type": "string", + }, + { + "name": "default", + "optional": true, + "type": "string", + }, + { + "name": "disabled", + "optional": true, + "type": "string", + }, + { + "name": "hover", + "optional": true, + "type": "string", + }, + { + "name": "pressed", + "optional": true, + "type": "string", + }, + ], + "type": "object", + }, + "name": "boxShadow", + "optional": true, + "type": "{ active?: string | undefined; default?: string | undefined; disabled?: string | undefined; hover?: string | undefined; pressed?: string | undefined; }", + }, + { + "inlineType": { + "name": "object", + "properties": [ + { + "name": "active", + "optional": true, + "type": "string", + }, + { + "name": "default", + "optional": true, + "type": "string", + }, + { + "name": "disabled", + "optional": true, + "type": "string", + }, + { + "name": "hover", + "optional": true, + "type": "string", + }, + { + "name": "pressed", + "optional": true, + "type": "string", + }, + ], + "type": "object", + }, + "name": "color", + "optional": true, + "type": "{ active?: string | undefined; default?: string | undefined; disabled?: string | undefined; hover?: string | undefined; pressed?: string | undefined; }", + }, + { + "inlineType": { + "name": "object", + "properties": [ + { + "name": "borderColor", + "optional": true, + "type": "string", + }, + { + "name": "borderRadius", + "optional": true, + "type": "string", + }, + { + "name": "borderWidth", + "optional": true, + "type": "string", + }, + ], + "type": "object", + }, + "name": "focusRing", + "optional": true, + "type": "{ borderColor?: string | undefined; borderRadius?: string | undefined; borderWidth?: string | undefined; }", + }, + { + "name": "paddingBlock", + "optional": true, + "type": "string", + }, + { + "name": "paddingInline", + "optional": true, + "type": "string", + }, + ], + "type": "object", + }, + "name": "root", + "optional": true, + "type": "{ background?: { active?: string | undefined; default?: string | undefined; disabled?: string | undefined; hover?: string | undefined; pressed?: string | undefined; } | undefined; ... 7 more ...; paddingInline?: string | undefined; }", + }, + ], + "type": "object", + }, + "name": "style", + "optional": true, + "systemTags": [ + "core", + ], + "type": "ToggleButtonProps.Style", + }, { "defaultValue": "'normal'", "description": "Determines the general styling of the toggle button as follows: diff --git a/src/toggle-button/__tests__/styles.test.tsx b/src/toggle-button/__tests__/styles.test.tsx new file mode 100644 index 0000000000..90de39165b --- /dev/null +++ b/src/toggle-button/__tests__/styles.test.tsx @@ -0,0 +1,300 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import customCssProps from '../../internal/generated/custom-css-properties'; +import { getToggleButtonStyles } from '../style'; + +// Mock the environment module +jest.mock('../../internal/environment', () => ({ + SYSTEM: 'core', +})); + +const STATES = ['default', 'disabled', 'hover', 'active', 'pressed'] as const; +const STATE_PROPERTIES = ['background', 'borderColor', 'boxShadow', 'color'] as const; + +const CSS_PROPERTY_MAP = { + background: { + default: customCssProps.styleBackgroundDefault, + disabled: customCssProps.styleBackgroundDisabled, + hover: customCssProps.styleBackgroundHover, + active: customCssProps.styleBackgroundActive, + pressed: customCssProps.styleBackgroundPressed, + }, + borderColor: { + default: customCssProps.styleBorderColorDefault, + disabled: customCssProps.styleBorderColorDisabled, + hover: customCssProps.styleBorderColorHover, + active: customCssProps.styleBorderColorActive, + pressed: customCssProps.styleBorderColorPressed, + }, + boxShadow: { + default: customCssProps.styleBoxShadowDefault, + disabled: customCssProps.styleBoxShadowDisabled, + hover: customCssProps.styleBoxShadowHover, + active: customCssProps.styleBoxShadowActive, + pressed: customCssProps.styleBoxShadowPressed, + }, + color: { + default: customCssProps.styleColorDefault, + disabled: customCssProps.styleColorDisabled, + hover: customCssProps.styleColorHover, + active: customCssProps.styleColorActive, + pressed: customCssProps.styleColorPressed, + }, +} as const; + +describe('getToggleButtonStyles', () => { + afterEach(() => { + jest.resetModules(); + }); + + test('returns undefined for undefined or empty style objects', () => { + expect(getToggleButtonStyles(undefined)).toBeUndefined(); + expect(getToggleButtonStyles({})).toBeUndefined(); + }); + + test('handles all possible style configurations', () => { + const allStyles = { + root: { + borderRadius: '4px', + borderWidth: '1px', + paddingBlock: '8px', + paddingInline: '12px', + background: { + default: '#ffffff', + disabled: '#f0f0f0', + hover: '#fafafa', + active: '#eeeeee', + pressed: '#e0e0e0', + }, + borderColor: { + default: '#cccccc', + disabled: '#e0e0e0', + hover: '#999999', + active: '#666666', + pressed: '#0073bb', + }, + boxShadow: { + default: 'none', + disabled: 'none', + hover: '0 1px 2px rgba(0,0,0,0.1)', + active: '0 1px 2px rgba(0,0,0,0.2)', + pressed: '0 0 0 2px #0073bb', + }, + color: { + default: '#000000', + disabled: '#999999', + hover: '#000000', + active: '#000000', + pressed: '#0073bb', + }, + focusRing: { + borderColor: '#0073bb', + borderRadius: '6px', + borderWidth: '2px', + }, + }, + }; + + expect(getToggleButtonStyles(allStyles)).toEqual({ + borderRadius: '4px', + borderWidth: '1px', + paddingBlock: '8px', + paddingInline: '12px', + [customCssProps.styleBackgroundDefault]: '#ffffff', + [customCssProps.styleBackgroundDisabled]: '#f0f0f0', + [customCssProps.styleBackgroundHover]: '#fafafa', + [customCssProps.styleBackgroundActive]: '#eeeeee', + [customCssProps.styleBackgroundPressed]: '#e0e0e0', + [customCssProps.styleBorderColorDefault]: '#cccccc', + [customCssProps.styleBorderColorDisabled]: '#e0e0e0', + [customCssProps.styleBorderColorHover]: '#999999', + [customCssProps.styleBorderColorActive]: '#666666', + [customCssProps.styleBorderColorPressed]: '#0073bb', + [customCssProps.styleBoxShadowDefault]: 'none', + [customCssProps.styleBoxShadowDisabled]: 'none', + [customCssProps.styleBoxShadowHover]: '0 1px 2px rgba(0,0,0,0.1)', + [customCssProps.styleBoxShadowActive]: '0 1px 2px rgba(0,0,0,0.2)', + [customCssProps.styleBoxShadowPressed]: '0 0 0 2px #0073bb', + [customCssProps.styleColorDefault]: '#000000', + [customCssProps.styleColorDisabled]: '#999999', + [customCssProps.styleColorHover]: '#000000', + [customCssProps.styleColorActive]: '#000000', + [customCssProps.styleColorPressed]: '#0073bb', + [customCssProps.styleFocusRingBorderColor]: '#0073bb', + [customCssProps.styleFocusRingBorderRadius]: '6px', + [customCssProps.styleFocusRingBorderWidth]: '2px', + }); + }); + + test('returns undefined when SYSTEM is not core', async () => { + jest.resetModules(); + jest.doMock('../../internal/environment', () => ({ + SYSTEM: 'visual-refresh', + })); + + const { getToggleButtonStyles: getToggleButtonStylesNonCore } = await import('../style'); + + const result = getToggleButtonStylesNonCore({ root: { borderRadius: '4px' } }); + + expect(result).toBeUndefined(); + }); + + describe('individual root properties', () => { + const rootProperties = { + borderRadius: '8px', + borderWidth: '2px', + paddingBlock: '10px', + paddingInline: '16px', + }; + + Object.entries(rootProperties).forEach(([property, value]) => { + test(`${property} only`, () => { + const result = getToggleButtonStyles({ root: { [property]: value } }); + + expect(result).toEqual({ + borderRadius: property === 'borderRadius' ? value : undefined, + borderWidth: property === 'borderWidth' ? value : undefined, + paddingBlock: property === 'paddingBlock' ? value : undefined, + paddingInline: property === 'paddingInline' ? value : undefined, + }); + }); + }); + }); + + describe('state properties', () => { + STATE_PROPERTIES.forEach(property => { + describe(`${property}`, () => { + STATES.forEach(state => { + test(`${state} only`, () => { + const testValue = `test-${property}-${state}`; + const style = { + root: { + [property]: { [state]: testValue }, + }, + }; + + const result = getToggleButtonStyles(style); + + // Verify base properties are undefined + expect(result?.borderRadius).toBeUndefined(); + expect(result?.borderWidth).toBeUndefined(); + expect(result?.paddingBlock).toBeUndefined(); + expect(result?.paddingInline).toBeUndefined(); + + // Verify the specific state is set + expect(result).toHaveProperty(CSS_PROPERTY_MAP[property][state], testValue); + + // Verify other states of the same property are undefined + STATES.filter(s => s !== state).forEach(otherState => { + expect(result).toHaveProperty(CSS_PROPERTY_MAP[property][otherState], undefined); + }); + }); + }); + + test(`all states together`, () => { + const allStateValues = STATES.reduce( + (acc, state) => ({ + ...acc, + [state]: `test-${property}-${state}`, + }), + {} + ); + + const style = { root: { [property]: allStateValues } }; + const result = getToggleButtonStyles(style); + + STATES.forEach(state => { + expect(result).toHaveProperty(CSS_PROPERTY_MAP[property][state], `test-${property}-${state}`); + }); + }); + }); + }); + }); + + describe('focusRing properties', () => { + const focusRingProperties = { + borderColor: { prop: customCssProps.styleFocusRingBorderColor, value: '#10b981' }, + borderRadius: { prop: customCssProps.styleFocusRingBorderRadius, value: '10px' }, + borderWidth: { prop: customCssProps.styleFocusRingBorderWidth, value: '3px' }, + }; + + Object.entries(focusRingProperties).forEach(([property, { prop, value }]) => { + test(`${property} only`, () => { + const result = getToggleButtonStyles({ + root: { focusRing: { [property]: value } }, + }); + + expect(result).toEqual({ + borderRadius: undefined, + borderWidth: undefined, + paddingBlock: undefined, + paddingInline: undefined, + [prop]: value, + ...Object.entries(focusRingProperties).reduce( + (acc, [key, { prop: p }]) => { + if (key !== property) { + acc[p] = undefined; + } + return acc; + }, + {} as Record + ), + }); + }); + }); + + test('all focusRing properties together', () => { + const result = getToggleButtonStyles({ + root: { + focusRing: { + borderColor: '#10b981', + borderRadius: '10px', + borderWidth: '3px', + }, + }, + }); + + expect(result).toEqual({ + borderRadius: undefined, + borderWidth: undefined, + paddingBlock: undefined, + paddingInline: undefined, + [customCssProps.styleFocusRingBorderColor]: '#10b981', + [customCssProps.styleFocusRingBorderRadius]: '10px', + [customCssProps.styleFocusRingBorderWidth]: '3px', + }); + }); + }); + + test('handles mixed property types', () => { + const result = getToggleButtonStyles({ + root: { + borderRadius: '8px', + paddingBlock: '10px', + background: { default: '#3b82f6', pressed: '#1e40af' }, + color: { hover: '#ffffff', pressed: '#f0f0f0' }, + focusRing: { borderColor: '#3b82f6' }, + }, + }); + + expect(result).toEqual({ + borderRadius: '8px', + borderWidth: undefined, + paddingBlock: '10px', + paddingInline: undefined, + [customCssProps.styleBackgroundDefault]: '#3b82f6', + [customCssProps.styleBackgroundDisabled]: undefined, + [customCssProps.styleBackgroundHover]: undefined, + [customCssProps.styleBackgroundActive]: undefined, + [customCssProps.styleBackgroundPressed]: '#1e40af', + [customCssProps.styleColorDefault]: undefined, + [customCssProps.styleColorDisabled]: undefined, + [customCssProps.styleColorHover]: '#ffffff', + [customCssProps.styleColorActive]: undefined, + [customCssProps.styleColorPressed]: '#f0f0f0', + [customCssProps.styleFocusRingBorderColor]: '#3b82f6', + [customCssProps.styleFocusRingBorderRadius]: undefined, + [customCssProps.styleFocusRingBorderWidth]: undefined, + }); + }); +}); From 7714e3bc9c23d0057dc30c2cb6e993799953e632 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Tue, 11 Nov 2025 20:07:24 +0100 Subject: [PATCH 03/13] refactor: change logic for building style properties --- src/toggle-button/style.tsx | 64 ++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/src/toggle-button/style.tsx b/src/toggle-button/style.tsx index e355cb0195..6c49e46348 100644 --- a/src/toggle-button/style.tsx +++ b/src/toggle-button/style.tsx @@ -5,47 +5,39 @@ import customCssProps from '../internal/generated/custom-css-properties'; import { ToggleButtonProps } from './interfaces'; export function getToggleButtonStyles(style: ToggleButtonProps['style']) { - if (SYSTEM !== 'core' || !style?.root) { - return undefined; + if (SYSTEM !== 'core') { + return {}; } - return { + const properties = { borderRadius: style?.root?.borderRadius, borderWidth: style?.root?.borderWidth, paddingBlock: style?.root?.paddingBlock, paddingInline: style?.root?.paddingInline, - ...(style?.root?.background && { - [customCssProps.styleBackgroundActive]: style.root.background?.active, - [customCssProps.styleBackgroundDefault]: style.root.background?.default, - [customCssProps.styleBackgroundDisabled]: style.root.background?.disabled, - [customCssProps.styleBackgroundHover]: style.root.background?.hover, - [customCssProps.styleBackgroundPressed]: style.root.background?.pressed, - }), - ...(style?.root?.borderColor && { - [customCssProps.styleBorderColorActive]: style.root.borderColor?.active, - [customCssProps.styleBorderColorDefault]: style.root.borderColor?.default, - [customCssProps.styleBorderColorDisabled]: style.root.borderColor?.disabled, - [customCssProps.styleBorderColorHover]: style.root.borderColor?.hover, - [customCssProps.styleBorderColorPressed]: style.root.borderColor?.pressed, - }), - ...(style?.root?.boxShadow && { - [customCssProps.styleBoxShadowActive]: style.root.boxShadow?.active, - [customCssProps.styleBoxShadowDefault]: style.root.boxShadow?.default, - [customCssProps.styleBoxShadowDisabled]: style.root.boxShadow?.disabled, - [customCssProps.styleBoxShadowHover]: style.root.boxShadow?.hover, - [customCssProps.styleBoxShadowPressed]: style.root.boxShadow?.pressed, - }), - ...(style?.root?.color && { - [customCssProps.styleColorActive]: style.root.color?.active, - [customCssProps.styleColorDefault]: style.root.color?.default, - [customCssProps.styleColorDisabled]: style.root.color?.disabled, - [customCssProps.styleColorHover]: style.root.color?.hover, - [customCssProps.styleColorPressed]: style.root.color?.pressed, - }), - ...(style?.root?.focusRing && { - [customCssProps.styleFocusRingBorderColor]: style.root.focusRing?.borderColor, - [customCssProps.styleFocusRingBorderRadius]: style.root.focusRing?.borderRadius, - [customCssProps.styleFocusRingBorderWidth]: style.root.focusRing?.borderWidth, - }), + [customCssProps.styleBackgroundActive]: style?.root?.background?.active, + [customCssProps.styleBackgroundDefault]: style?.root?.background?.default, + [customCssProps.styleBackgroundDisabled]: style?.root?.background?.disabled, + [customCssProps.styleBackgroundHover]: style?.root?.background?.hover, + [customCssProps.styleBackgroundPressed]: style?.root?.background?.pressed, + [customCssProps.styleBorderColorActive]: style?.root?.borderColor?.active, + [customCssProps.styleBorderColorDefault]: style?.root?.borderColor?.default, + [customCssProps.styleBorderColorDisabled]: style?.root?.borderColor?.disabled, + [customCssProps.styleBorderColorHover]: style?.root?.borderColor?.hover, + [customCssProps.styleBorderColorPressed]: style?.root?.borderColor?.pressed, + [customCssProps.styleBoxShadowActive]: style?.root?.boxShadow?.active, + [customCssProps.styleBoxShadowDefault]: style?.root?.boxShadow?.default, + [customCssProps.styleBoxShadowDisabled]: style?.root?.boxShadow?.disabled, + [customCssProps.styleBoxShadowHover]: style?.root?.boxShadow?.hover, + [customCssProps.styleBoxShadowPressed]: style?.root?.boxShadow?.pressed, + [customCssProps.styleColorActive]: style?.root?.color?.active, + [customCssProps.styleColorDefault]: style?.root?.color?.default, + [customCssProps.styleColorDisabled]: style?.root?.color?.disabled, + [customCssProps.styleColorHover]: style?.root?.color?.hover, + [customCssProps.styleColorPressed]: style?.root?.color?.pressed, + [customCssProps.styleFocusRingBorderColor]: style?.root?.focusRing?.borderColor, + [customCssProps.styleFocusRingBorderRadius]: style?.root?.focusRing?.borderRadius, + [customCssProps.styleFocusRingBorderWidth]: style?.root?.focusRing?.borderWidth, }; + + return Object.fromEntries(Object.entries(properties).filter(([, value]) => value)); } From 1c9288625553b190fb4db10405fab44bb1d39c08 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Tue, 11 Nov 2025 20:16:23 +0100 Subject: [PATCH 04/13] test: adapt for latest changes --- src/toggle-button/__tests__/styles.test.tsx | 56 +++------------------ 1 file changed, 8 insertions(+), 48 deletions(-) diff --git a/src/toggle-button/__tests__/styles.test.tsx b/src/toggle-button/__tests__/styles.test.tsx index 90de39165b..07f08f0486 100644 --- a/src/toggle-button/__tests__/styles.test.tsx +++ b/src/toggle-button/__tests__/styles.test.tsx @@ -47,9 +47,9 @@ describe('getToggleButtonStyles', () => { jest.resetModules(); }); - test('returns undefined for undefined or empty style objects', () => { - expect(getToggleButtonStyles(undefined)).toBeUndefined(); - expect(getToggleButtonStyles({})).toBeUndefined(); + test('returns empty object for undefined or empty style objects', () => { + expect(getToggleButtonStyles(undefined)).toEqual({}); + expect(getToggleButtonStyles({})).toEqual({}); }); test('handles all possible style configurations', () => { @@ -126,7 +126,7 @@ describe('getToggleButtonStyles', () => { }); }); - test('returns undefined when SYSTEM is not core', async () => { + test('returns empty object when SYSTEM is not core', async () => { jest.resetModules(); jest.doMock('../../internal/environment', () => ({ SYSTEM: 'visual-refresh', @@ -136,7 +136,7 @@ describe('getToggleButtonStyles', () => { const result = getToggleButtonStylesNonCore({ root: { borderRadius: '4px' } }); - expect(result).toBeUndefined(); + expect(result).toEqual({}); }); describe('individual root properties', () => { @@ -152,10 +152,7 @@ describe('getToggleButtonStyles', () => { const result = getToggleButtonStyles({ root: { [property]: value } }); expect(result).toEqual({ - borderRadius: property === 'borderRadius' ? value : undefined, - borderWidth: property === 'borderWidth' ? value : undefined, - paddingBlock: property === 'paddingBlock' ? value : undefined, - paddingInline: property === 'paddingInline' ? value : undefined, + [property]: value, }); }); }); @@ -175,18 +172,8 @@ describe('getToggleButtonStyles', () => { const result = getToggleButtonStyles(style); - // Verify base properties are undefined - expect(result?.borderRadius).toBeUndefined(); - expect(result?.borderWidth).toBeUndefined(); - expect(result?.paddingBlock).toBeUndefined(); - expect(result?.paddingInline).toBeUndefined(); - - // Verify the specific state is set - expect(result).toHaveProperty(CSS_PROPERTY_MAP[property][state], testValue); - - // Verify other states of the same property are undefined - STATES.filter(s => s !== state).forEach(otherState => { - expect(result).toHaveProperty(CSS_PROPERTY_MAP[property][otherState], undefined); + expect(result).toEqual({ + [CSS_PROPERTY_MAP[property][state]]: testValue, }); }); }); @@ -225,20 +212,7 @@ describe('getToggleButtonStyles', () => { }); expect(result).toEqual({ - borderRadius: undefined, - borderWidth: undefined, - paddingBlock: undefined, - paddingInline: undefined, [prop]: value, - ...Object.entries(focusRingProperties).reduce( - (acc, [key, { prop: p }]) => { - if (key !== property) { - acc[p] = undefined; - } - return acc; - }, - {} as Record - ), }); }); }); @@ -255,10 +229,6 @@ describe('getToggleButtonStyles', () => { }); expect(result).toEqual({ - borderRadius: undefined, - borderWidth: undefined, - paddingBlock: undefined, - paddingInline: undefined, [customCssProps.styleFocusRingBorderColor]: '#10b981', [customCssProps.styleFocusRingBorderRadius]: '10px', [customCssProps.styleFocusRingBorderWidth]: '3px', @@ -279,22 +249,12 @@ describe('getToggleButtonStyles', () => { expect(result).toEqual({ borderRadius: '8px', - borderWidth: undefined, paddingBlock: '10px', - paddingInline: undefined, [customCssProps.styleBackgroundDefault]: '#3b82f6', - [customCssProps.styleBackgroundDisabled]: undefined, - [customCssProps.styleBackgroundHover]: undefined, - [customCssProps.styleBackgroundActive]: undefined, [customCssProps.styleBackgroundPressed]: '#1e40af', - [customCssProps.styleColorDefault]: undefined, - [customCssProps.styleColorDisabled]: undefined, [customCssProps.styleColorHover]: '#ffffff', - [customCssProps.styleColorActive]: undefined, [customCssProps.styleColorPressed]: '#f0f0f0', [customCssProps.styleFocusRingBorderColor]: '#3b82f6', - [customCssProps.styleFocusRingBorderRadius]: undefined, - [customCssProps.styleFocusRingBorderWidth]: undefined, }); }); }); From 9329336860a9377f57e4be42007455b4a5db69e8 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Wed, 12 Nov 2025 11:18:01 +0100 Subject: [PATCH 05/13] recactor: make filter logic more explicit --- src/toggle-button/style.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/toggle-button/style.tsx b/src/toggle-button/style.tsx index 6c49e46348..5d4397fd2c 100644 --- a/src/toggle-button/style.tsx +++ b/src/toggle-button/style.tsx @@ -39,5 +39,5 @@ export function getToggleButtonStyles(style: ToggleButtonProps['style']) { [customCssProps.styleFocusRingBorderWidth]: style?.root?.focusRing?.borderWidth, }; - return Object.fromEntries(Object.entries(properties).filter(([, value]) => value)); + return Object.fromEntries(Object.entries(properties).filter(([, value]) => value !== undefined)); } From 5cd09541a5805ee938c023f30c04ef7ff17e9162 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Wed, 12 Nov 2025 11:24:03 +0100 Subject: [PATCH 06/13] refactor: extract styles into names variables --- .../toggle-button/style-permutations.page.tsx | 238 +++++++++--------- 1 file changed, 120 insertions(+), 118 deletions(-) diff --git a/pages/toggle-button/style-permutations.page.tsx b/pages/toggle-button/style-permutations.page.tsx index 8b04a3e673..1697e7d701 100644 --- a/pages/toggle-button/style-permutations.page.tsx +++ b/pages/toggle-button/style-permutations.page.tsx @@ -8,6 +8,124 @@ import createPermutations from '../utils/permutations'; import PermutationsView from '../utils/permutations-view'; import ScreenshotArea from '../utils/screenshot-area'; +const blueSquareStyle: ToggleButtonProps['style'] = { + root: { + background: { + default: '#2563eb', + hover: '#1d4ed8', + active: '#1e40af', + disabled: '#bfdbfe', + pressed: '#1e3a8a', + }, + borderColor: { + default: '#2563eb', + hover: '#1d4ed8', + active: '#1e3a8a', + disabled: '#60a5fa', + pressed: '#1e3a8a', + }, + borderWidth: '2px', + borderRadius: '0', + boxShadow: { + default: '0 1px 2px rgba(0, 0, 0, 0.05)', + hover: '0 2px 4px rgba(59, 130, 246, 0.15)', + active: '0 1px 2px rgba(0, 0, 0, 0.05)', + disabled: 'none', + pressed: '0 0 0 4px rgba(59, 130, 246, 0.2)', + }, + color: { + default: '#ffffff', + hover: '#ffffff', + active: '#ffffff', + disabled: '#0c4a6e', + pressed: '#ffffff', + }, + paddingBlock: '10px', + paddingInline: '16px', + focusRing: { + borderColor: '#3b82f6', + borderRadius: '2px', + borderWidth: '3px', + }, + }, +}; + +const purpleRoundedStyle: ToggleButtonProps['style'] = { + root: { + background: { + default: '#7c3aed', + hover: '#6d28d9', + active: '#5b21b6', + disabled: '#ddd6fe', + pressed: '#4c1d95', + }, + borderColor: { + default: '#7c3aed', + hover: '#6d28d9', + active: '#5b21b6', + disabled: '#c4b5fd', + pressed: '#5b21b6', + }, + borderWidth: '2px', + borderRadius: '12px', + boxShadow: { + default: '0 1px 3px rgba(0, 0, 0, 0.1)', + hover: '0 4px 6px rgba(139, 92, 246, 0.2)', + active: '0 2px 4px rgba(0, 0, 0, 0.1)', + disabled: 'none', + pressed: '0 0 0 4px rgba(139, 92, 246, 0.3)', + }, + color: { + default: '#ffffff', + hover: '#ffffff', + active: '#ffffff', + disabled: '#2e1065', + pressed: '#ffffff', + }, + paddingBlock: '12px', + paddingInline: '20px', + focusRing: { + borderColor: '#8b5cf6', + borderRadius: '14px', + borderWidth: '3px', + }, + }, +}; + +const blueIconStyle: ToggleButtonProps['style'] = { + root: { + color: { + default: 'light-dark(#3b82f6, #93c5fd)', + hover: 'light-dark(#2563eb, #60a5fa)', + active: 'light-dark(#1d4ed8, #3b82f6)', + disabled: 'light-dark(#93c5fd, #94a3b8)', + pressed: 'light-dark(#1e40af, #2563eb)', + }, + focusRing: { + borderColor: '#3b82f6', + borderRadius: '50%', + borderWidth: '3px', + }, + }, +}; + +const purpleIconStyle: ToggleButtonProps['style'] = { + root: { + color: { + default: 'light-dark(#8b5cf6, #c4b5fd)', + hover: 'light-dark(#7c3aed, #a78bfa)', + active: 'light-dark(#6d28d9, #8b5cf6)', + disabled: 'light-dark(#c4b5fd, #94a3b8)', + pressed: 'light-dark(#5b21b6, #7c3aed)', + }, + focusRing: { + borderColor: '#8b5cf6', + borderRadius: '10px', + borderWidth: '3px', + }, + }, +}; + const permutations = createPermutations([ { variant: ['normal'], @@ -17,90 +135,7 @@ const permutations = createPermutations([ iconName: ['thumbs-up'], pressedIconName: ['thumbs-up-filled'], onChange: [() => {}], - style: [ - { - root: { - background: { - default: '#2563eb', - hover: '#1d4ed8', - active: '#1e40af', - disabled: '#bfdbfe', - pressed: '#1e3a8a', - }, - borderColor: { - default: '#2563eb', - hover: '#1d4ed8', - active: '#1e3a8a', - disabled: '#60a5fa', - pressed: '#1e3a8a', - }, - borderWidth: '2px', - borderRadius: '0', - boxShadow: { - default: '0 1px 2px rgba(0, 0, 0, 0.05)', - hover: '0 2px 4px rgba(59, 130, 246, 0.15)', - active: '0 1px 2px rgba(0, 0, 0, 0.05)', - disabled: 'none', - pressed: '0 0 0 4px rgba(59, 130, 246, 0.2)', - }, - color: { - default: '#ffffff', - hover: '#ffffff', - active: '#ffffff', - disabled: '#0c4a6e', - pressed: '#ffffff', - }, - paddingBlock: '10px', - paddingInline: '16px', - focusRing: { - borderColor: '#3b82f6', - borderRadius: '2px', - borderWidth: '3px', - }, - }, - }, - { - root: { - background: { - default: '#7c3aed', - hover: '#6d28d9', - active: '#5b21b6', - disabled: '#ddd6fe', - pressed: '#4c1d95', - }, - borderColor: { - default: '#7c3aed', - hover: '#6d28d9', - active: '#5b21b6', - disabled: '#c4b5fd', - pressed: '#5b21b6', - }, - borderWidth: '2px', - borderRadius: '12px', - boxShadow: { - default: '0 1px 3px rgba(0, 0, 0, 0.1)', - hover: '0 4px 6px rgba(139, 92, 246, 0.2)', - active: '0 2px 4px rgba(0, 0, 0, 0.1)', - disabled: 'none', - pressed: '0 0 0 4px rgba(139, 92, 246, 0.3)', - }, - color: { - default: '#ffffff', - hover: '#ffffff', - active: '#ffffff', - disabled: '#2e1065', - pressed: '#ffffff', - }, - paddingBlock: '12px', - paddingInline: '20px', - focusRing: { - borderColor: '#8b5cf6', - borderRadius: '14px', - borderWidth: '3px', - }, - }, - }, - ], + style: [blueSquareStyle, purpleRoundedStyle], }, { variant: ['icon'], @@ -110,40 +145,7 @@ const permutations = createPermutations([ pressedIconName: ['thumbs-up-filled'], onChange: [() => {}], ariaLabel: ['Toggle'], - style: [ - { - root: { - color: { - default: 'light-dark(#3b82f6, #93c5fd)', - hover: 'light-dark(#2563eb, #60a5fa)', - active: 'light-dark(#1d4ed8, #3b82f6)', - disabled: 'light-dark(#93c5fd, #94a3b8)', - pressed: 'light-dark(#1e40af, #2563eb)', - }, - focusRing: { - borderColor: '#3b82f6', - borderRadius: '50%', - borderWidth: '3px', - }, - }, - }, - { - root: { - color: { - default: 'light-dark(#8b5cf6, #c4b5fd)', - hover: 'light-dark(#7c3aed, #a78bfa)', - active: 'light-dark(#6d28d9, #8b5cf6)', - disabled: 'light-dark(#c4b5fd, #94a3b8)', - pressed: 'light-dark(#5b21b6, #7c3aed)', - }, - focusRing: { - borderColor: '#8b5cf6', - borderRadius: '10px', - borderWidth: '3px', - }, - }, - }, - ], + style: [blueIconStyle, purpleIconStyle], }, ]); From 8f3e05d113bcc280ab9008b117c3d6de8c99e185 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Wed, 12 Nov 2025 11:52:20 +0100 Subject: [PATCH 07/13] retrigger checks From e1fe6b4cb97c2c46e90bcd6e0a591aa84a57d96c Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Wed, 12 Nov 2025 13:42:36 +0100 Subject: [PATCH 08/13] refactor: use snapshot tests --- .../__snapshots__/styles.test.tsx.snap | 33 +++++++++++++++++++ src/toggle-button/__tests__/styles.test.tsx | 30 +---------------- 2 files changed, 34 insertions(+), 29 deletions(-) create mode 100644 src/toggle-button/__tests__/__snapshots__/styles.test.tsx.snap diff --git a/src/toggle-button/__tests__/__snapshots__/styles.test.tsx.snap b/src/toggle-button/__tests__/__snapshots__/styles.test.tsx.snap new file mode 100644 index 0000000000..9472617e01 --- /dev/null +++ b/src/toggle-button/__tests__/__snapshots__/styles.test.tsx.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getToggleButtonStyles handles all possible style configurations 1`] = ` +{ + "--awsui-style-background-active-o05uld": "#eeeeee", + "--awsui-style-background-default-o05uld": "#ffffff", + "--awsui-style-background-disabled-o05uld": "#f0f0f0", + "--awsui-style-background-hover-o05uld": "#fafafa", + "--awsui-style-background-pressed-o05uld": "#e0e0e0", + "--awsui-style-border-color-active-o05uld": "#666666", + "--awsui-style-border-color-default-o05uld": "#cccccc", + "--awsui-style-border-color-disabled-o05uld": "#e0e0e0", + "--awsui-style-border-color-hover-o05uld": "#999999", + "--awsui-style-border-color-pressed-o05uld": "#0073bb", + "--awsui-style-box-shadow-active-o05uld": "0 1px 2px rgba(0,0,0,0.2)", + "--awsui-style-box-shadow-default-o05uld": "none", + "--awsui-style-box-shadow-disabled-o05uld": "none", + "--awsui-style-box-shadow-hover-o05uld": "0 1px 2px rgba(0,0,0,0.1)", + "--awsui-style-box-shadow-pressed-o05uld": "0 0 0 2px #0073bb", + "--awsui-style-color-active-o05uld": "#000000", + "--awsui-style-color-default-o05uld": "#000000", + "--awsui-style-color-disabled-o05uld": "#999999", + "--awsui-style-color-hover-o05uld": "#000000", + "--awsui-style-color-pressed-o05uld": "#0073bb", + "--awsui-style-focus-ring-border-color-o05uld": "#0073bb", + "--awsui-style-focus-ring-border-radius-o05uld": "6px", + "--awsui-style-focus-ring-border-width-o05uld": "2px", + "borderRadius": "4px", + "borderWidth": "1px", + "paddingBlock": "8px", + "paddingInline": "12px", +} +`; diff --git a/src/toggle-button/__tests__/styles.test.tsx b/src/toggle-button/__tests__/styles.test.tsx index 07f08f0486..8ade2c29c9 100644 --- a/src/toggle-button/__tests__/styles.test.tsx +++ b/src/toggle-button/__tests__/styles.test.tsx @@ -95,35 +95,7 @@ describe('getToggleButtonStyles', () => { }, }; - expect(getToggleButtonStyles(allStyles)).toEqual({ - borderRadius: '4px', - borderWidth: '1px', - paddingBlock: '8px', - paddingInline: '12px', - [customCssProps.styleBackgroundDefault]: '#ffffff', - [customCssProps.styleBackgroundDisabled]: '#f0f0f0', - [customCssProps.styleBackgroundHover]: '#fafafa', - [customCssProps.styleBackgroundActive]: '#eeeeee', - [customCssProps.styleBackgroundPressed]: '#e0e0e0', - [customCssProps.styleBorderColorDefault]: '#cccccc', - [customCssProps.styleBorderColorDisabled]: '#e0e0e0', - [customCssProps.styleBorderColorHover]: '#999999', - [customCssProps.styleBorderColorActive]: '#666666', - [customCssProps.styleBorderColorPressed]: '#0073bb', - [customCssProps.styleBoxShadowDefault]: 'none', - [customCssProps.styleBoxShadowDisabled]: 'none', - [customCssProps.styleBoxShadowHover]: '0 1px 2px rgba(0,0,0,0.1)', - [customCssProps.styleBoxShadowActive]: '0 1px 2px rgba(0,0,0,0.2)', - [customCssProps.styleBoxShadowPressed]: '0 0 0 2px #0073bb', - [customCssProps.styleColorDefault]: '#000000', - [customCssProps.styleColorDisabled]: '#999999', - [customCssProps.styleColorHover]: '#000000', - [customCssProps.styleColorActive]: '#000000', - [customCssProps.styleColorPressed]: '#0073bb', - [customCssProps.styleFocusRingBorderColor]: '#0073bb', - [customCssProps.styleFocusRingBorderRadius]: '6px', - [customCssProps.styleFocusRingBorderWidth]: '2px', - }); + expect(getToggleButtonStyles(allStyles)).toMatchSnapshot(); }); test('returns empty object when SYSTEM is not core', async () => { From 129cf1dab2b8ac09edff41590da22d20633204a4 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Wed, 12 Nov 2025 16:05:42 +0100 Subject: [PATCH 09/13] retrigger checks From cdf73df70ad562f9c2c50e41b502f9d2797db813 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Wed, 12 Nov 2025 16:26:07 +0100 Subject: [PATCH 10/13] refactor: remove redundant cast --- src/toggle-button/internal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/toggle-button/internal.tsx b/src/toggle-button/internal.tsx index e7f3903c91..cf56e61319 100644 --- a/src/toggle-button/internal.tsx +++ b/src/toggle-button/internal.tsx @@ -66,7 +66,7 @@ export const InternalToggleButton = React.forwardRef( ref={ref} nativeButtonAttributes={{ ...nativeButtonAttributes, - style: { ...nativeButtonAttributes?.style, ...getToggleButtonStyles(style) } as React.CSSProperties, + style: { ...nativeButtonAttributes?.style, ...getToggleButtonStyles(style) }, }} analyticsAction={analyticsAction} /> From 60f2671146ee0cedf91804cadcdbae8121cb38f9 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Fri, 14 Nov 2025 10:54:24 +0100 Subject: [PATCH 11/13] refactor: pass styles to InternalButton --- src/toggle-button/__tests__/styles.test.tsx | 105 ++++++++++++++------ src/toggle-button/internal.tsx | 5 +- src/toggle-button/style.tsx | 33 ++---- 3 files changed, 86 insertions(+), 57 deletions(-) diff --git a/src/toggle-button/__tests__/styles.test.tsx b/src/toggle-button/__tests__/styles.test.tsx index 8ade2c29c9..be7f53282b 100644 --- a/src/toggle-button/__tests__/styles.test.tsx +++ b/src/toggle-button/__tests__/styles.test.tsx @@ -47,12 +47,12 @@ describe('getToggleButtonStyles', () => { jest.resetModules(); }); - test('returns empty object for undefined or empty style objects', () => { - expect(getToggleButtonStyles(undefined)).toEqual({}); - expect(getToggleButtonStyles({})).toEqual({}); + test('returns empty nativeButtonStyles for undefined or empty style objects', () => { + expect(getToggleButtonStyles(undefined)).toEqual({ nativeButtonStyles: {} }); + expect(getToggleButtonStyles({})).toEqual({ nativeButtonStyles: {} }); }); - test('handles all possible style configurations', () => { + test('extracts only pressed states to nativeButtonStyles', () => { const allStyles = { root: { borderRadius: '4px', @@ -95,10 +95,26 @@ describe('getToggleButtonStyles', () => { }, }; - expect(getToggleButtonStyles(allStyles)).toMatchSnapshot(); + const result = getToggleButtonStyles(allStyles); + + // Should only contain pressed states + expect(result).toEqual({ + nativeButtonStyles: { + [customCssProps.styleBackgroundPressed]: '#e0e0e0', + [customCssProps.styleBorderColorPressed]: '#0073bb', + [customCssProps.styleBoxShadowPressed]: '0 0 0 2px #0073bb', + [customCssProps.styleColorPressed]: '#0073bb', + }, + }); + + // Should NOT contain non-pressed states (these are handled by InternalButton) + expect(result.nativeButtonStyles).not.toHaveProperty('borderRadius'); + expect(result.nativeButtonStyles).not.toHaveProperty('borderWidth'); + expect(result.nativeButtonStyles).not.toHaveProperty(customCssProps.styleBackgroundDefault); + expect(result.nativeButtonStyles).not.toHaveProperty(customCssProps.styleBackgroundHover); }); - test('returns empty object when SYSTEM is not core', async () => { + test('returns empty nativeButtonStyles when SYSTEM is not core', async () => { jest.resetModules(); jest.doMock('../../internal/environment', () => ({ SYSTEM: 'visual-refresh', @@ -108,7 +124,7 @@ describe('getToggleButtonStyles', () => { const result = getToggleButtonStylesNonCore({ root: { borderRadius: '4px' } }); - expect(result).toEqual({}); + expect(result).toEqual({ nativeButtonStyles: {} }); }); describe('individual root properties', () => { @@ -120,11 +136,13 @@ describe('getToggleButtonStyles', () => { }; Object.entries(rootProperties).forEach(([property, value]) => { - test(`${property} only`, () => { + test(`${property} only - not extracted (handled by InternalButton)`, () => { const result = getToggleButtonStyles({ root: { [property]: value } }); + // These properties are passed to InternalButton via the style prop + // and are not extracted to nativeButtonStyles expect(result).toEqual({ - [property]: value, + nativeButtonStyles: {}, }); }); }); @@ -133,8 +151,26 @@ describe('getToggleButtonStyles', () => { describe('state properties', () => { STATE_PROPERTIES.forEach(property => { describe(`${property}`, () => { - STATES.forEach(state => { - test(`${state} only`, () => { + test('pressed state only', () => { + const testValue = `test-${property}-pressed`; + const style = { + root: { + [property]: { pressed: testValue }, + }, + }; + + const result = getToggleButtonStyles(style); + + // Pressed state should be extracted to nativeButtonStyles + expect(result).toEqual({ + nativeButtonStyles: { + [CSS_PROPERTY_MAP[property].pressed]: testValue, + }, + }); + }); + + ['default', 'disabled', 'hover', 'active'].forEach(state => { + test(`${state} only - not extracted (handled by InternalButton)`, () => { const testValue = `test-${property}-${state}`; const style = { root: { @@ -144,13 +180,14 @@ describe('getToggleButtonStyles', () => { const result = getToggleButtonStyles(style); + // Non-pressed states are not extracted expect(result).toEqual({ - [CSS_PROPERTY_MAP[property][state]]: testValue, + nativeButtonStyles: {}, }); }); }); - test(`all states together`, () => { + test('all states together - only pressed extracted', () => { const allStateValues = STATES.reduce( (acc, state) => ({ ...acc, @@ -162,8 +199,11 @@ describe('getToggleButtonStyles', () => { const style = { root: { [property]: allStateValues } }; const result = getToggleButtonStyles(style); - STATES.forEach(state => { - expect(result).toHaveProperty(CSS_PROPERTY_MAP[property][state], `test-${property}-${state}`); + // Only pressed state should be in nativeButtonStyles + expect(result).toEqual({ + nativeButtonStyles: { + [CSS_PROPERTY_MAP[property].pressed]: `test-${property}-pressed`, + }, }); }); }); @@ -177,19 +217,20 @@ describe('getToggleButtonStyles', () => { borderWidth: { prop: customCssProps.styleFocusRingBorderWidth, value: '3px' }, }; - Object.entries(focusRingProperties).forEach(([property, { prop, value }]) => { - test(`${property} only`, () => { + Object.entries(focusRingProperties).forEach(([property, { value }]) => { + test(`${property} only - not extracted (handled by InternalButton)`, () => { const result = getToggleButtonStyles({ root: { focusRing: { [property]: value } }, }); + // focusRing properties are passed to InternalButton via the style prop expect(result).toEqual({ - [prop]: value, + nativeButtonStyles: {}, }); }); }); - test('all focusRing properties together', () => { + test('all focusRing properties together - not extracted (handled by InternalButton)', () => { const result = getToggleButtonStyles({ root: { focusRing: { @@ -200,15 +241,14 @@ describe('getToggleButtonStyles', () => { }, }); + // focusRing properties are passed to InternalButton via the style prop expect(result).toEqual({ - [customCssProps.styleFocusRingBorderColor]: '#10b981', - [customCssProps.styleFocusRingBorderRadius]: '10px', - [customCssProps.styleFocusRingBorderWidth]: '3px', + nativeButtonStyles: {}, }); }); }); - test('handles mixed property types', () => { + test('handles mixed property types - only pressed states extracted', () => { const result = getToggleButtonStyles({ root: { borderRadius: '8px', @@ -219,14 +259,19 @@ describe('getToggleButtonStyles', () => { }, }); + // Only pressed states should be extracted expect(result).toEqual({ - borderRadius: '8px', - paddingBlock: '10px', - [customCssProps.styleBackgroundDefault]: '#3b82f6', - [customCssProps.styleBackgroundPressed]: '#1e40af', - [customCssProps.styleColorHover]: '#ffffff', - [customCssProps.styleColorPressed]: '#f0f0f0', - [customCssProps.styleFocusRingBorderColor]: '#3b82f6', + nativeButtonStyles: { + [customCssProps.styleBackgroundPressed]: '#1e40af', + [customCssProps.styleColorPressed]: '#f0f0f0', + }, }); + + // Other properties are passed to InternalButton via the style prop + expect(result.nativeButtonStyles).not.toHaveProperty('borderRadius'); + expect(result.nativeButtonStyles).not.toHaveProperty('paddingBlock'); + expect(result.nativeButtonStyles).not.toHaveProperty(customCssProps.styleBackgroundDefault); + expect(result.nativeButtonStyles).not.toHaveProperty(customCssProps.styleColorHover); + expect(result.nativeButtonStyles).not.toHaveProperty(customCssProps.styleFocusRingBorderColor); }); }); diff --git a/src/toggle-button/internal.tsx b/src/toggle-button/internal.tsx index cf56e61319..549e0a376d 100644 --- a/src/toggle-button/internal.tsx +++ b/src/toggle-button/internal.tsx @@ -48,6 +48,8 @@ export const InternalToggleButton = React.forwardRef( } } + const { nativeButtonStyles } = getToggleButtonStyles(style); + return ( diff --git a/src/toggle-button/style.tsx b/src/toggle-button/style.tsx index 5d4397fd2c..50b636b30f 100644 --- a/src/toggle-button/style.tsx +++ b/src/toggle-button/style.tsx @@ -6,38 +6,19 @@ import { ToggleButtonProps } from './interfaces'; export function getToggleButtonStyles(style: ToggleButtonProps['style']) { if (SYSTEM !== 'core') { - return {}; + return { nativeButtonStyles: {} }; } - const properties = { - borderRadius: style?.root?.borderRadius, - borderWidth: style?.root?.borderWidth, - paddingBlock: style?.root?.paddingBlock, - paddingInline: style?.root?.paddingInline, - [customCssProps.styleBackgroundActive]: style?.root?.background?.active, - [customCssProps.styleBackgroundDefault]: style?.root?.background?.default, - [customCssProps.styleBackgroundDisabled]: style?.root?.background?.disabled, - [customCssProps.styleBackgroundHover]: style?.root?.background?.hover, + // Styles NOT supported by InternalButton (pressed states only) + // InternalButton will handle all other styles when the original style object is passed to it + const nativeButtonStyles = { [customCssProps.styleBackgroundPressed]: style?.root?.background?.pressed, - [customCssProps.styleBorderColorActive]: style?.root?.borderColor?.active, - [customCssProps.styleBorderColorDefault]: style?.root?.borderColor?.default, - [customCssProps.styleBorderColorDisabled]: style?.root?.borderColor?.disabled, - [customCssProps.styleBorderColorHover]: style?.root?.borderColor?.hover, [customCssProps.styleBorderColorPressed]: style?.root?.borderColor?.pressed, - [customCssProps.styleBoxShadowActive]: style?.root?.boxShadow?.active, - [customCssProps.styleBoxShadowDefault]: style?.root?.boxShadow?.default, - [customCssProps.styleBoxShadowDisabled]: style?.root?.boxShadow?.disabled, - [customCssProps.styleBoxShadowHover]: style?.root?.boxShadow?.hover, [customCssProps.styleBoxShadowPressed]: style?.root?.boxShadow?.pressed, - [customCssProps.styleColorActive]: style?.root?.color?.active, - [customCssProps.styleColorDefault]: style?.root?.color?.default, - [customCssProps.styleColorDisabled]: style?.root?.color?.disabled, - [customCssProps.styleColorHover]: style?.root?.color?.hover, [customCssProps.styleColorPressed]: style?.root?.color?.pressed, - [customCssProps.styleFocusRingBorderColor]: style?.root?.focusRing?.borderColor, - [customCssProps.styleFocusRingBorderRadius]: style?.root?.focusRing?.borderRadius, - [customCssProps.styleFocusRingBorderWidth]: style?.root?.focusRing?.borderWidth, }; - return Object.fromEntries(Object.entries(properties).filter(([, value]) => value !== undefined)); + return { + nativeButtonStyles, + }; } From f0f647ef1a48e52678a9ed654dc4a63f1db1be95 Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Fri, 14 Nov 2025 11:12:34 +0100 Subject: [PATCH 12/13] chore: remove obsolete snapshot --- .../__snapshots__/styles.test.tsx.snap | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 src/toggle-button/__tests__/__snapshots__/styles.test.tsx.snap diff --git a/src/toggle-button/__tests__/__snapshots__/styles.test.tsx.snap b/src/toggle-button/__tests__/__snapshots__/styles.test.tsx.snap deleted file mode 100644 index 9472617e01..0000000000 --- a/src/toggle-button/__tests__/__snapshots__/styles.test.tsx.snap +++ /dev/null @@ -1,33 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`getToggleButtonStyles handles all possible style configurations 1`] = ` -{ - "--awsui-style-background-active-o05uld": "#eeeeee", - "--awsui-style-background-default-o05uld": "#ffffff", - "--awsui-style-background-disabled-o05uld": "#f0f0f0", - "--awsui-style-background-hover-o05uld": "#fafafa", - "--awsui-style-background-pressed-o05uld": "#e0e0e0", - "--awsui-style-border-color-active-o05uld": "#666666", - "--awsui-style-border-color-default-o05uld": "#cccccc", - "--awsui-style-border-color-disabled-o05uld": "#e0e0e0", - "--awsui-style-border-color-hover-o05uld": "#999999", - "--awsui-style-border-color-pressed-o05uld": "#0073bb", - "--awsui-style-box-shadow-active-o05uld": "0 1px 2px rgba(0,0,0,0.2)", - "--awsui-style-box-shadow-default-o05uld": "none", - "--awsui-style-box-shadow-disabled-o05uld": "none", - "--awsui-style-box-shadow-hover-o05uld": "0 1px 2px rgba(0,0,0,0.1)", - "--awsui-style-box-shadow-pressed-o05uld": "0 0 0 2px #0073bb", - "--awsui-style-color-active-o05uld": "#000000", - "--awsui-style-color-default-o05uld": "#000000", - "--awsui-style-color-disabled-o05uld": "#999999", - "--awsui-style-color-hover-o05uld": "#000000", - "--awsui-style-color-pressed-o05uld": "#0073bb", - "--awsui-style-focus-ring-border-color-o05uld": "#0073bb", - "--awsui-style-focus-ring-border-radius-o05uld": "6px", - "--awsui-style-focus-ring-border-width-o05uld": "2px", - "borderRadius": "4px", - "borderWidth": "1px", - "paddingBlock": "8px", - "paddingInline": "12px", -} -`; From 31fa1d4ad8a345aaaaed94c93dfec3a21cfb7bdf Mon Sep 17 00:00:00 2001 From: Maximilian Schoell Date: Fri, 14 Nov 2025 11:33:37 +0100 Subject: [PATCH 13/13] retrigger checks