From fb90bcfca65a1cd5448a5c4061413aeece482ec4 Mon Sep 17 00:00:00 2001 From: Simon Brebeck Date: Sun, 29 Mar 2026 16:47:36 +0200 Subject: [PATCH 1/4] feat: Action card --- build-tools/utils/custom-css-properties.js | 24 + build-tools/utils/pluralize.js | 1 + pages/action-card/customization.page.tsx | 805 ++++++++++++++++++ pages/action-card/permutations.page.tsx | 131 +++ pages/action-card/simple.page.tsx | 61 ++ .../__tests__/action-card.test.tsx | 341 ++++++++ src/action-card/index.tsx | 54 ++ src/action-card/interfaces.ts | 160 ++++ src/action-card/internal.tsx | 149 ++++ src/action-card/motion.scss | 31 + src/action-card/style.tsx | 71 ++ src/action-card/styles.scss | 379 +++++++++ .../components/structured-item/styles.scss | 1 + src/test-utils/dom/action-card/index.ts | 45 + style-dictionary/classic/borders.ts | 5 + style-dictionary/classic/colors.ts | 13 + style-dictionary/classic/spacing.ts | 6 +- style-dictionary/utils/token-names.ts | 28 +- style-dictionary/visual-refresh/borders.ts | 5 + style-dictionary/visual-refresh/colors.ts | 13 + style-dictionary/visual-refresh/spacing.ts | 4 + 21 files changed, 2323 insertions(+), 4 deletions(-) create mode 100644 pages/action-card/customization.page.tsx create mode 100644 pages/action-card/permutations.page.tsx create mode 100644 pages/action-card/simple.page.tsx create mode 100644 src/action-card/__tests__/action-card.test.tsx create mode 100644 src/action-card/index.tsx create mode 100644 src/action-card/interfaces.ts create mode 100644 src/action-card/internal.tsx create mode 100644 src/action-card/motion.scss create mode 100644 src/action-card/style.tsx create mode 100644 src/action-card/styles.scss create mode 100644 src/test-utils/dom/action-card/index.ts diff --git a/build-tools/utils/custom-css-properties.js b/build-tools/utils/custom-css-properties.js index 73511df043..d1e22af3f4 100644 --- a/build-tools/utils/custom-css-properties.js +++ b/build-tools/utils/custom-css-properties.js @@ -184,5 +184,29 @@ const customCssPropertiesList = [ 'styleItemCardBorderRadius', 'styleItemCardBorderWidthDefault', 'styleItemCardBoxShadowDefault', + // Action card specific style properties + 'styleActionCardBackgroundDefault', + 'styleActionCardBackgroundHover', + 'styleActionCardBackgroundActive', + 'styleActionCardBackgroundDisabled', + 'styleActionCardBorderColorDefault', + 'styleActionCardBorderColorHover', + 'styleActionCardBorderColorActive', + 'styleActionCardBorderColorDisabled', + 'styleActionCardBorderRadiusDefault', + 'styleActionCardBorderRadiusHover', + 'styleActionCardBorderRadiusActive', + 'styleActionCardBorderRadiusDisabled', + 'styleActionCardBorderWidthDefault', + 'styleActionCardBorderWidthHover', + 'styleActionCardBorderWidthActive', + 'styleActionCardBorderWidthDisabled', + 'styleActionCardBoxShadowDefault', + 'styleActionCardBoxShadowHover', + 'styleActionCardBoxShadowActive', + 'styleActionCardBoxShadowDisabled', + 'styleActionCardFocusRingBorderColor', + 'styleActionCardFocusRingBorderRadius', + 'styleActionCardFocusRingBorderWidth', ]; module.exports = customCssPropertiesList; diff --git a/build-tools/utils/pluralize.js b/build-tools/utils/pluralize.js index 2359e8b107..43d4a24493 100644 --- a/build-tools/utils/pluralize.js +++ b/build-tools/utils/pluralize.js @@ -1,6 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 const pluralizationMap = { + ActionCard: 'ActionCards', Alert: 'Alerts', AnchorNavigation: 'AnchorNavigations', Annotation: 'Annotations', diff --git a/pages/action-card/customization.page.tsx b/pages/action-card/customization.page.tsx new file mode 100644 index 0000000000..469b86ae6d --- /dev/null +++ b/pages/action-card/customization.page.tsx @@ -0,0 +1,805 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React, { useContext } from 'react'; + +import { + Box, + Checkbox, + ColumnLayout, + Container, + ExpandableSection, + FormField, + Grid, + Header, + Input, + Select, + SelectProps, + SpaceBetween, + Toggle, +} from '~components'; +import ActionCard, { ActionCardProps } from '~components/action-card'; +import Icon from '~components/icon'; +import { IconProps } from '~components/icon/interfaces'; +import PanelLayout from '~components/panel-layout'; + +import AppContext, { AppContextType } from '../app/app-context'; +import { SimplePage } from '../app/templates'; + +type DevPageContext = React.Context< + AppContextType<{ + containerWidth: string; + headerText: string; + descriptionText: string; + contentText: string; + showHeader: boolean; + showDescription: boolean; + showContent: boolean; + showIcon: boolean; + iconName: string; + iconPosition: string; + iconVerticalAlignment: string; + disabled: boolean; + disableHeaderPaddings: boolean; + disableContentPaddings: boolean; + variant: 'embedded' | 'default'; + useCustomStyles: boolean; + rootBackgroundDefault: string; + rootBackgroundHover: string; + rootBackgroundActive: string; + rootBackgroundDisabled: string; + rootBorderColorDefault: string; + rootBorderColorHover: string; + rootBorderColorActive: string; + rootBorderColorDisabled: string; + rootBorderRadiusDefault: string; + rootBorderRadiusHover: string; + rootBorderRadiusActive: string; + rootBorderRadiusDisabled: string; + rootBorderWidthDefault: string; + rootBorderWidthHover: string; + rootBorderWidthActive: string; + rootBorderWidthDisabled: string; + rootBoxShadowDefault: string; + rootBoxShadowHover: string; + rootBoxShadowActive: string; + rootBoxShadowDisabled: string; + rootFocusRingBorderColor: string; + rootFocusRingBorderRadius: string; + rootFocusRingBorderWidth: string; + contentPaddingBlock: string; + contentPaddingInline: string; + headerPaddingBlock: string; + headerPaddingInline: string; + containerType: string; + cardCount: string; + containerHeight: string; + }> +>; + +const iconOptions: Array = [ + { value: 'angle-right', label: 'angle-right' }, + { value: 'arrow-right', label: 'arrow-right' }, + { value: 'settings', label: 'settings' }, + { value: 'edit', label: 'edit' }, + { value: 'remove', label: 'remove' }, + { value: 'add-plus', label: 'add-plus' }, + { value: 'calendar', label: 'calendar' }, + { value: 'call', label: 'call' }, + { value: 'copy', label: 'copy' }, + { value: 'delete-marker', label: 'delete-marker' }, + { value: 'download', label: 'download' }, + { value: 'envelope', label: 'envelope' }, + { value: 'external', label: 'external' }, + { value: 'file', label: 'file' }, + { value: 'folder', label: 'folder' }, + { value: 'heart', label: 'heart' }, + { value: 'key', label: 'key' }, + { value: 'lock-private', label: 'lock-private' }, + { value: 'microphone', label: 'microphone' }, + { value: 'notification', label: 'notification' }, + { value: 'search', label: 'search' }, + { value: 'share', label: 'share' }, + { value: 'star-filled', label: 'star-filled' }, + { value: 'status-positive', label: 'status-positive' }, + { value: 'status-warning', label: 'status-warning' }, + { value: 'status-negative', label: 'status-negative' }, + { value: 'status-info', label: 'status-info' }, + { value: 'upload', label: 'upload' }, + { value: 'user-profile', label: 'user-profile' }, +]; + +const variantOptions: Array = [ + { value: 'default', label: 'Default' }, + { value: 'embedded', label: 'Embedded' }, +]; + +const iconVerticalAlignmentOptions: Array = [ + { value: 'top', label: 'Top' }, + { value: 'center', label: 'Center' }, +]; + +const containerTypeOptions: Array = [ + { value: 'div', label: 'div (block)', description: 'Plain
wrapper' }, + { value: 'flexbox', label: 'Flexbox', description: 'display: flex' }, + { value: 'css-grid', label: 'CSS Grid', description: 'display: grid' }, + { value: 'grid-component', label: 'Grid component', description: 'Cloudscape Grid (2 columns)' }, + { value: 'container-component', label: 'Container component', description: 'Cloudscape Container' }, + { value: 'none', label: 'None', description: 'No wrapper' }, +]; + +export default function ActionCardCustomizationPage() { + const { urlParams, setUrlParams } = useContext(AppContext as DevPageContext); + + const containerWidth = urlParams.containerWidth || '400'; + const headerText = urlParams.headerText || 'Action Card Header'; + const descriptionText = urlParams.descriptionText || 'A short description of the action card'; + const contentText = + urlParams.contentText || + 'This is the main content area of the action card. It can contain any text or information.'; + const showHeader = urlParams.showHeader !== false; + const showDescription = urlParams.showDescription !== false; + const showContent = urlParams.showContent !== false; + const showIcon = urlParams.showIcon !== false; + const iconName = (urlParams.iconName || 'angle-right') as IconProps.Name; + const iconVerticalAlignment = (urlParams.iconVerticalAlignment || 'top') as ActionCardProps.IconVerticalAlignment; + const disabled = urlParams.disabled === true; + const disableHeaderPaddings = urlParams.disableHeaderPaddings === true; + const disableContentPaddings = urlParams.disableContentPaddings === true; + const useCustomStyles = urlParams.useCustomStyles === true; + const variant = urlParams.variant || undefined; + + // Root styles - background (state-based) + const rootBackgroundDefault = urlParams.rootBackgroundDefault || ''; + const rootBackgroundHover = urlParams.rootBackgroundHover || ''; + const rootBackgroundActive = urlParams.rootBackgroundActive || ''; + const rootBackgroundDisabled = urlParams.rootBackgroundDisabled || ''; + + // Root styles - borderColor (state-based) + const rootBorderColorDefault = urlParams.rootBorderColorDefault || ''; + const rootBorderColorHover = urlParams.rootBorderColorHover || ''; + const rootBorderColorActive = urlParams.rootBorderColorActive || ''; + const rootBorderColorDisabled = urlParams.rootBorderColorDisabled || ''; + + // Root styles - borderRadius (state-based) + const rootBorderRadiusDefault = urlParams.rootBorderRadiusDefault || ''; + const rootBorderRadiusHover = urlParams.rootBorderRadiusHover || ''; + const rootBorderRadiusActive = urlParams.rootBorderRadiusActive || ''; + const rootBorderRadiusDisabled = urlParams.rootBorderRadiusDisabled || ''; + + // Root styles - borderWidth (state-based) + const rootBorderWidthDefault = urlParams.rootBorderWidthDefault || ''; + const rootBorderWidthHover = urlParams.rootBorderWidthHover || ''; + const rootBorderWidthActive = urlParams.rootBorderWidthActive || ''; + const rootBorderWidthDisabled = urlParams.rootBorderWidthDisabled || ''; + + // Root styles - boxShadow (state-based) + const rootBoxShadowDefault = urlParams.rootBoxShadowDefault || ''; + const rootBoxShadowHover = urlParams.rootBoxShadowHover || ''; + const rootBoxShadowActive = urlParams.rootBoxShadowActive || ''; + const rootBoxShadowDisabled = urlParams.rootBoxShadowDisabled || ''; + + // Root styles - focusRing + const rootFocusRingBorderColor = urlParams.rootFocusRingBorderColor || ''; + const rootFocusRingBorderRadius = urlParams.rootFocusRingBorderRadius || ''; + const rootFocusRingBorderWidth = urlParams.rootFocusRingBorderWidth || ''; + + // Content styles + const contentPaddingBlock = urlParams.contentPaddingBlock || ''; + const contentPaddingInline = urlParams.contentPaddingInline || ''; + + // Header styles + const headerPaddingBlock = urlParams.headerPaddingBlock || ''; + const headerPaddingInline = urlParams.headerPaddingInline || ''; + + const containerType = urlParams.containerType || 'div'; + const cardCount = Math.max(1, parseInt(urlParams.cardCount || '1', 10) || 1); + const containerHeight = urlParams.containerHeight || ''; + + const selectedIconOption = iconOptions.find(opt => opt.value === iconName) ?? iconOptions[0]; + const selectedVariantOption = variantOptions.find(opt => opt.value === variant) ?? variantOptions[0]; + const selectedIconVerticalAlignmentOption = + iconVerticalAlignmentOptions.find(opt => opt.value === iconVerticalAlignment) ?? iconVerticalAlignmentOptions[0]; + const selectedContainerTypeOption = + containerTypeOptions.find(opt => opt.value === containerType) ?? containerTypeOptions[0]; + + const hasRootBackground = + rootBackgroundDefault || rootBackgroundHover || rootBackgroundActive || rootBackgroundDisabled; + const hasRootBorderColor = + rootBorderColorDefault || rootBorderColorHover || rootBorderColorActive || rootBorderColorDisabled; + const hasRootBorderRadius = + rootBorderRadiusDefault || rootBorderRadiusHover || rootBorderRadiusActive || rootBorderRadiusDisabled; + const hasRootBorderWidth = + rootBorderWidthDefault || rootBorderWidthHover || rootBorderWidthActive || rootBorderWidthDisabled; + const hasRootBoxShadow = rootBoxShadowDefault || rootBoxShadowHover || rootBoxShadowActive || rootBoxShadowDisabled; + const hasRootFocusRing = rootFocusRingBorderColor || rootFocusRingBorderRadius || rootFocusRingBorderWidth; + + const customStyle: ActionCardProps.Style | undefined = useCustomStyles + ? { + root: { + ...(hasRootBackground + ? { + background: { + ...(rootBackgroundDefault ? { default: rootBackgroundDefault } : {}), + ...(rootBackgroundHover ? { hover: rootBackgroundHover } : {}), + ...(rootBackgroundActive ? { active: rootBackgroundActive } : {}), + ...(rootBackgroundDisabled ? { disabled: rootBackgroundDisabled } : {}), + }, + } + : {}), + ...(hasRootBorderColor + ? { + borderColor: { + ...(rootBorderColorDefault ? { default: rootBorderColorDefault } : {}), + ...(rootBorderColorHover ? { hover: rootBorderColorHover } : {}), + ...(rootBorderColorActive ? { active: rootBorderColorActive } : {}), + ...(rootBorderColorDisabled ? { disabled: rootBorderColorDisabled } : {}), + }, + } + : {}), + ...(hasRootBorderRadius + ? { + borderRadius: { + ...(rootBorderRadiusDefault ? { default: rootBorderRadiusDefault } : {}), + ...(rootBorderRadiusHover ? { hover: rootBorderRadiusHover } : {}), + ...(rootBorderRadiusActive ? { active: rootBorderRadiusActive } : {}), + ...(rootBorderRadiusDisabled ? { disabled: rootBorderRadiusDisabled } : {}), + }, + } + : {}), + ...(hasRootBorderWidth + ? { + borderWidth: { + ...(rootBorderWidthDefault ? { default: rootBorderWidthDefault } : {}), + ...(rootBorderWidthHover ? { hover: rootBorderWidthHover } : {}), + ...(rootBorderWidthActive ? { active: rootBorderWidthActive } : {}), + ...(rootBorderWidthDisabled ? { disabled: rootBorderWidthDisabled } : {}), + }, + } + : {}), + ...(hasRootBoxShadow + ? { + boxShadow: { + ...(rootBoxShadowDefault ? { default: rootBoxShadowDefault } : {}), + ...(rootBoxShadowHover ? { hover: rootBoxShadowHover } : {}), + ...(rootBoxShadowActive ? { active: rootBoxShadowActive } : {}), + ...(rootBoxShadowDisabled ? { disabled: rootBoxShadowDisabled } : {}), + }, + } + : {}), + ...(hasRootFocusRing + ? { + focusRing: { + ...(rootFocusRingBorderColor ? { borderColor: rootFocusRingBorderColor } : {}), + ...(rootFocusRingBorderRadius ? { borderRadius: rootFocusRingBorderRadius } : {}), + ...(rootFocusRingBorderWidth ? { borderWidth: rootFocusRingBorderWidth } : {}), + }, + } + : {}), + }, + content: { + ...(contentPaddingBlock ? { paddingBlock: contentPaddingBlock } : {}), + ...(contentPaddingInline ? { paddingInline: contentPaddingInline } : {}), + }, + header: { + ...(headerPaddingBlock ? { paddingBlock: headerPaddingBlock } : {}), + ...(headerPaddingInline ? { paddingInline: headerPaddingInline } : {}), + }, + } + : undefined; + + const [lastClicked, setLastClicked] = React.useState(null); + + const settingsPanel = ( + Settings}> + + + setUrlParams({ containerWidth: detail.value })} + type="number" + inputMode="numeric" + /> + + + + setUrlParams({ containerHeight: detail.value })} + type="number" + inputMode="numeric" + placeholder="auto" + /> + + + + setUrlParams({ cardCount: detail.value })} + type="number" + inputMode="numeric" + /> + + + + + + setUrlParams({ showHeader: detail.checked })}> + Show header + + setUrlParams({ showDescription: detail.checked })} + > + Show description + + setUrlParams({ showContent: detail.checked })}> + Show content + + setUrlParams({ showIcon: detail.checked })}> + Show icon + + setUrlParams({ disabled: detail.checked })}> + Disabled + + + + + + + + + setUrlParams({ headerText: detail.value })} + disabled={!showHeader} + /> + + + setUrlParams({ descriptionText: detail.value })} + disabled={!showDescription} + /> + + + setUrlParams({ contentText: detail.value })} + disabled={!showContent} + /> + + + setUrlParams({ containerType: detail.selectedOption.value ?? 'div' })} + /> + + + + + + + + setUrlParams({ iconVerticalAlignment: detail.selectedOption.value ?? 'top' })} + disabled={!showIcon} + /> + + + + + + + setUrlParams({ disableHeaderPaddings: detail.checked })} + > + Disable header paddings + + setUrlParams({ disableContentPaddings: detail.checked })} + > + Disable content paddings + + + + + + + setUrlParams({ useCustomStyles: detail.checked })} + > + Enable custom styles + + + + + + setUrlParams({ rootBackgroundDefault: detail.value })} + disabled={!useCustomStyles} + placeholder="light-dark(rgb(204, 225, 249), rgb(1, 20, 25))" + /> + + + setUrlParams({ rootBackgroundHover: detail.value })} + disabled={!useCustomStyles} + placeholder="light-dark(rgb(180, 210, 245), rgb(10, 35, 45))" + /> + + + setUrlParams({ rootBackgroundActive: detail.value })} + disabled={!useCustomStyles} + placeholder="light-dark(rgb(160, 195, 240), rgb(15, 45, 55))" + /> + + + setUrlParams({ rootBackgroundDisabled: detail.value })} + disabled={!useCustomStyles} + placeholder="light-dark(rgb(235, 235, 235), rgb(30, 30, 30))" + /> + + + + + + + + setUrlParams({ rootBorderColorDefault: detail.value })} + disabled={!useCustomStyles} + placeholder="light-dark(rgb(1, 20, 25), rgb(204, 225, 249))" + /> + + + setUrlParams({ rootBorderColorHover: detail.value })} + disabled={!useCustomStyles} + placeholder="light-dark(rgb(0, 100, 200), rgb(100, 180, 255))" + /> + + + setUrlParams({ rootBorderColorActive: detail.value })} + disabled={!useCustomStyles} + placeholder="light-dark(rgb(0, 80, 160), rgb(80, 160, 240))" + /> + + + setUrlParams({ rootBorderColorDisabled: detail.value })} + disabled={!useCustomStyles} + placeholder="light-dark(rgb(200, 200, 200), rgb(60, 60, 60))" + /> + + + + + + + + setUrlParams({ rootBorderRadiusDefault: detail.value })} + disabled={!useCustomStyles} + placeholder="10px" + /> + + + setUrlParams({ rootBorderRadiusHover: detail.value })} + disabled={!useCustomStyles} + placeholder="12px" + /> + + + setUrlParams({ rootBorderRadiusActive: detail.value })} + disabled={!useCustomStyles} + placeholder="6px" + /> + + + setUrlParams({ rootBorderRadiusDisabled: detail.value })} + disabled={!useCustomStyles} + placeholder="10px" + /> + + + + + + + + setUrlParams({ rootBorderWidthDefault: detail.value })} + disabled={!useCustomStyles} + placeholder="4px" + /> + + + setUrlParams({ rootBorderWidthHover: detail.value })} + disabled={!useCustomStyles} + placeholder="2px" + /> + + + setUrlParams({ rootBorderWidthActive: detail.value })} + disabled={!useCustomStyles} + placeholder="1px" + /> + + + setUrlParams({ rootBorderWidthDisabled: detail.value })} + disabled={!useCustomStyles} + placeholder="1px" + /> + + + + + + + + setUrlParams({ rootBoxShadowDefault: detail.value })} + disabled={!useCustomStyles} + placeholder="2px 2px 6px 4px rgba(0,0,0,0.2)" + /> + + + setUrlParams({ rootBoxShadowHover: detail.value })} + disabled={!useCustomStyles} + placeholder="2px 2px 8px 6px rgba(0,0,0,0.3)" + /> + + + setUrlParams({ rootBoxShadowActive: detail.value })} + disabled={!useCustomStyles} + placeholder="1px 1px 4px 2px rgba(0,0,0,0.15)" + /> + + + setUrlParams({ rootBoxShadowDisabled: detail.value })} + disabled={!useCustomStyles} + placeholder="none" + /> + + + + + + + + setUrlParams({ rootFocusRingBorderColor: detail.value })} + disabled={!useCustomStyles} + placeholder="blue" + /> + + + setUrlParams({ rootFocusRingBorderRadius: detail.value })} + disabled={!useCustomStyles} + placeholder="10px" + /> + + + setUrlParams({ rootFocusRingBorderWidth: detail.value })} + disabled={!useCustomStyles} + placeholder="2px" + /> + + + + + + + + setUrlParams({ headerPaddingBlock: detail.value })} + disabled={!useCustomStyles} + placeholder="14px" + /> + + + setUrlParams({ headerPaddingInline: detail.value })} + disabled={!useCustomStyles} + placeholder="16px" + /> + + + + + + + + setUrlParams({ contentPaddingBlock: detail.value })} + disabled={!useCustomStyles} + placeholder="0px 16px" + /> + + + setUrlParams({ contentPaddingInline: detail.value })} + disabled={!useCustomStyles} + placeholder="16px" + /> + + + + + + + + ); + + const renderActionCard = (index: number) => ( + {`${headerText}${cardCount > 1 ? ` #${index + 1}` : ''}`} : undefined} + description={showDescription ? descriptionText : undefined} + icon={showIcon && iconName && } + iconVerticalAlignment={showIcon ? iconVerticalAlignment : undefined} + disabled={disabled} + disableHeaderPaddings={disableHeaderPaddings} + disableContentPaddings={disableContentPaddings} + onClick={() => setLastClicked(`Card #${index + 1}`)} + style={customStyle} + > + {showContent ? contentText : undefined} + + ); + + const cards = Array.from({ length: cardCount }, (_, i) => renderActionCard(i)); + + const heightStyle = containerHeight + ? { blockSize: `${parseInt(containerHeight)}px`, overflow: 'auto', padding: '16px' } + : {}; + + const wrapInContainer = (cardElements: React.ReactNode[]) => { + switch (containerType) { + case 'flexbox': + return
{cardElements}
; + case 'css-grid': + return ( +
+ {cardElements} +
+ ); + case 'grid-component': + return ( +
+ ({ colspan: 6 }))}>{cardElements} +
+ ); + case 'container-component': + return ( + Inside Container component}> +
+ {cardElements} +
+
+ ); + case 'none': + return
{cardElements}
; + case 'div': + default: + return ( +
+ {cardElements} +
+ ); + } + }; + + const mainContent = ( + + +
+ +
+ Action Card Preview +
+
+ {wrapInContainer(cards)} +
+
+
+ ); + + return ( + +
+ +
+
+ ); +} diff --git a/pages/action-card/permutations.page.tsx b/pages/action-card/permutations.page.tsx new file mode 100644 index 0000000000..991cb3e917 --- /dev/null +++ b/pages/action-card/permutations.page.tsx @@ -0,0 +1,131 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React from 'react'; + +import { Icon } from '~components'; +import ActionCard, { ActionCardProps } from '~components/action-card'; + +import createPermutations from '../utils/permutations'; +import PermutationsView from '../utils/permutations-view'; +import ScreenshotArea from '../utils/screenshot-area'; + +const onClick = () => {}; + +const icon = ; + +const permutations = createPermutations([ + // Header + description + children combinations + { + header: [Card header, undefined], + description: ['A description of the action card', undefined], + children: ['Card content', undefined], + onClick: [onClick], + }, + // Disabled state + { + header: [Card header], + description: ['A description of the action card'], + children: ['Card content'], + disabled: [false, true], + onClick: [onClick], + }, + // Icon with position and alignment variations + { + header: [Card header], + description: ['A description of the action card'], + icon: [icon], + iconVerticalAlignment: ['top', 'center'], + onClick: [onClick], + }, + // Icon with children content + { + header: [Card header], + children: ['Card content'], + icon: [icon], + iconVerticalAlignment: ['top', 'center'], + disabled: [false, true], + onClick: [onClick], + }, + // Padding variations + { + header: [Card header], + description: ['A description'], + children: ['Card content'], + disableHeaderPaddings: [false, true], + disableContentPaddings: [false, true], + onClick: [onClick], + }, + // Long content wrapping + { + header: [ + + A very long header text that should wrap to multiple lines to test the layout behavior of the action card + component + , + ], + description: [ + 'A very long description text that should wrap to multiple lines to test the layout behavior of the action card component when content overflows', + ], + children: [ + 'Very long content that should wrap to multiple lines to test the layout behavior of the action card component when the content area has a lot of text', + ], + icon: [icon], + iconVerticalAlignment: ['top', 'center'], + onClick: [onClick], + }, + // Minimal: content only + { + children: ['Minimal card with content only'], + onClick: [onClick], + }, + // Header only + { + header: [Header only card], + onClick: [onClick], + }, + // All content slots with disabled padding and disabled state + { + header: [Full card], + description: ['Description text'], + children: ['Card content'], + icon: [icon], + disabled: [true], + disableHeaderPaddings: [true], + disableContentPaddings: [true], + onClick: [onClick], + }, + // Variant variations + { + header: [Card header], + description: ['A description of the action card'], + children: ['Card content'], + variant: ['default', 'embedded'], + onClick: [onClick], + }, + // Variant with icon + { + header: [Card header], + description: ['A description of the action card'], + icon: [icon], + variant: ['default', 'embedded'], + onClick: [onClick], + }, +]); + +export default function ActionCardPermutations() { + return ( + <> +

Action card permutations

+ + ( +
+ +
+ )} + /> +
+ + ); +} diff --git a/pages/action-card/simple.page.tsx b/pages/action-card/simple.page.tsx new file mode 100644 index 0000000000..4f7a8aecf7 --- /dev/null +++ b/pages/action-card/simple.page.tsx @@ -0,0 +1,61 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as React from 'react'; + +import { ActionCard, Badge, Box, Icon, SpaceBetween } from '~components'; + +import { SimplePage } from '../app/templates'; + +export default function ActionCardSimplePage() { + const [clickedCard, setClickedCard] = React.useState(null); + + return ( + + +
Last clicked: {clickedCard ?? 'None'}
+ +
+ + setClickedCard('Juice')} + > + +
+ +
Lambda Execution Role - Current IAM Policies
+ Table +
+
+
+ } + iconVerticalAlignment="center" + onClick={() => setClickedCard('Basic')} + > + Test + + EC2 access to S3} + description="A description of the template/icebreaker" + icon={} + iconVerticalAlignment="top" + onClick={() => setClickedCard('Basic')} + > + Account alias (111112222233333)} + description="Dev/john.doe@amazon.com" + icon={} + iconVerticalAlignment="top" + onClick={() => setClickedCard('Basic')} + > + Logged in 1 minute ago + +
+
+
+
+ ); +} diff --git a/src/action-card/__tests__/action-card.test.tsx b/src/action-card/__tests__/action-card.test.tsx new file mode 100644 index 0000000000..eee12b8317 --- /dev/null +++ b/src/action-card/__tests__/action-card.test.tsx @@ -0,0 +1,341 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React from 'react'; +import { render } from '@testing-library/react'; + +import ActionCard, { ActionCardProps } from '../../../lib/components/action-card'; +import createWrapper from '../../../lib/components/test-utils/dom'; + +import styles from '../../../lib/components/action-card/styles.css.js'; + +function renderActionCard(props: ActionCardProps = {}) { + const renderResult = render(); + return createWrapper(renderResult.container).findActionCard()!; +} + +describe('ActionCard Component', () => { + describe('header property', () => { + test('renders header when provided', () => { + const wrapper = renderActionCard({ header: 'Test Header' }); + expect(wrapper.findHeader()!.getElement()).toHaveTextContent('Test Header'); + }); + + test('does not render header element when not provided', () => { + const wrapper = renderActionCard(); + expect(wrapper.findHeader()).toBeNull(); + }); + }); + + describe('description property', () => { + test('renders description when provided', () => { + const wrapper = renderActionCard({ description: 'Test Description' }); + expect(wrapper.findDescription()!.getElement()).toHaveTextContent('Test Description'); + }); + + test('does not render description element when not provided', () => { + const wrapper = renderActionCard(); + expect(wrapper.findDescription()).toBeNull(); + }); + }); + + describe('children property', () => { + test('renders children content when provided', () => { + const wrapper = renderActionCard({ children: 'Test Content' }); + expect(wrapper.findContent()!.getElement()).toHaveTextContent('Test Content'); + }); + + test('does not render content element when children not provided', () => { + const wrapper = renderActionCard(); + expect(wrapper.findContent()).toBeNull(); + }); + }); + + describe('disabled property', () => { + test('renders action card with normal styling by default', () => { + const wrapper = renderActionCard(); + expect(wrapper.isDisabled()).toEqual(false); + expect(wrapper.getElement()).not.toHaveAttribute('disabled'); + expect(wrapper.getElement()).not.toHaveClass(styles.disabled); + }); + + test('renders action card with disabled styling when true', () => { + const wrapper = renderActionCard({ disabled: true }); + expect(wrapper.isDisabled()).toEqual(true); + expect(wrapper.getElement()).toHaveClass(styles.disabled); + expect(wrapper.getElement()).toHaveAttribute('disabled'); + expect(wrapper.getElement()).toHaveAttribute('aria-disabled', 'true'); + }); + + test('does not call onClick when disabled', () => { + const onClickSpy = jest.fn(); + const wrapper = renderActionCard({ onClick: onClickSpy, disabled: true }); + wrapper.click(); + expect(onClickSpy).not.toHaveBeenCalled(); + }); + }); + + describe('onClick property', () => { + test('calls onClick when the action card is clicked', () => { + const onClickSpy = jest.fn(); + const wrapper = renderActionCard({ onClick: onClickSpy }); + wrapper.click(); + expect(onClickSpy).toHaveBeenCalled(); + }); + + test('does not call onClick when disabled', () => { + const onClickSpy = jest.fn(); + const wrapper = renderActionCard({ onClick: onClickSpy, disabled: true }); + wrapper.click(); + expect(onClickSpy).not.toHaveBeenCalled(); + }); + }); + + describe('ariaLabel property', () => { + test('adds aria-label attribute when provided', () => { + const wrapper = renderActionCard({ ariaLabel: 'Test Aria Label' }); + expect(wrapper.getElement()).toHaveAttribute('aria-label', 'Test Aria Label'); + }); + + test('does not add aria-label when not provided', () => { + const wrapper = renderActionCard(); + expect(wrapper.getElement()).not.toHaveAttribute('aria-label'); + }); + }); + + describe('ariaDescribedby property', () => { + test('adds aria-describedby attribute when provided', () => { + const wrapper = renderActionCard({ ariaDescribedby: 'description-id' }); + expect(wrapper.getElement()).toHaveAttribute('aria-describedby', 'description-id'); + }); + + test('does not add aria-describedby when not provided', () => { + const wrapper = renderActionCard(); + expect(wrapper.getElement()).not.toHaveAttribute('aria-describedby'); + }); + }); + + describe('focus management', () => { + test('can be focused through the API', () => { + let actionCard: ActionCardProps.Ref | null = null; + const renderResult = render( (actionCard = el)} />); + const wrapper = createWrapper(renderResult.container); + actionCard!.focus(); + expect(document.activeElement).toBe(wrapper.findActionCard()!.getElement()); + }); + }); + + describe('button element', () => { + test('renders as a button element', () => { + const wrapper = renderActionCard(); + expect(wrapper.getElement().tagName).toBe('BUTTON'); + }); + + test('has type="button" attribute', () => { + const wrapper = renderActionCard(); + expect(wrapper.getElement()).toHaveAttribute('type', 'button'); + }); + }); + + /** + * Preservation Property Tests + * Validates: Requirements 3.1, 3.2, 3.3, 3.4, 3.5, 3.6 + * + * These tests capture the current (correct) behavior that must be preserved after the fix. + * They MUST PASS on the current unfixed code. + */ + describe('Preservation: center alignment and no-icon layout unchanged', () => { + test('center-aligned icon is a direct child of root button, not inside inner-card', () => { + /** + * Validates: Requirements 3.1 + */ + const wrapper = renderActionCard({ + icon: icon, + iconVerticalAlignment: 'center', + header: 'Header', + children: 'Body', + }); + + const rootEl = wrapper.getElement(); + const innerCard = rootEl.querySelector(`.${styles['inner-card']}`); + expect(innerCard).not.toBeNull(); + + // Icon should be a direct child of root button (sibling of inner-card) + const iconDirectChildOfRoot = Array.from(rootEl.children).find(child => child.classList.contains(styles.icon)); + expect(iconDirectChildOfRoot).toBeDefined(); + + // Icon should NOT be inside inner-card + const iconInsideInnerCard = innerCard!.querySelector(`.${styles.icon}`); + expect(iconInsideInnerCard).toBeNull(); + }); + + test('no-icon card has no icon wrapper element in the DOM', () => { + /** + * Validates: Requirements 3.2 + */ + const wrapper = renderActionCard({ + header: 'Header', + children: 'Body', + }); + + const rootEl = wrapper.getElement(); + const iconEl = rootEl.querySelector(`.${styles.icon}`); + expect(iconEl).toBeNull(); + }); + + test('disabled card with icon has disabled class, aria-disabled, and disabled attribute', () => { + /** + * Validates: Requirements 3.3 + */ + const wrapper = renderActionCard({ + icon: icon, + iconVerticalAlignment: 'center', + header: 'Header', + disabled: true, + }); + + const rootEl = wrapper.getElement(); + expect(rootEl).toHaveClass(styles.disabled); + expect(rootEl).toHaveAttribute('aria-disabled', 'true'); + expect(rootEl).toHaveAttribute('disabled'); + }); + + test('disableHeaderPaddings=true applies no-padding class on header', () => { + /** + * Validates: Requirements 3.4 + */ + const wrapper = renderActionCard({ + header: 'Header', + children: 'Body', + disableHeaderPaddings: true, + }); + + const rootEl = wrapper.getElement(); + const headerEl = rootEl.querySelector(`.${styles.header}`); + expect(headerEl).not.toBeNull(); + expect(headerEl!.classList.contains(styles['no-padding'])).toBe(true); + }); + + test('disableContentPaddings=true applies no-padding class on body', () => { + /** + * Validates: Requirements 3.4 + */ + const wrapper = renderActionCard({ + header: 'Header', + children: 'Body', + disableContentPaddings: true, + }); + + const rootEl = wrapper.getElement(); + const bodyEl = rootEl.querySelector(`.${styles.body}`); + expect(bodyEl).not.toBeNull(); + expect(bodyEl!.classList.contains(styles['no-padding'])).toBe(true); + }); + + test('variant="default" applies variant-default class on root', () => { + /** + * Validates: Requirements 3.5 + */ + const wrapper = renderActionCard({ + header: 'Header', + variant: 'default', + }); + + const rootEl = wrapper.getElement(); + expect(rootEl).toHaveClass(styles['variant-default']); + }); + + test('variant="embedded" applies variant-embedded class on root', () => { + /** + * Validates: Requirements 3.5 + */ + const wrapper = renderActionCard({ + header: 'Header', + variant: 'embedded', + }); + + const rootEl = wrapper.getElement(); + expect(rootEl).toHaveClass(styles['variant-embedded']); + }); + }); + + /** + * Bug Condition Exploration Tests + * Validates: Requirements 1.1, 1.2 + * + * These tests encode the EXPECTED (fixed) behavior for iconVerticalAlignment='top'. + * They are expected to FAIL on unfixed code, proving the bug exists. + * The bug: icon is rendered as a sibling of inner-card at the root level, + * instead of inside inner-card within a header-row wrapper. + */ + describe('Bug Condition: icon alignment when iconVerticalAlignment="top"', () => { + test('icon is rendered inside inner-card within a header-row wrapper, not as a direct child of root', () => { + const wrapper = renderActionCard({ + icon: icon, + iconVerticalAlignment: 'top', + header: 'Header', + children: 'Body', + }); + + const rootEl = wrapper.getElement(); + const innerCard = rootEl.querySelector(`.${styles['inner-card']}`); + expect(innerCard).not.toBeNull(); + + // The icon should be inside inner-card (within a header-row wrapper), NOT a direct child of root + const iconInsideInnerCard = innerCard!.querySelector(`.${styles.icon}`); + expect(iconInsideInnerCard).not.toBeNull(); + + // The icon should NOT be a direct child of the root button + const iconDirectChildOfRoot = Array.from(rootEl.children).find(child => child.classList.contains(styles.icon)); + expect(iconDirectChildOfRoot).toBeUndefined(); + }); + + test('a header-row wrapper exists inside inner-card containing both header and icon', () => { + const wrapper = renderActionCard({ + icon: icon, + iconVerticalAlignment: 'top', + header: 'Header', + description: 'Description', + children: 'Body', + }); + + const rootEl = wrapper.getElement(); + const innerCard = rootEl.querySelector(`.${styles['inner-card']}`); + expect(innerCard).not.toBeNull(); + + // A header-row wrapper should exist inside inner-card + const headerRow = innerCard!.querySelector(`.${styles['header-row']}`); + expect(headerRow).not.toBeNull(); + + // header-row should contain both the header section and the icon + const headerInRow = headerRow!.querySelector(`.${styles.header}`); + const iconInRow = headerRow!.querySelector(`.${styles.icon}`); + expect(headerInRow).not.toBeNull(); + expect(iconInRow).not.toBeNull(); + + // body should be a sibling of header-row inside inner-card, not inside header-row + const bodyEl = innerCard!.querySelector(`.${styles.body}`); + expect(bodyEl).not.toBeNull(); + expect(headerRow!.contains(bodyEl)).toBe(false); + }); + + test('edge case: iconVerticalAlignment="top" with no header/description but with children — icon falls back to root sibling', () => { + const wrapper = renderActionCard({ + icon: icon, + iconVerticalAlignment: 'top', + children: 'Body content', + }); + + const rootEl = wrapper.getElement(); + const innerCard = rootEl.querySelector(`.${styles['inner-card']}`); + expect(innerCard).not.toBeNull(); + + // Without a header, icon should fall back to root sibling (like center alignment) + const iconDirectChildOfRoot = Array.from(rootEl.children).find(child => child.classList.contains(styles.icon)); + expect(iconDirectChildOfRoot).toBeDefined(); + + // Icon should NOT be inside inner-card + const iconInsideInnerCard = innerCard!.querySelector(`.${styles.icon}`); + expect(iconInsideInnerCard).toBeNull(); + }); + }); +}); diff --git a/src/action-card/index.tsx b/src/action-card/index.tsx new file mode 100644 index 0000000000..775b44d609 --- /dev/null +++ b/src/action-card/index.tsx @@ -0,0 +1,54 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +'use client'; +import React from 'react'; + +import useBaseComponent from '../internal/hooks/use-base-component'; +import { applyDisplayName } from '../internal/utils/apply-display-name'; +import { getExternalProps } from '../internal/utils/external-props'; +import { type ActionCardProps } from './interfaces'; +import InternalActionCard from './internal'; + +export { ActionCardProps }; + +const ActionCard = React.forwardRef( + ( + { + disabled = false, + disableHeaderPaddings = false, + disableContentPaddings = false, + iconVerticalAlignment = 'top', + variant = 'default', + ...props + }: ActionCardProps, + ref: React.Ref + ) => { + const baseComponentProps = useBaseComponent('ActionCard', { + props: { + disabled, + disableHeaderPaddings, + disableContentPaddings, + iconVerticalAlignment, + variant, + }, + }); + + const externalProps = getExternalProps(props); + + return ( + + ); + } +); + +applyDisplayName(ActionCard, 'ActionCard'); +export default ActionCard; diff --git a/src/action-card/interfaces.ts b/src/action-card/interfaces.ts new file mode 100644 index 0000000000..8d6c4e50d2 --- /dev/null +++ b/src/action-card/interfaces.ts @@ -0,0 +1,160 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React, { ReactNode } from 'react'; + +import { BaseComponentProps } from '../internal/base-component'; +import { CancelableEventHandler } from '../internal/events'; +import { NativeAttributes } from '../internal/utils/with-native-attributes'; + +export interface ActionCardProps extends BaseComponentProps { + /** + * The header content displayed at the top of the action card. + */ + header?: ReactNode; + + /** + * The description content displayed below the header. + */ + description?: ReactNode; + + /** + * The main content of the action card. + */ + children?: ReactNode; + + /** + * Called when the user clicks on the action card. + */ + onClick?: CancelableEventHandler; + + /** + * Adds an aria-label to the action card. + */ + ariaLabel?: string; + + /** + * Adds an aria-describedby reference for the action card. + */ + ariaDescribedby?: string; + + /** + * Determines whether the action card is disabled. + * @default false + */ + disabled?: boolean; + + /** + * Removes the default padding from the header area. + * @default false + */ + disableHeaderPaddings?: boolean; + + /** + * Removes the default padding from the content area. + * @default false + */ + disableContentPaddings?: boolean; + + /** + * Displays an icon next to the content. You can use the `iconPosition` and `iconVerticalAlignment` properties to position the icon. + */ + icon?: React.ReactNode; + + /** + * Specifies the vertical alignment of the icon. + * @default 'top' + */ + iconVerticalAlignment?: ActionCardProps.IconVerticalAlignment; + + /** + * Specifies the visual variant of the card, which controls the border radius and padding. + * + * - `default` - Uses container-level border radius and padding (larger). + * - `embedded` - Uses compact border radius and padding (smaller). + * + * @default 'default' + */ + variant?: ActionCardProps.Variant; + + /** + * An object containing CSS properties to customize the action card's visual appearance. + * Refer to the [style](/components/action-card/?tabId=style) tab for more details. + * @awsuiSystem core + */ + style?: ActionCardProps.Style; + + /** + * Attributes to add to the native button element. + * Some attributes will be automatically combined with internal attribute values: + * - `className` will be appended. + * - Event handlers will be chained, unless the default is prevented. + * + * We do not support using this attribute to apply custom styling. + * + * @awsuiSystem core + */ + nativeButtonAttributes?: NativeAttributes>; +} + +export namespace ActionCardProps { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type + export interface ClickDetail {} + + export type IconVerticalAlignment = 'top' | 'center'; + export type Variant = 'default' | 'embedded'; + + export interface Ref { + /** + * Sets focus on the action card. + */ + focus(): void; + } + + export interface Style { + root?: { + background?: { + default?: string; + hover?: string; + active?: string; + disabled?: string; + }; + borderColor?: { + default?: string; + hover?: string; + active?: string; + disabled?: string; + }; + borderRadius?: { + default?: string; + hover?: string; + active?: string; + disabled?: string; + }; + borderWidth?: { + default?: string; + hover?: string; + active?: string; + disabled?: string; + }; + boxShadow?: { + default?: string; + hover?: string; + active?: string; + disabled?: string; + }; + focusRing?: { + borderColor?: string; + borderRadius?: string; + borderWidth?: string; + }; + }; + content?: { + paddingBlock?: string; + paddingInline?: string; + }; + header?: { + paddingBlock?: string; + paddingInline?: string; + }; + } +} diff --git a/src/action-card/internal.tsx b/src/action-card/internal.tsx new file mode 100644 index 0000000000..3978d2cc8b --- /dev/null +++ b/src/action-card/internal.tsx @@ -0,0 +1,149 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import React, { useImperativeHandle, useRef } from 'react'; +import clsx from 'clsx'; + +import { useMergeRefs, useUniqueId } from '@cloudscape-design/component-toolkit/internal'; + +import { getBaseProps } from '../internal/base-component'; +import InternalStructuredItem from '../internal/components/structured-item'; +import { fireCancelableEvent } from '../internal/events'; +import { InternalBaseComponentProps } from '../internal/hooks/use-base-component'; +import WithNativeAttributes from '../internal/utils/with-native-attributes'; +import { type ActionCardProps } from './interfaces'; +import { getContentStyles, getHeaderStyles, getRootStyles } from './style'; + +import styles from './styles.css.js'; + +export type InternalActionCardProps = ActionCardProps & InternalBaseComponentProps; + +const InternalActionCard = React.forwardRef( + ( + { + header, + description, + children, + onClick, + ariaLabel, + ariaDescribedby, + disabled = false, + disableHeaderPaddings = false, + disableContentPaddings = false, + icon, + iconVerticalAlignment = 'top', + variant = 'default', + style, + nativeButtonAttributes, + __internalRootRef, + ...rest + }: InternalActionCardProps, + ref: React.Ref + ) => { + const baseProps = getBaseProps(rest); + const buttonRef = useRef(null); + const headerId = useUniqueId('action-card-header-'); + const descriptionId = useUniqueId('action-card-description-'); + + useImperativeHandle(ref, () => ({ + focus: () => { + buttonRef.current?.focus(); + }, + })); + + const handleClick = (event: React.MouseEvent) => { + if (disabled) { + return; + } + fireCancelableEvent(onClick, {}, event); + }; + + const rootStyleProps = getRootStyles(style); + const headerStyleProps = getHeaderStyles(style); + const contentStyleProps = getContentStyles(style); + + const headerRowEmpty = !header && !description; + + const iconWrapper = icon && ( + + ); + const iconInHeaderRow = icon && iconVerticalAlignment === 'top' && !!header; + + const headerSection = !headerRowEmpty ? ( +
+ + {header} +
+ ) + } + secondaryContent={ + description && ( +
+ {description} +
+ ) + } + disablePaddings={disableHeaderPaddings} + /> +
+ ) : null; + + const contentElement = ( +
+ {iconInHeaderRow ? ( +
+ {headerSection} + {iconWrapper} +
+ ) : ( + headerSection + )} + {children && ( +
+ {children} +
+ )} +
+ ); + const rootRef = useMergeRefs(buttonRef, __internalRootRef); + + return ( + > + {...baseProps} + ref={rootRef} + tag="button" + componentName="ActionCard" + nativeAttributes={nativeButtonAttributes} + type="button" + className={clsx( + styles.root, + styles[`variant-${variant}`], + disabled && styles.disabled, + !!icon && styles['has-icon'], + !!icon && styles[`icon-align-end`], + !!icon && styles[`icon-vertical-align-${iconVerticalAlignment}`], + baseProps.className + )} + style={rootStyleProps} + onClick={handleClick} + aria-label={ariaLabel} + aria-labelledby={!ariaLabel && header ? headerId : undefined} + aria-describedby={ariaDescribedby || (description ? descriptionId : undefined)} + aria-disabled={disabled} + disabled={disabled} + > + {contentElement} + {!iconInHeaderRow && iconWrapper} + + ); + } +); + +export default InternalActionCard; diff --git a/src/action-card/motion.scss b/src/action-card/motion.scss new file mode 100644 index 0000000000..36b174d371 --- /dev/null +++ b/src/action-card/motion.scss @@ -0,0 +1,31 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +@use '../internal/styles' as styles; +@use '../internal/styles/tokens' as awsui; + +.root { + @include styles.with-motion { + transition-property: background-color; + transition-duration: awsui.$motion-duration-transition-show-paced; + transition-timing-function: awsui.$motion-easing-transition-show-paced; + } +} + +.root::after { + @include styles.with-motion { + transition-property: border-top-color, border-right-color, border-bottom-color, border-left-color; + transition-duration: awsui.$motion-duration-transition-show-paced; + transition-timing-function: awsui.$motion-easing-transition-show-paced; + } +} + +.icon { + @include styles.with-motion { + transition-property: color; + transition-duration: awsui.$motion-duration-transition-show-paced; + transition-timing-function: awsui.$motion-easing-transition-show-paced; + } +} diff --git a/src/action-card/style.tsx b/src/action-card/style.tsx new file mode 100644 index 0000000000..b53dee3d0c --- /dev/null +++ b/src/action-card/style.tsx @@ -0,0 +1,71 @@ +// 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 { ActionCardProps } from './interfaces'; + +export function getRootStyles(style: ActionCardProps.Style | undefined) { + if (SYSTEM !== 'core' || !style?.root) { + return undefined; + } + + return { + ...(style.root.background && { + [customCssProps.styleActionCardBackgroundActive]: style.root.background?.active, + [customCssProps.styleActionCardBackgroundDefault]: style.root.background?.default, + [customCssProps.styleActionCardBackgroundDisabled]: style.root.background?.disabled, + [customCssProps.styleActionCardBackgroundHover]: style.root.background?.hover, + }), + ...(style.root.borderColor && { + [customCssProps.styleActionCardBorderColorActive]: style.root.borderColor?.active, + [customCssProps.styleActionCardBorderColorDefault]: style.root.borderColor?.default, + [customCssProps.styleActionCardBorderColorDisabled]: style.root.borderColor?.disabled, + [customCssProps.styleActionCardBorderColorHover]: style.root.borderColor?.hover, + }), + ...(style.root.borderRadius && { + [customCssProps.styleActionCardBorderRadiusDefault]: style.root.borderRadius?.default, + [customCssProps.styleActionCardBorderRadiusHover]: style.root.borderRadius?.hover, + [customCssProps.styleActionCardBorderRadiusActive]: style.root.borderRadius?.active, + [customCssProps.styleActionCardBorderRadiusDisabled]: style.root.borderRadius?.disabled, + }), + ...(style.root.borderWidth && { + [customCssProps.styleActionCardBorderWidthDefault]: style.root.borderWidth?.default, + [customCssProps.styleActionCardBorderWidthHover]: style.root.borderWidth?.hover, + [customCssProps.styleActionCardBorderWidthActive]: style.root.borderWidth?.active, + [customCssProps.styleActionCardBorderWidthDisabled]: style.root.borderWidth?.disabled, + }), + ...(style.root.boxShadow && { + [customCssProps.styleActionCardBoxShadowActive]: style.root.boxShadow?.active, + [customCssProps.styleActionCardBoxShadowDefault]: style.root.boxShadow?.default, + [customCssProps.styleActionCardBoxShadowDisabled]: style.root.boxShadow?.disabled, + [customCssProps.styleActionCardBoxShadowHover]: style.root.boxShadow?.hover, + }), + ...(style.root.focusRing && { + [customCssProps.styleActionCardFocusRingBorderColor]: style.root.focusRing?.borderColor, + [customCssProps.styleActionCardFocusRingBorderRadius]: style.root.focusRing?.borderRadius, + [customCssProps.styleActionCardFocusRingBorderWidth]: style.root.focusRing?.borderWidth, + }), + }; +} + +export function getHeaderStyles(style: ActionCardProps.Style | undefined) { + if (SYSTEM !== 'core' || !style?.header) { + return undefined; + } + + return { + paddingBlock: style.header.paddingBlock, + paddingInline: style.header.paddingInline, + }; +} + +export function getContentStyles(style: ActionCardProps.Style | undefined) { + if (SYSTEM !== 'core' || !style?.content) { + return undefined; + } + + return { + paddingBlock: style.content.paddingBlock, + paddingInline: style.content.paddingInline, + }; +} diff --git a/src/action-card/styles.scss b/src/action-card/styles.scss new file mode 100644 index 0000000000..a5af577061 --- /dev/null +++ b/src/action-card/styles.scss @@ -0,0 +1,379 @@ +/* + Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + SPDX-License-Identifier: Apache-2.0 +*/ + +@use 'sass:map'; +@use 'sass:math'; + +@use '../internal/styles' as styles; +@use '../internal/styles/tokens' as awsui; +@use '../internal/generated/custom-css-properties/index.scss' as custom-props; +@use '@cloudscape-design/component-toolkit/internal/focus-visible' as focus-visible; +@use './motion'; + +// Variant configuration maps +$variant-border-radius: ( + 'embedded': awsui.$border-radius-card-embedded, + 'default': awsui.$border-radius-action-card, +); + +$variant-padding-block: ( + 'embedded': awsui.$space-card-vertical-embedded, + 'default': awsui.$space-action-card-vertical, +); + +$variant-padding-inline: ( + 'embedded': awsui.$space-card-horizontal-embedded, + 'default': awsui.$space-action-card-horizontal, +); + +@mixin apply-border-radius($variant: 'default', $state: 'default') { + $radius: map.get($variant-border-radius, $variant); + @if $state == 'default' { + border-start-start-radius: var(#{custom-props.$styleActionCardBorderRadiusDefault}, $radius); + border-start-end-radius: var(#{custom-props.$styleActionCardBorderRadiusDefault}, $radius); + border-end-start-radius: var(#{custom-props.$styleActionCardBorderRadiusDefault}, $radius); + border-end-end-radius: var(#{custom-props.$styleActionCardBorderRadiusDefault}, $radius); + } @else if $state == 'hover' { + border-start-start-radius: var( + #{custom-props.$styleActionCardBorderRadiusHover}, + var(#{custom-props.$styleActionCardBorderRadiusDefault}, $radius) + ); + border-start-end-radius: var( + #{custom-props.$styleActionCardBorderRadiusHover}, + var(#{custom-props.$styleActionCardBorderRadiusDefault}, $radius) + ); + border-end-start-radius: var( + #{custom-props.$styleActionCardBorderRadiusHover}, + var(#{custom-props.$styleActionCardBorderRadiusDefault}, $radius) + ); + border-end-end-radius: var( + #{custom-props.$styleActionCardBorderRadiusHover}, + var(#{custom-props.$styleActionCardBorderRadiusDefault}, $radius) + ); + } @else if $state == 'active' { + border-start-start-radius: var( + #{custom-props.$styleActionCardBorderRadiusActive}, + var(#{custom-props.$styleActionCardBorderRadiusDefault}, $radius) + ); + border-start-end-radius: var( + #{custom-props.$styleActionCardBorderRadiusActive}, + var(#{custom-props.$styleActionCardBorderRadiusDefault}, $radius) + ); + border-end-start-radius: var( + #{custom-props.$styleActionCardBorderRadiusActive}, + var(#{custom-props.$styleActionCardBorderRadiusDefault}, $radius) + ); + border-end-end-radius: var( + #{custom-props.$styleActionCardBorderRadiusActive}, + var(#{custom-props.$styleActionCardBorderRadiusDefault}, $radius) + ); + } @else if $state == 'disabled' { + border-start-start-radius: var( + #{custom-props.$styleActionCardBorderRadiusDisabled}, + var(#{custom-props.$styleActionCardBorderRadiusDefault}, $radius) + ); + border-start-end-radius: var( + #{custom-props.$styleActionCardBorderRadiusDisabled}, + var(#{custom-props.$styleActionCardBorderRadiusDefault}, $radius) + ); + border-end-start-radius: var( + #{custom-props.$styleActionCardBorderRadiusDisabled}, + var(#{custom-props.$styleActionCardBorderRadiusDefault}, $radius) + ); + border-end-end-radius: var( + #{custom-props.$styleActionCardBorderRadiusDisabled}, + var(#{custom-props.$styleActionCardBorderRadiusDefault}, $radius) + ); + } +} + +.header { + &-inner { + @include styles.font-heading-s; + &.disabled { + color: awsui.$color-text-action-card-disabled; + } + } + &:not(:has(+ .body)) { + flex: 1; + } +} + +.header-row { + display: flex; + align-items: flex-start; + > .header { + flex: 1; + min-inline-size: 0; + } +} + +.body { + flex: 1; +} + +.description { + @include styles.font-body-s; + color: awsui.$color-text-body-secondary; + &.has-header { + padding-block-start: awsui.$space-action-card-description-padding-top; + } + &.disabled { + color: awsui.$color-text-action-card-disabled; + } +} + +.icon { + flex-shrink: 0; + display: flex; + color: awsui.$color-icon-action-card-default; +} + +.root { + @include styles.styles-reset(); + box-sizing: border-box; + position: relative; + inline-size: 100%; + min-inline-size: 0; + text-align: start; + cursor: pointer; + display: flex; + background-color: var(#{custom-props.$styleActionCardBackgroundDefault}, awsui.$color-background-action-card-default); + box-shadow: var(#{custom-props.$styleActionCardBoxShadowDefault}, none); + color: inherit; + padding-inline: 0; + padding-block: 0; + border-block: none; + border-inline: none; + + &:after { + @include styles.base-pseudo-element; + + box-shadow: none; + border-block: solid var(#{custom-props.$styleActionCardBorderWidthDefault}, awsui.$border-width-action-card-default) + var(#{custom-props.$styleActionCardBorderColorDefault}, awsui.$color-border-action-card-default); + border-inline: solid + var(#{custom-props.$styleActionCardBorderWidthDefault}, awsui.$border-width-action-card-default) + var(#{custom-props.$styleActionCardBorderColorDefault}, awsui.$color-border-action-card-default); + } + + &.variant-default, + &.variant-embedded { + > .inner-card { + display: flex; + flex-direction: column; + block-size: 100%; + inline-size: 100%; + min-inline-size: 0; + + > .header + .body { + &:not(.no-padding) { + padding-block-start: awsui.$space-xxs; + } + } + + > .header { + &:not(.no-padding):has(+ .body) { + padding-block-end: awsui.$space-xxs; + } + } + + > .header-row + .body { + &:not(.no-padding) { + padding-block-start: awsui.$space-xxs; + } + } + + > .header-row:has(+ .body) { + > .header:not(.no-padding) { + padding-block-end: awsui.$space-xxs; + } + } + } + } + + &.icon-vertical-align-center > .icon { + align-self: center; + } + + &.disabled { + cursor: default; + color: awsui.$color-text-action-card-disabled; + background-color: var( + #{custom-props.$styleActionCardBackgroundDisabled}, + awsui.$color-background-action-card-disabled + ); + box-shadow: var(#{custom-props.$styleActionCardBoxShadowDisabled}, none); + + @each $variant in ('embedded', 'default') { + &.variant-#{$variant} { + &, + &:after { + @include apply-border-radius($variant, 'disabled'); + } + } + } + + > .icon { + color: awsui.$color-icon-action-card-disabled; + } + + &:after { + border-color: var(#{custom-props.$styleActionCardBorderColorDisabled}, awsui.$color-border-action-card-disabled); + border-block-width: var( + #{custom-props.$styleActionCardBorderWidthDisabled}, + awsui.$border-width-action-card-disabled + ); + border-inline-width: var( + #{custom-props.$styleActionCardBorderWidthDisabled}, + awsui.$border-width-action-card-disabled + ); + } + } + + &:not(.disabled):hover { + background-color: var(#{custom-props.$styleActionCardBackgroundHover}, awsui.$color-background-action-card-hover); + box-shadow: var(#{custom-props.$styleActionCardBoxShadowHover}, none); + + > .icon { + color: awsui.$color-icon-action-card-hover; + } + + @each $variant in ('embedded', 'default') { + &.variant-#{$variant} { + &, + &:after { + @include apply-border-radius($variant, 'hover'); + } + } + } + + &:after { + border-color: var(#{custom-props.$styleActionCardBorderColorHover}, awsui.$color-border-action-card-hover); + border-block-width: var(#{custom-props.$styleActionCardBorderWidthHover}, awsui.$border-width-action-card-hover); + border-inline-width: var(#{custom-props.$styleActionCardBorderWidthHover}, awsui.$border-width-action-card-hover); + } + } + + // Active state + &:not(.disabled):active { + background-color: var(#{custom-props.$styleActionCardBackgroundActive}, awsui.$color-background-action-card-active); + box-shadow: var(#{custom-props.$styleActionCardBoxShadowActive}, none); + + > .icon { + color: awsui.$color-icon-action-card-active; + } + + @each $variant in ('embedded', 'default') { + &.variant-#{$variant} { + &, + &:after { + @include apply-border-radius($variant, 'active'); + } + } + } + + &:after { + border-color: var(#{custom-props.$styleActionCardBorderColorActive}, awsui.$color-border-action-card-active); + border-block-width: var( + #{custom-props.$styleActionCardBorderWidthActive}, + awsui.$border-width-action-card-active + ); + border-inline-width: var( + #{custom-props.$styleActionCardBorderWidthActive}, + awsui.$border-width-action-card-active + ); + } + } + + &.disabled > .inner-card > .header-row > .icon { + color: awsui.$color-icon-action-card-disabled; + } + + &:not(.disabled):hover > .inner-card > .header-row > .icon { + color: awsui.$color-icon-action-card-hover; + } + + &:not(.disabled):active > .inner-card > .header-row > .icon { + color: awsui.$color-icon-action-card-active; + } + + @include focus-visible.when-visible { + @include styles.focus-highlight( + 2px, + var(#{custom-props.$styleActionCardFocusRingBorderRadius}, awsui.$border-radius-control-default-focus-ring), + 0 0 0 var(#{custom-props.$styleActionCardFocusRingBorderWidth}, 2px) + var(#{custom-props.$styleActionCardFocusRingBorderColor}, awsui.$color-border-item-focused) + ); + + @each $variant in ('embedded', 'default') { + &.variant-#{$variant}::before { + $focus-radius: var( + #{custom-props.$styleActionCardFocusRingBorderRadius}, + awsui.$border-radius-control-default-focus-ring + ); + border-end-end-radius: $focus-radius; + } + } + } + + @each $variant in ('embedded', 'default') { + &.variant-#{$variant} { + &, + &:after { + @include apply-border-radius($variant); + } + + $padding-block: map.get($variant-padding-block, $variant); + $padding-inline: map.get($variant-padding-inline, $variant); + + > .inner-card > .header, + > .inner-card > .body { + &:not(.no-padding) { + padding-block: $padding-block; + padding-inline: $padding-inline; + } + } + + > .inner-card > .header-row > .header { + &:not(.no-padding) { + padding-block: $padding-block; + padding-inline: $padding-inline; + } + } + + > .icon { + padding-block: $padding-block; + } + + > .inner-card > .header-row > .icon { + padding-block-start: $padding-block; + } + + &.icon-align-end { + > .icon { + padding-inline-end: $padding-inline; + } + + > .inner-card > .header-row > .icon { + padding-inline-end: $padding-inline; + } + + > .inner-card > .header, + > .inner-card > .body { + &:not(.no-padding) { + padding-inline-end: awsui.$space-xs; + } + } + + > .inner-card > .header-row > .header { + &:not(.no-padding) { + padding-inline-end: awsui.$space-xs; + } + } + } + } + } +} diff --git a/src/internal/components/structured-item/styles.scss b/src/internal/components/structured-item/styles.scss index 1850de8ebb..6ca8f8b65c 100644 --- a/src/internal/components/structured-item/styles.scss +++ b/src/internal/components/structured-item/styles.scss @@ -8,6 +8,7 @@ .root { @include styles.styles-reset; @include styles.text-wrapping; + cursor: inherit; display: flex; flex: 1; flex-direction: row; diff --git a/src/test-utils/dom/action-card/index.ts b/src/test-utils/dom/action-card/index.ts new file mode 100644 index 0000000000..5c41c914b3 --- /dev/null +++ b/src/test-utils/dom/action-card/index.ts @@ -0,0 +1,45 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { ComponentWrapper, ElementWrapper, usesDom } from '@cloudscape-design/test-utils-core/dom'; + +import styles from '../../../action-card/styles.selectors.js'; + +export default class ActionCardWrapper extends ComponentWrapper { + static rootSelector: string = styles.root; + + /** + * Returns the header element of the action card. + */ + findHeader(): ElementWrapper | null { + return this.findByClassName(styles['header-inner']); + } + + /** + * Returns the description element of the action card. + */ + findDescription(): ElementWrapper | null { + return this.findByClassName(styles.description); + } + + /** + * Returns the content element of the action card. + */ + findContent(): ElementWrapper | null { + return this.findByClassName(styles.body); + } + + /** + * Finds the icon slot of the action card. + */ + findIcon(): ElementWrapper | null { + return this.findByClassName(styles.icon); + } + + /** + * Returns whether the action card is disabled. + */ + @usesDom + isDisabled(): boolean { + return this.element.disabled || this.element.getAttribute('aria-disabled') === 'true'; + } +} diff --git a/style-dictionary/classic/borders.ts b/style-dictionary/classic/borders.ts index af0f0c8bb5..b6c3cbb6ba 100644 --- a/style-dictionary/classic/borders.ts +++ b/style-dictionary/classic/borders.ts @@ -43,6 +43,11 @@ const tokens: StyleDictionary.BordersDictionary = { borderWidthField: '1px', borderWidthPopover: '1px', borderWidthToken: '1px', + borderRadiusActionCard: '{borderRadiusContainer}', + borderWidthActionCardDefault: '1px', + borderWidthActionCardHover: '1px', + borderWidthActionCardActive: '1px', + borderWidthActionCardDisabled: '1px', }; const expandedTokens: StyleDictionary.ExpandedGlobalScopeDictionary = merge({}, parentTokens, tokens); diff --git a/style-dictionary/classic/colors.ts b/style-dictionary/classic/colors.ts index 146ad785ca..ce34729b4b 100644 --- a/style-dictionary/classic/colors.ts +++ b/style-dictionary/classic/colors.ts @@ -149,6 +149,19 @@ const tokens: StyleDictionary.ColorsDictionary = { colorDragPlaceholderActive: { light: '{colorNeutral300}', dark: '{colorNeutral600}' }, colorDragPlaceholderHover: { light: '{colorPrimary200}', dark: '{colorPrimary600}' }, colorBackgroundDropdownItemHover: { light: '{colorNeutral200}', dark: '{colorNeutral700}' }, + colorBackgroundActionCardDefault: { light: '{colorWhite}', dark: '{colorNeutral850}' }, + colorBackgroundActionCardHover: { light: '{colorPrimary50}', dark: '{colorNeutral800}' }, + colorBackgroundActionCardActive: { light: '{colorPrimary100}', dark: '{colorNeutral700}' }, + colorBorderActionCardDefault: { light: '{colorPrimary600}', dark: '{colorPrimary400}' }, + colorBorderActionCardHover: { light: '{colorPrimary900}', dark: '{colorPrimary300}' }, + colorBorderActionCardActive: { light: '{colorPrimary900}', dark: '{colorPrimary300}' }, + colorBorderActionCardDisabled: { light: '{colorNeutral400}', dark: '{colorNeutral600}' }, + colorBackgroundActionCardDisabled: { light: '{colorNeutral250}', dark: '{colorNeutral800}' }, + colorTextActionCardDisabled: { light: '{colorNeutral500}', dark: '{colorNeutral500}' }, + colorIconActionCardDefault: { light: '{colorPrimary600}', dark: '{colorPrimary400}' }, + colorIconActionCardHover: { light: '{colorPrimary900}', dark: '{colorPrimary300}' }, + colorIconActionCardActive: { light: '{colorPrimary900}', dark: '{colorPrimary300}' }, + colorIconActionCardDisabled: { light: '{colorNeutral400}', dark: '{colorNeutral600}' }, }; const expandedTokens: StyleDictionary.ExpandedColorScopeDictionary = merge( diff --git a/style-dictionary/classic/spacing.ts b/style-dictionary/classic/spacing.ts index 8bee0f2cb0..7d4c06e9be 100644 --- a/style-dictionary/classic/spacing.ts +++ b/style-dictionary/classic/spacing.ts @@ -22,7 +22,7 @@ const tokens: StyleDictionary.SpacingDictionary = { spaceCalendarGridGutter: '0px', spaceCardHorizontalDefault: '{spaceContainerHorizontal}', spaceCardHorizontalEmbedded: { comfortable: '{spaceS}', compact: '10px' }, - spaceCardVerticalDefault: '{spaceScaledL}', + spaceCardVerticalDefault: '{spaceScaledM}', spaceCardVerticalEmbedded: { comfortable: '10px', compact: '{spaceXs}' }, spaceCodeEditorStatusFocusOutlineGutter: '3px', spaceContainerContentTop: '{spaceScaledM}', @@ -60,6 +60,10 @@ const tokens: StyleDictionary.SpacingDictionary = { spaceTableHeaderHorizontal: '{spaceContainerHorizontal}', spaceTableHeaderToolsBottom: '{spaceScaledXxs}', spaceTableHorizontal: '0px', + spaceActionCardHorizontal: '{spaceL}', + spaceActionCardVertical: '{spaceM}', + spaceActionCardDescriptionPaddingTop: '{spaceScaledXxs}', + spaceActionCardContentPaddingTop: '{spaceScaledS}', }; const expandedTokens: StyleDictionary.ExpandedDensityScopeDictionary = merge( diff --git a/style-dictionary/utils/token-names.ts b/style-dictionary/utils/token-names.ts index fd791ece8e..79e405c78d 100644 --- a/style-dictionary/utils/token-names.ts +++ b/style-dictionary/utils/token-names.ts @@ -774,7 +774,20 @@ export type ColorsTokenName = | 'colorDropzoneBorderDefault' | 'colorDropzoneBorderHover' | 'colorGapGlobalDrawer' - | 'colorItemSelected'; + | 'colorItemSelected' + | 'colorBackgroundActionCardDefault' + | 'colorBackgroundActionCardHover' + | 'colorBackgroundActionCardActive' + | 'colorBorderActionCardDefault' + | 'colorBorderActionCardHover' + | 'colorBorderActionCardActive' + | 'colorBorderActionCardDisabled' + | 'colorBackgroundActionCardDisabled' + | 'colorTextActionCardDisabled' + | 'colorIconActionCardDefault' + | 'colorIconActionCardHover' + | 'colorIconActionCardActive' + | 'colorIconActionCardDisabled'; export type TypographyTokenName = | 'fontBoxValueLargeWeight' | 'fontButtonLetterSpacing' @@ -880,7 +893,12 @@ export type BordersTokenName = | 'borderWidthIconNormal' | 'borderWidthIconMedium' | 'borderWidthIconBig' - | 'borderWidthIconLarge'; + | 'borderWidthIconLarge' + | 'borderRadiusActionCard' + | 'borderWidthActionCardDefault' + | 'borderWidthActionCardHover' + | 'borderWidthActionCardActive' + | 'borderWidthActionCardDisabled'; export type MotionTokenName = | 'motionDurationExtraFast' | 'motionDurationExtraSlow' @@ -1035,7 +1053,11 @@ export type SpacingTokenName = | 'spaceXxl' | 'spaceXxs' | 'spaceXxxl' - | 'spaceXxxs'; + | 'spaceXxxs' + | 'spaceActionCardHorizontal' + | 'spaceActionCardVertical' + | 'spaceActionCardDescriptionPaddingTop' + | 'spaceActionCardContentPaddingTop'; export type ShadowsTokenName = | 'shadowCard' | 'shadowContainer' diff --git a/style-dictionary/visual-refresh/borders.ts b/style-dictionary/visual-refresh/borders.ts index 6bae311727..75db955040 100644 --- a/style-dictionary/visual-refresh/borders.ts +++ b/style-dictionary/visual-refresh/borders.ts @@ -56,4 +56,9 @@ export const tokens: StyleDictionary.BordersDictionary = { borderWidthIconMedium: '2px', borderWidthIconBig: '3px', borderWidthIconLarge: '4px', + borderRadiusActionCard: '{borderRadiusContainer}', + borderWidthActionCardDefault: '1px', + borderWidthActionCardHover: '1px', + borderWidthActionCardActive: '1px', + borderWidthActionCardDisabled: '1px', }; diff --git a/style-dictionary/visual-refresh/colors.ts b/style-dictionary/visual-refresh/colors.ts index ec7adff6a6..8abd84f75e 100644 --- a/style-dictionary/visual-refresh/colors.ts +++ b/style-dictionary/visual-refresh/colors.ts @@ -292,6 +292,19 @@ const tokens: StyleDictionary.ColorsDictionary = { colorDropzoneBorderHover: { light: '{colorPrimary900}', dark: '{colorPrimary300}' }, colorGapGlobalDrawer: { light: '{colorNeutral250}', dark: '{colorNeutral950}' }, colorTreeViewConnectorLine: { light: '{colorNeutral500}', dark: '{colorNeutral300}' }, + colorBackgroundActionCardDefault: { light: '{colorWhite}', dark: '{colorNeutral850}' }, + colorBackgroundActionCardHover: { light: '{colorPrimary50}', dark: '{colorNeutral800}' }, + colorBackgroundActionCardActive: { light: '{colorPrimary100}', dark: '{colorNeutral700}' }, + colorBorderActionCardDefault: { light: '{colorPrimary600}', dark: '{colorPrimary400}' }, + colorBorderActionCardHover: { light: '{colorPrimary900}', dark: '{colorPrimary300}' }, + colorBorderActionCardActive: { light: '{colorPrimary900}', dark: '{colorPrimary300}' }, + colorBorderActionCardDisabled: { light: '{colorNeutral400}', dark: '{colorNeutral600}' }, + colorBackgroundActionCardDisabled: { light: '{colorWhite}', dark: '{colorNeutral850}' }, + colorTextActionCardDisabled: { light: '{colorNeutral500}', dark: '{colorNeutral500}' }, + colorIconActionCardDefault: { light: '{colorPrimary600}', dark: '{colorPrimary400}' }, + colorIconActionCardHover: { light: '{colorPrimary900}', dark: '{colorPrimary300}' }, + colorIconActionCardActive: { light: '{colorPrimary900}', dark: '{colorPrimary300}' }, + colorIconActionCardDisabled: { light: '{colorNeutral400}', dark: '{colorNeutral600}' }, }; const expandedTokens: StyleDictionary.ExpandedColorScopeDictionary = expandColorDictionary(tokens); diff --git a/style-dictionary/visual-refresh/spacing.ts b/style-dictionary/visual-refresh/spacing.ts index 76c306aacb..25e3699422 100644 --- a/style-dictionary/visual-refresh/spacing.ts +++ b/style-dictionary/visual-refresh/spacing.ts @@ -67,6 +67,10 @@ const tokens: StyleDictionary.SpacingDictionary = { spaceTableHorizontal: '{spaceContainerHorizontal}', spaceTreeViewIndentation: '{spaceXl}', spaceTileGutter: { comfortable: '{spaceXl}', compact: '{spaceM}' }, + spaceActionCardHorizontal: '{spaceContainerHorizontal}', + spaceActionCardVertical: '{spaceScaledM}', + spaceActionCardDescriptionPaddingTop: '{spaceScaledXxs}', + spaceActionCardContentPaddingTop: '{spaceScaledXs}', spaceScaled2xNone: '{spaceNone}', spaceScaled2xXxxs: { comfortable: '{spaceXxxs}', compact: '{spaceNone}' }, From 44c0cf5f24606000130bff018878f613695782ab Mon Sep 17 00:00:00 2001 From: Simon Brebeck Date: Sun, 29 Mar 2026 16:55:05 +0200 Subject: [PATCH 2/4] chore: Update snapshots, increase package.json size-limit --- package.json | 2 +- .../__snapshots__/themes.test.ts.snap | 176 +++++- .../__snapshots__/documenter.test.ts.snap | 514 +++++++++++++++++- .../test-utils-selectors.test.tsx.snap | 7 + .../test-utils-wrappers.test.tsx.snap | 80 +++ .../__snapshots__/styles.test.tsx.snap | 6 +- .../__snapshots__/styles.test.tsx.snap | 144 ++--- .../__snapshots__/styles.test.tsx.snap | 10 +- .../__snapshots__/styles.test.tsx.snap | 66 +-- .../__snapshots__/styles.test.tsx.snap | 42 +- .../__snapshots__/styles.test.tsx.snap | 40 +- .../__snapshots__/styles.test.tsx.snap | 144 ++--- .../__snapshots__/styles.test.tsx.snap | 26 +- 13 files changed, 1012 insertions(+), 245 deletions(-) diff --git a/package.json b/package.json index adaf406727..dd11ec7af1 100644 --- a/package.json +++ b/package.json @@ -177,7 +177,7 @@ { "path": "lib/components/internal/widget-exports.js", "brotli": false, - "limit": "1250 kB", + "limit": "1255 kB", "ignore": "react-dom" } ], diff --git a/src/__integ__/__snapshots__/themes.test.ts.snap b/src/__integ__/__snapshots__/themes.test.ts.snap index 34358bd451..6db38c765f 100644 --- a/src/__integ__/__snapshots__/themes.test.ts.snap +++ b/src/__integ__/__snapshots__/themes.test.ts.snap @@ -21,6 +21,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "compact" 1`] = "border-link-focus-ring-shadow-spread": "0px", "border-panel-header-width": "0px", "border-panel-top-width": "0px", + "border-radius-action-card": "0px", "border-radius-alert": "2px", "border-radius-badge": "16px", "border-radius-button": "2px", @@ -43,6 +44,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "compact" 1`] = "border-radius-token": "2px", "border-radius-tutorial-panel-item": "2px", "border-table-sticky-width": "0px", + "border-width-action-card-active": "1px", + "border-width-action-card-default": "1px", + "border-width-action-card-disabled": "1px", + "border-width-action-card-hover": "1px", "border-width-alert": "1px", "border-width-button": "1px", "border-width-card": "0px", @@ -57,6 +62,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "compact" 1`] = "border-width-popover": "1px", "border-width-token": "1px", "color-aws-squid-ink": "#232f3e", + "color-background-action-card-active": "#d1f1ff", + "color-background-action-card-default": "#ffffff", + "color-background-action-card-disabled": "#eaeded", + "color-background-action-card-hover": "#f1faff", "color-background-avatar-default": "#545b64", "color-background-avatar-gen-ai": "radial-gradient(circle farthest-corner at top right, #b8e7ff 0%, #0099ff 25%, #5c7fff 40% , #8575ff 60%, #962eff 80%)", "color-background-badge-icon": "#d13212", @@ -160,6 +169,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "compact" 1`] = "color-black": "#000000", "color-board-placeholder-active": "#d5dbdb", "color-board-placeholder-hover": "#99cbe4", + "color-border-action-card-active": "#002b66", + "color-border-action-card-default": "#0073bb", + "color-border-action-card-disabled": "#aab7b8", + "color-border-action-card-hover": "#002b66", "color-border-button-normal-active": "#545b64", "color-border-button-normal-default": "#545b64", "color-border-button-normal-disabled": "#d5dbdb", @@ -399,6 +412,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "compact" 1`] = "color-grey-transparent": "rgba(0, 28, 36, 0.3)", "color-grey-transparent-heavy": "rgba(0, 28, 36, 0.5)", "color-grey-transparent-light": "rgba(0, 28, 36, 0.15)", + "color-icon-action-card-active": "#002b66", + "color-icon-action-card-default": "#0073bb", + "color-icon-action-card-disabled": "#aab7b8", + "color-icon-action-card-hover": "#002b66", "color-info-1000": "#12293b", "color-info-300": "#44b9d6", "color-info-400": "#00a1c9", @@ -451,6 +468,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "compact" 1`] = "color-success-500": "#6aaf35", "color-success-600": "#1d8102", "color-text-accent": "#0073bb", + "color-text-action-card-disabled": "#879596", "color-text-avatar": "#ffffff", "color-text-body-default": "#16191f", "color-text-body-secondary": "#545b64", @@ -677,6 +695,9 @@ exports[`CSS Custom Properties match previous snapshot for mode "compact" 1`] = "size-table-selection-horizontal": "54px", "size-vertical-input": "28px", "size-vertical-panel-icon-offset": "13px", + "space-action-card-description-padding-top": "2px", + "space-action-card-horizontal": "20px", + "space-action-card-vertical": "16px", "space-alert-action-left": "20px", "space-alert-horizontal": "20px", "space-alert-message-right": "0px", @@ -692,7 +713,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "compact" 1`] = "space-calendar-grid-selected-focus-outline-gutter": "2px", "space-card-horizontal-default": "20px", "space-card-horizontal-embedded": "10px", - "space-card-vertical-default": "16px", + "space-card-vertical-default": "12px", "space-card-vertical-embedded": "8px", "space-code-editor-status-focus-outline-gutter": "3px", "space-container-content-top": "12px", @@ -798,6 +819,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "dark" 1`] = ` "border-link-focus-ring-shadow-spread": "0px", "border-panel-header-width": "0px", "border-panel-top-width": "0px", + "border-radius-action-card": "0px", "border-radius-alert": "2px", "border-radius-badge": "16px", "border-radius-button": "2px", @@ -820,6 +842,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "dark" 1`] = ` "border-radius-token": "2px", "border-radius-tutorial-panel-item": "2px", "border-table-sticky-width": "0px", + "border-width-action-card-active": "1px", + "border-width-action-card-default": "1px", + "border-width-action-card-disabled": "1px", + "border-width-action-card-hover": "1px", "border-width-alert": "1px", "border-width-button": "1px", "border-width-card": "0px", @@ -834,6 +860,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "dark" 1`] = ` "border-width-popover": "1px", "border-width-token": "1px", "color-aws-squid-ink": "#232f3e", + "color-background-action-card-active": "#414750", + "color-background-action-card-default": "#1a2029", + "color-background-action-card-disabled": "#21252c", + "color-background-action-card-hover": "#21252c", "color-background-avatar-default": "#545b64", "color-background-avatar-gen-ai": "radial-gradient(circle farthest-corner at top right, #b8e7ff 0%, #0099ff 25%, #5c7fff 40% , #8575ff 60%, #962eff 80%)", "color-background-badge-icon": "#ff5d64", @@ -937,6 +967,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "dark" 1`] = ` "color-black": "#000000", "color-board-placeholder-active": "#687078", "color-board-placeholder-hover": "#0073bb", + "color-border-action-card-active": "#44b9d6", + "color-border-action-card-default": "#00a1c9", + "color-border-action-card-disabled": "#687078", + "color-border-action-card-hover": "#44b9d6", "color-border-button-normal-active": "#879596", "color-border-button-normal-default": "#879596", "color-border-button-normal-disabled": "#414750", @@ -1176,6 +1210,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "dark" 1`] = ` "color-grey-transparent": "rgba(0, 0, 0, 0.3)", "color-grey-transparent-heavy": "rgba(0, 0, 0, 0.5)", "color-grey-transparent-light": "rgba(0, 0, 0, 0.3)", + "color-icon-action-card-active": "#44b9d6", + "color-icon-action-card-default": "#00a1c9", + "color-icon-action-card-disabled": "#687078", + "color-icon-action-card-hover": "#44b9d6", "color-info-1000": "#12293b", "color-info-300": "#44b9d6", "color-info-400": "#00a1c9", @@ -1228,6 +1266,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "dark" 1`] = ` "color-success-500": "#6aaf35", "color-success-600": "#1d8102", "color-text-accent": "#44b9d6", + "color-text-action-card-disabled": "#879596", "color-text-avatar": "#ffffff", "color-text-body-default": "#d5dbdb", "color-text-body-secondary": "#d5dbdb", @@ -1454,6 +1493,9 @@ exports[`CSS Custom Properties match previous snapshot for mode "dark" 1`] = ` "size-table-selection-horizontal": "54px", "size-vertical-input": "32px", "size-vertical-panel-icon-offset": "15px", + "space-action-card-description-padding-top": "4px", + "space-action-card-horizontal": "20px", + "space-action-card-vertical": "16px", "space-alert-action-left": "20px", "space-alert-horizontal": "20px", "space-alert-message-right": "0px", @@ -1469,7 +1511,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "dark" 1`] = ` "space-calendar-grid-selected-focus-outline-gutter": "2px", "space-card-horizontal-default": "20px", "space-card-horizontal-embedded": "12px", - "space-card-vertical-default": "20px", + "space-card-vertical-default": "16px", "space-card-vertical-embedded": "10px", "space-code-editor-status-focus-outline-gutter": "3px", "space-container-content-top": "16px", @@ -1575,6 +1617,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "light" 1`] = ` "border-link-focus-ring-shadow-spread": "0px", "border-panel-header-width": "0px", "border-panel-top-width": "0px", + "border-radius-action-card": "0px", "border-radius-alert": "2px", "border-radius-badge": "16px", "border-radius-button": "2px", @@ -1597,6 +1640,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "light" 1`] = ` "border-radius-token": "2px", "border-radius-tutorial-panel-item": "2px", "border-table-sticky-width": "0px", + "border-width-action-card-active": "1px", + "border-width-action-card-default": "1px", + "border-width-action-card-disabled": "1px", + "border-width-action-card-hover": "1px", "border-width-alert": "1px", "border-width-button": "1px", "border-width-card": "0px", @@ -1611,6 +1658,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "light" 1`] = ` "border-width-popover": "1px", "border-width-token": "1px", "color-aws-squid-ink": "#232f3e", + "color-background-action-card-active": "#d1f1ff", + "color-background-action-card-default": "#ffffff", + "color-background-action-card-disabled": "#eaeded", + "color-background-action-card-hover": "#f1faff", "color-background-avatar-default": "#545b64", "color-background-avatar-gen-ai": "radial-gradient(circle farthest-corner at top right, #b8e7ff 0%, #0099ff 25%, #5c7fff 40% , #8575ff 60%, #962eff 80%)", "color-background-badge-icon": "#d13212", @@ -1714,6 +1765,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "light" 1`] = ` "color-black": "#000000", "color-board-placeholder-active": "#d5dbdb", "color-board-placeholder-hover": "#99cbe4", + "color-border-action-card-active": "#002b66", + "color-border-action-card-default": "#0073bb", + "color-border-action-card-disabled": "#aab7b8", + "color-border-action-card-hover": "#002b66", "color-border-button-normal-active": "#545b64", "color-border-button-normal-default": "#545b64", "color-border-button-normal-disabled": "#d5dbdb", @@ -1953,6 +2008,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "light" 1`] = ` "color-grey-transparent": "rgba(0, 28, 36, 0.3)", "color-grey-transparent-heavy": "rgba(0, 28, 36, 0.5)", "color-grey-transparent-light": "rgba(0, 28, 36, 0.15)", + "color-icon-action-card-active": "#002b66", + "color-icon-action-card-default": "#0073bb", + "color-icon-action-card-disabled": "#aab7b8", + "color-icon-action-card-hover": "#002b66", "color-info-1000": "#12293b", "color-info-300": "#44b9d6", "color-info-400": "#00a1c9", @@ -2005,6 +2064,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "light" 1`] = ` "color-success-500": "#6aaf35", "color-success-600": "#1d8102", "color-text-accent": "#0073bb", + "color-text-action-card-disabled": "#879596", "color-text-avatar": "#ffffff", "color-text-body-default": "#16191f", "color-text-body-secondary": "#545b64", @@ -2231,6 +2291,9 @@ exports[`CSS Custom Properties match previous snapshot for mode "light" 1`] = ` "size-table-selection-horizontal": "54px", "size-vertical-input": "32px", "size-vertical-panel-icon-offset": "15px", + "space-action-card-description-padding-top": "4px", + "space-action-card-horizontal": "20px", + "space-action-card-vertical": "16px", "space-alert-action-left": "20px", "space-alert-horizontal": "20px", "space-alert-message-right": "0px", @@ -2246,7 +2309,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "light" 1`] = ` "space-calendar-grid-selected-focus-outline-gutter": "2px", "space-card-horizontal-default": "20px", "space-card-horizontal-embedded": "12px", - "space-card-vertical-default": "20px", + "space-card-vertical-default": "16px", "space-card-vertical-embedded": "10px", "space-code-editor-status-focus-outline-gutter": "3px", "space-container-content-top": "16px", @@ -2352,6 +2415,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "reduced-motion" "border-link-focus-ring-shadow-spread": "0px", "border-panel-header-width": "0px", "border-panel-top-width": "0px", + "border-radius-action-card": "0px", "border-radius-alert": "2px", "border-radius-badge": "16px", "border-radius-button": "2px", @@ -2374,6 +2438,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "reduced-motion" "border-radius-token": "2px", "border-radius-tutorial-panel-item": "2px", "border-table-sticky-width": "0px", + "border-width-action-card-active": "1px", + "border-width-action-card-default": "1px", + "border-width-action-card-disabled": "1px", + "border-width-action-card-hover": "1px", "border-width-alert": "1px", "border-width-button": "1px", "border-width-card": "0px", @@ -2388,6 +2456,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "reduced-motion" "border-width-popover": "1px", "border-width-token": "1px", "color-aws-squid-ink": "#232f3e", + "color-background-action-card-active": "#d1f1ff", + "color-background-action-card-default": "#ffffff", + "color-background-action-card-disabled": "#eaeded", + "color-background-action-card-hover": "#f1faff", "color-background-avatar-default": "#545b64", "color-background-avatar-gen-ai": "radial-gradient(circle farthest-corner at top right, #b8e7ff 0%, #0099ff 25%, #5c7fff 40% , #8575ff 60%, #962eff 80%)", "color-background-badge-icon": "#d13212", @@ -2491,6 +2563,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "reduced-motion" "color-black": "#000000", "color-board-placeholder-active": "#d5dbdb", "color-board-placeholder-hover": "#99cbe4", + "color-border-action-card-active": "#002b66", + "color-border-action-card-default": "#0073bb", + "color-border-action-card-disabled": "#aab7b8", + "color-border-action-card-hover": "#002b66", "color-border-button-normal-active": "#545b64", "color-border-button-normal-default": "#545b64", "color-border-button-normal-disabled": "#d5dbdb", @@ -2730,6 +2806,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "reduced-motion" "color-grey-transparent": "rgba(0, 28, 36, 0.3)", "color-grey-transparent-heavy": "rgba(0, 28, 36, 0.5)", "color-grey-transparent-light": "rgba(0, 28, 36, 0.15)", + "color-icon-action-card-active": "#002b66", + "color-icon-action-card-default": "#0073bb", + "color-icon-action-card-disabled": "#aab7b8", + "color-icon-action-card-hover": "#002b66", "color-info-1000": "#12293b", "color-info-300": "#44b9d6", "color-info-400": "#00a1c9", @@ -2782,6 +2862,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "reduced-motion" "color-success-500": "#6aaf35", "color-success-600": "#1d8102", "color-text-accent": "#0073bb", + "color-text-action-card-disabled": "#879596", "color-text-avatar": "#ffffff", "color-text-body-default": "#16191f", "color-text-body-secondary": "#545b64", @@ -3008,6 +3089,9 @@ exports[`CSS Custom Properties match previous snapshot for mode "reduced-motion" "size-table-selection-horizontal": "54px", "size-vertical-input": "32px", "size-vertical-panel-icon-offset": "15px", + "space-action-card-description-padding-top": "4px", + "space-action-card-horizontal": "20px", + "space-action-card-vertical": "16px", "space-alert-action-left": "20px", "space-alert-horizontal": "20px", "space-alert-message-right": "0px", @@ -3023,7 +3107,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "reduced-motion" "space-calendar-grid-selected-focus-outline-gutter": "2px", "space-card-horizontal-default": "20px", "space-card-horizontal-embedded": "12px", - "space-card-vertical-default": "20px", + "space-card-vertical-default": "16px", "space-card-vertical-embedded": "10px", "space-code-editor-status-focus-outline-gutter": "3px", "space-container-content-top": "16px", @@ -3129,6 +3213,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh" "border-link-focus-ring-shadow-spread": "2px", "border-panel-header-width": "1px", "border-panel-top-width": "1px", + "border-radius-action-card": "16px", "border-radius-alert": "12px", "border-radius-badge": "4px", "border-radius-button": "20px", @@ -3151,6 +3236,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh" "border-radius-token": "8px", "border-radius-tutorial-panel-item": "8px", "border-table-sticky-width": "1px", + "border-width-action-card-active": "1px", + "border-width-action-card-default": "1px", + "border-width-action-card-disabled": "1px", + "border-width-action-card-hover": "1px", "border-width-alert": "2px", "border-width-button": "2px", "border-width-card": "1px", @@ -3165,6 +3254,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh" "border-width-popover": "2px", "border-width-token": "2px", "color-aws-squid-ink": "#232f3e", + "color-background-action-card-active": "#d1f1ff", + "color-background-action-card-default": "#ffffff", + "color-background-action-card-disabled": "#ffffff", + "color-background-action-card-hover": "#f0fbff", "color-background-avatar-default": "#424650", "color-background-avatar-gen-ai": "radial-gradient(circle farthest-corner at top right, #b8e7ff 0%, #0099ff 25%, #5c7fff 40% , #8575ff 60%, #962eff 80%)", "color-background-badge-icon": "#db0000", @@ -3268,6 +3361,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh" "color-black": "#000000", "color-board-placeholder-active": "#ebebf0", "color-board-placeholder-hover": "#d1f1ff", + "color-border-action-card-active": "#002b66", + "color-border-action-card-default": "#006ce0", + "color-border-action-card-disabled": "#b4b4bb", + "color-border-action-card-hover": "#002b66", "color-border-button-normal-active": "#002b66", "color-border-button-normal-default": "#006ce0", "color-border-button-normal-disabled": "#b4b4bb", @@ -3507,6 +3604,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh" "color-grey-transparent": "rgba(15, 20, 26, 0.12)", "color-grey-transparent-heavy": "rgba(15, 20, 26, 0.12)", "color-grey-transparent-light": "rgba(15, 20, 26, 0.12)", + "color-icon-action-card-active": "#002b66", + "color-icon-action-card-default": "#006ce0", + "color-icon-action-card-disabled": "#b4b4bb", + "color-icon-action-card-hover": "#002b66", "color-info-1000": "#001129", "color-info-300": "#75cfff", "color-info-400": "#42b4ff", @@ -3559,6 +3660,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh" "color-success-500": "#2bb534", "color-success-600": "#00802f", "color-text-accent": "#006ce0", + "color-text-action-card-disabled": "#8c8c94", "color-text-avatar": "#ffffff", "color-text-body-default": "#0f141a", "color-text-body-secondary": "#424650", @@ -3785,6 +3887,9 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh" "size-table-selection-horizontal": "40px", "size-vertical-input": "32px", "size-vertical-panel-icon-offset": "15px", + "space-action-card-description-padding-top": "4px", + "space-action-card-horizontal": "20px", + "space-action-card-vertical": "16px", "space-alert-action-left": "12px", "space-alert-horizontal": "16px", "space-alert-message-right": "4px", @@ -3906,6 +4011,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "border-link-focus-ring-shadow-spread": "2px", "border-panel-header-width": "1px", "border-panel-top-width": "1px", + "border-radius-action-card": "16px", "border-radius-alert": "12px", "border-radius-badge": "4px", "border-radius-button": "20px", @@ -3928,6 +4034,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "border-radius-token": "8px", "border-radius-tutorial-panel-item": "8px", "border-table-sticky-width": "1px", + "border-width-action-card-active": "1px", + "border-width-action-card-default": "1px", + "border-width-action-card-disabled": "1px", + "border-width-action-card-hover": "1px", "border-width-alert": "2px", "border-width-button": "2px", "border-width-card": "1px", @@ -3942,6 +4052,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "border-width-popover": "2px", "border-width-token": "2px", "color-aws-squid-ink": "#232f3e", + "color-background-action-card-active": "#d1f1ff", + "color-background-action-card-default": "#ffffff", + "color-background-action-card-disabled": "#ffffff", + "color-background-action-card-hover": "#f0fbff", "color-background-avatar-default": "#424650", "color-background-avatar-gen-ai": "radial-gradient(circle farthest-corner at top right, #b8e7ff 0%, #0099ff 25%, #5c7fff 40% , #8575ff 60%, #962eff 80%)", "color-background-badge-icon": "#db0000", @@ -4045,6 +4159,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "color-black": "#000000", "color-board-placeholder-active": "#ebebf0", "color-board-placeholder-hover": "#d1f1ff", + "color-border-action-card-active": "#002b66", + "color-border-action-card-default": "#006ce0", + "color-border-action-card-disabled": "#b4b4bb", + "color-border-action-card-hover": "#002b66", "color-border-button-normal-active": "#002b66", "color-border-button-normal-default": "#006ce0", "color-border-button-normal-disabled": "#b4b4bb", @@ -4284,6 +4402,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "color-grey-transparent": "rgba(15, 20, 26, 0.12)", "color-grey-transparent-heavy": "rgba(15, 20, 26, 0.12)", "color-grey-transparent-light": "rgba(15, 20, 26, 0.12)", + "color-icon-action-card-active": "#002b66", + "color-icon-action-card-default": "#006ce0", + "color-icon-action-card-disabled": "#b4b4bb", + "color-icon-action-card-hover": "#002b66", "color-info-1000": "#001129", "color-info-300": "#75cfff", "color-info-400": "#42b4ff", @@ -4336,6 +4458,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "color-success-500": "#2bb534", "color-success-600": "#00802f", "color-text-accent": "#006ce0", + "color-text-action-card-disabled": "#8c8c94", "color-text-avatar": "#ffffff", "color-text-body-default": "#0f141a", "color-text-body-secondary": "#424650", @@ -4562,6 +4685,9 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "size-table-selection-horizontal": "40px", "size-vertical-input": "28px", "size-vertical-panel-icon-offset": "13px", + "space-action-card-description-padding-top": "2px", + "space-action-card-horizontal": "20px", + "space-action-card-vertical": "12px", "space-alert-action-left": "12px", "space-alert-horizontal": "16px", "space-alert-message-right": "4px", @@ -4683,6 +4809,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "border-link-focus-ring-shadow-spread": "2px", "border-panel-header-width": "1px", "border-panel-top-width": "1px", + "border-radius-action-card": "16px", "border-radius-alert": "12px", "border-radius-badge": "4px", "border-radius-button": "20px", @@ -4705,6 +4832,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "border-radius-token": "8px", "border-radius-tutorial-panel-item": "8px", "border-table-sticky-width": "1px", + "border-width-action-card-active": "1px", + "border-width-action-card-default": "1px", + "border-width-action-card-disabled": "1px", + "border-width-action-card-hover": "1px", "border-width-alert": "2px", "border-width-button": "2px", "border-width-card": "1px", @@ -4719,6 +4850,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "border-width-popover": "2px", "border-width-token": "2px", "color-aws-squid-ink": "#232f3e", + "color-background-action-card-active": "#333843", + "color-background-action-card-default": "#161d26", + "color-background-action-card-disabled": "#161d26", + "color-background-action-card-hover": "#1b232d", "color-background-avatar-default": "#424650", "color-background-avatar-gen-ai": "radial-gradient(circle farthest-corner at top right, #b8e7ff 0%, #0099ff 25%, #5c7fff 40% , #8575ff 60%, #962eff 80%)", "color-background-badge-icon": "#ff7a7a", @@ -4822,6 +4957,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "color-black": "#000000", "color-board-placeholder-active": "#656871", "color-board-placeholder-hover": "#006ce0", + "color-border-action-card-active": "#75cfff", + "color-border-action-card-default": "#42b4ff", + "color-border-action-card-disabled": "#656871", + "color-border-action-card-hover": "#75cfff", "color-border-button-normal-active": "#75cfff", "color-border-button-normal-default": "#42b4ff", "color-border-button-normal-disabled": "#656871", @@ -5061,6 +5200,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "color-grey-transparent": "rgba(15, 20, 26, 1)", "color-grey-transparent-heavy": "rgba(15, 20, 26, 1)", "color-grey-transparent-light": "rgba(15, 20, 26, 1)", + "color-icon-action-card-active": "#75cfff", + "color-icon-action-card-default": "#42b4ff", + "color-icon-action-card-disabled": "#656871", + "color-icon-action-card-hover": "#75cfff", "color-info-1000": "#001129", "color-info-300": "#75cfff", "color-info-400": "#42b4ff", @@ -5113,6 +5256,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "color-success-500": "#2bb534", "color-success-600": "#00802f", "color-text-accent": "#42b4ff", + "color-text-action-card-disabled": "#8c8c94", "color-text-avatar": "#ffffff", "color-text-body-default": "#c6c6cd", "color-text-body-secondary": "#c6c6cd", @@ -5339,6 +5483,9 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "size-table-selection-horizontal": "40px", "size-vertical-input": "32px", "size-vertical-panel-icon-offset": "15px", + "space-action-card-description-padding-top": "4px", + "space-action-card-horizontal": "20px", + "space-action-card-vertical": "16px", "space-alert-action-left": "12px", "space-alert-horizontal": "16px", "space-alert-message-right": "4px", @@ -5460,6 +5607,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "border-link-focus-ring-shadow-spread": "2px", "border-panel-header-width": "1px", "border-panel-top-width": "1px", + "border-radius-action-card": "16px", "border-radius-alert": "12px", "border-radius-badge": "4px", "border-radius-button": "20px", @@ -5482,6 +5630,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "border-radius-token": "8px", "border-radius-tutorial-panel-item": "8px", "border-table-sticky-width": "1px", + "border-width-action-card-active": "1px", + "border-width-action-card-default": "1px", + "border-width-action-card-disabled": "1px", + "border-width-action-card-hover": "1px", "border-width-alert": "2px", "border-width-button": "2px", "border-width-card": "1px", @@ -5496,6 +5648,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "border-width-popover": "2px", "border-width-token": "2px", "color-aws-squid-ink": "#232f3e", + "color-background-action-card-active": "#333843", + "color-background-action-card-default": "#161d26", + "color-background-action-card-disabled": "#161d26", + "color-background-action-card-hover": "#1b232d", "color-background-avatar-default": "#424650", "color-background-avatar-gen-ai": "radial-gradient(circle farthest-corner at top right, #b8e7ff 0%, #0099ff 25%, #5c7fff 40% , #8575ff 60%, #962eff 80%)", "color-background-badge-icon": "#ff7a7a", @@ -5599,6 +5755,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "color-black": "#000000", "color-board-placeholder-active": "#656871", "color-board-placeholder-hover": "#006ce0", + "color-border-action-card-active": "#75cfff", + "color-border-action-card-default": "#42b4ff", + "color-border-action-card-disabled": "#656871", + "color-border-action-card-hover": "#75cfff", "color-border-button-normal-active": "#75cfff", "color-border-button-normal-default": "#42b4ff", "color-border-button-normal-disabled": "#656871", @@ -5838,6 +5998,10 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "color-grey-transparent": "rgba(15, 20, 26, 1)", "color-grey-transparent-heavy": "rgba(15, 20, 26, 1)", "color-grey-transparent-light": "rgba(15, 20, 26, 1)", + "color-icon-action-card-active": "#75cfff", + "color-icon-action-card-default": "#42b4ff", + "color-icon-action-card-disabled": "#656871", + "color-icon-action-card-hover": "#75cfff", "color-info-1000": "#001129", "color-info-300": "#75cfff", "color-info-400": "#42b4ff", @@ -5890,6 +6054,7 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "color-success-500": "#2bb534", "color-success-600": "#00802f", "color-text-accent": "#42b4ff", + "color-text-action-card-disabled": "#8c8c94", "color-text-avatar": "#ffffff", "color-text-body-default": "#c6c6cd", "color-text-body-secondary": "#c6c6cd", @@ -6116,6 +6281,9 @@ exports[`CSS Custom Properties match previous snapshot for mode "visual-refresh- "size-table-selection-horizontal": "40px", "size-vertical-input": "32px", "size-vertical-panel-icon-offset": "15px", + "space-action-card-description-padding-top": "4px", + "space-action-card-horizontal": "20px", + "space-action-card-vertical": "16px", "space-alert-action-left": "12px", "space-alert-horizontal": "16px", "space-alert-message-right": "4px", diff --git a/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap b/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap index 73af93d722..20b97974e3 100644 --- a/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap +++ b/src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap @@ -1,5 +1,406 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Components definition for action-card matches the snapshot: action-card 1`] = ` +{ + "dashCaseName": "action-card", + "events": [ + { + "cancelable": true, + "description": "Called when the user clicks on the action card.", + "name": "onClick", + }, + ], + "functions": [ + { + "description": "Sets focus on the action card.", + "name": "focus", + "parameters": [], + "returnType": "void", + }, + ], + "name": "ActionCard", + "properties": [ + { + "description": "Adds an aria-describedby reference for the action card.", + "name": "ariaDescribedby", + "optional": true, + "type": "string", + }, + { + "description": "Adds an aria-label to the action card.", + "name": "ariaLabel", + "optional": true, + "type": "string", + }, + { + "deprecatedTag": "Custom CSS is not supported. For testing and other use cases, use [data attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes).", + "description": "Adds the specified classes to the root element of the component.", + "name": "className", + "optional": true, + "type": "string", + }, + { + "defaultValue": "false", + "description": "Removes the default padding from the content area.", + "name": "disableContentPaddings", + "optional": true, + "type": "boolean", + }, + { + "defaultValue": "false", + "description": "Determines whether the action card is disabled.", + "name": "disabled", + "optional": true, + "type": "boolean", + }, + { + "defaultValue": "false", + "description": "Removes the default padding from the header area.", + "name": "disableHeaderPaddings", + "optional": true, + "type": "boolean", + }, + { + "defaultValue": "'top'", + "description": "Specifies the vertical alignment of the icon.", + "inlineType": { + "name": "ActionCardProps.IconVerticalAlignment", + "type": "union", + "values": [ + "center", + "top", + ], + }, + "name": "iconVerticalAlignment", + "optional": true, + "type": "string", + }, + { + "deprecatedTag": "The usage of the \`id\` attribute is reserved for internal use cases. For testing and other use cases, +use [data attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes). If you must +use the \`id\` attribute, consider setting it on a parent element instead.", + "description": "Adds the specified ID to the root element of the component.", + "name": "id", + "optional": true, + "type": "string", + }, + { + "description": "Attributes to add to the native button element. +Some attributes will be automatically combined with internal attribute values: +- \`className\` will be appended. +- Event handlers will be chained, unless the default is prevented. + +We do not support using this attribute to apply custom styling.", + "inlineType": { + "name": "Omit, "children"> & Record<\`data-\${string}\`, string>", + "type": "union", + "values": [ + "Omit, "children">", + "Record<\`data-\${string}\`, string>", + ], + }, + "name": "nativeButtonAttributes", + "optional": true, + "systemTags": [ + "core", + ], + "type": "Omit, "children"> & Record<\`data-\${string}\`, string>", + }, + { + "description": "An object containing CSS properties to customize the action card's visual appearance. +Refer to the [style](/components/action-card/?tabId=style) tab for more details.", + "inlineType": { + "name": "ActionCardProps.Style", + "properties": [ + { + "inlineType": { + "name": "{ paddingBlock?: string | undefined; paddingInline?: string | undefined; }", + "properties": [ + { + "name": "paddingBlock", + "optional": true, + "type": "string", + }, + { + "name": "paddingInline", + "optional": true, + "type": "string", + }, + ], + "type": "object", + }, + "name": "content", + "optional": true, + "type": "{ paddingBlock?: string | undefined; paddingInline?: string | undefined; }", + }, + { + "inlineType": { + "name": "{ paddingBlock?: string | undefined; paddingInline?: string | undefined; }", + "properties": [ + { + "name": "paddingBlock", + "optional": true, + "type": "string", + }, + { + "name": "paddingInline", + "optional": true, + "type": "string", + }, + ], + "type": "object", + }, + "name": "header", + "optional": true, + "type": "{ paddingBlock?: string | undefined; paddingInline?: string | undefined; }", + }, + { + "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", + }, + ], + "type": "object", + }, + "name": "background", + "optional": true, + "type": "{ default?: string | undefined; hover?: string | undefined; active?: string | undefined; disabled?: 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", + }, + ], + "type": "object", + }, + "name": "borderColor", + "optional": true, + "type": "{ default?: string | undefined; hover?: string | undefined; active?: string | undefined; disabled?: 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", + }, + ], + "type": "object", + }, + "name": "borderRadius", + "optional": true, + "type": "{ default?: string | undefined; hover?: string | undefined; active?: string | undefined; disabled?: 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", + }, + ], + "type": "object", + }, + "name": "borderWidth", + "optional": true, + "type": "{ default?: string | undefined; hover?: string | undefined; active?: string | undefined; disabled?: 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", + }, + ], + "type": "object", + }, + "name": "boxShadow", + "optional": true, + "type": "{ default?: string | undefined; hover?: string | undefined; active?: string | undefined; disabled?: 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; }", + }, + ], + "type": "object", + }, + "name": "root", + "optional": true, + "type": "{ background?: { default?: string | undefined; hover?: string | undefined; active?: string | undefined; disabled?: string | undefined; } | undefined; borderColor?: { default?: string | undefined; hover?: string | undefined; active?: string | undefined; disabled?: string | undefined; } | undefined; borderRadius?: { ....", + }, + ], + "type": "object", + }, + "name": "style", + "optional": true, + "systemTags": [ + "core", + ], + "type": "ActionCardProps.Style", + }, + { + "defaultValue": "'default'", + "description": "Specifies the visual variant of the card, which controls the border radius and padding. + +- \`default\` - Uses container-level border radius and padding (larger). +- \`embedded\` - Uses compact border radius and padding (smaller).", + "inlineType": { + "name": "ActionCardProps.Variant", + "type": "union", + "values": [ + "default", + "embedded", + ], + }, + "name": "variant", + "optional": true, + "type": "string", + }, + ], + "regions": [ + { + "description": "The main content of the action card.", + "isDefault": true, + "name": "children", + }, + { + "description": "The description content displayed below the header.", + "isDefault": false, + "name": "description", + }, + { + "description": "The header content displayed at the top of the action card.", + "isDefault": false, + "name": "header", + }, + { + "description": "Displays an icon next to the content. You can use the \`iconPosition\` and \`iconVerticalAlignment\` properties to position the icon.", + "isDefault": false, + "name": "icon", + }, + ], + "releaseStatus": "stable", +} +`; + exports[`Components definition for alert matches the snapshot: alert 1`] = ` { "dashCaseName": "alert", @@ -17174,9 +17575,9 @@ The default is \`secondary\`, except inside the following components where it de "name": "LinkProps.Variant", "type": "union", "values": [ + "secondary", "info", "primary", - "secondary", "awsui-value-large", ], }, @@ -31898,6 +32299,76 @@ Use this if you need to wait for a response from the server before the user can exports[`Test-utils dom definitions match the snapshot 1`] = ` { "classes": [ + { + "methods": [ + { + "description": "Returns the content element of the action card.", + "name": "findContent", + "parameters": [], + "returnType": { + "isNullable": true, + "name": "ElementWrapper", + "typeArguments": [ + { + "name": "HTMLElement", + }, + ], + }, + }, + { + "description": "Returns the description element of the action card.", + "name": "findDescription", + "parameters": [], + "returnType": { + "isNullable": true, + "name": "ElementWrapper", + "typeArguments": [ + { + "name": "HTMLElement", + }, + ], + }, + }, + { + "description": "Returns the header element of the action card.", + "name": "findHeader", + "parameters": [], + "returnType": { + "isNullable": true, + "name": "ElementWrapper", + "typeArguments": [ + { + "name": "HTMLElement", + }, + ], + }, + }, + { + "description": "Finds the icon slot of the action card.", + "name": "findIcon", + "parameters": [], + "returnType": { + "isNullable": true, + "name": "ElementWrapper", + "typeArguments": [ + { + "name": "HTMLElement", + }, + ], + }, + }, + { + "description": "Returns whether the action card is disabled.", + "name": "isDisabled", + "parameters": [], + "returnType": { + "isNullable": false, + "name": "boolean", + }, + }, + ], + "name": "ActionCardWrapper", + }, { "methods": [ { @@ -43805,6 +44276,47 @@ Supported options: exports[`Test-utils selectors definitions match the snapshot 1`] = ` { "classes": [ + { + "methods": [ + { + "description": "Returns the content element of the action card.", + "name": "findContent", + "parameters": [], + "returnType": { + "isNullable": false, + "name": "ElementWrapper", + }, + }, + { + "description": "Returns the description element of the action card.", + "name": "findDescription", + "parameters": [], + "returnType": { + "isNullable": false, + "name": "ElementWrapper", + }, + }, + { + "description": "Returns the header element of the action card.", + "name": "findHeader", + "parameters": [], + "returnType": { + "isNullable": false, + "name": "ElementWrapper", + }, + }, + { + "description": "Finds the icon slot of the action card.", + "name": "findIcon", + "parameters": [], + "returnType": { + "isNullable": false, + "name": "ElementWrapper", + }, + }, + ], + "name": "ActionCardWrapper", + }, { "methods": [ { diff --git a/src/__tests__/snapshot-tests/__snapshots__/test-utils-selectors.test.tsx.snap b/src/__tests__/snapshot-tests/__snapshots__/test-utils-selectors.test.tsx.snap index ff0f1a847c..029efa0806 100644 --- a/src/__tests__/snapshot-tests/__snapshots__/test-utils-selectors.test.tsx.snap +++ b/src/__tests__/snapshot-tests/__snapshots__/test-utils-selectors.test.tsx.snap @@ -2,6 +2,13 @@ exports[`test-utils selectors 1`] = ` { + "action-card": [ + "awsui_body_16248", + "awsui_description_16248", + "awsui_header-inner_16248", + "awsui_icon_16248", + "awsui_root_16248", + ], "alert": [ "awsui_action-button_mx3cw", "awsui_action-slot_mx3cw", diff --git a/src/__tests__/snapshot-tests/__snapshots__/test-utils-wrappers.test.tsx.snap b/src/__tests__/snapshot-tests/__snapshots__/test-utils-wrappers.test.tsx.snap index c3891760d6..d98cd076ae 100644 --- a/src/__tests__/snapshot-tests/__snapshots__/test-utils-wrappers.test.tsx.snap +++ b/src/__tests__/snapshot-tests/__snapshots__/test-utils-wrappers.test.tsx.snap @@ -9,6 +9,7 @@ import { appendSelector } from '@cloudscape-design/test-utils-core/utils'; export { ElementWrapper }; +import ActionCardWrapper from './action-card'; import AlertWrapper from './alert'; import AnchorNavigationWrapper from './anchor-navigation'; import AnnotationWrapper from './annotation'; @@ -101,6 +102,7 @@ import TutorialPanelWrapper from './tutorial-panel'; import WizardWrapper from './wizard'; +export { ActionCardWrapper }; export { AlertWrapper }; export { AnchorNavigationWrapper }; export { AnnotationWrapper }; @@ -195,6 +197,34 @@ export { WizardWrapper }; declare module '@cloudscape-design/test-utils-core/dist/dom' { interface ElementWrapper { +/** + * Returns the wrapper of the first ActionCard that matches the specified CSS selector. + * If no CSS selector is specified, returns the wrapper of the first ActionCard. + * If no matching ActionCard is found, returns \`null\`. + * + * @param {string} [selector] CSS Selector + * @returns {ActionCardWrapper | null} + */ +findActionCard(selector?: string): ActionCardWrapper | null; + +/** + * Returns an array of ActionCard wrapper that matches the specified CSS selector. + * If no CSS selector is specified, returns all of the ActionCards inside the current wrapper. + * If no matching ActionCard is found, returns an empty array. + * + * @param {string} [selector] CSS Selector + * @returns {Array} + */ +findAllActionCards(selector?: string): Array; + +/** + * Returns the wrapper of the closest parent ActionCard for the current element, + * or the element itself if it is an instance of ActionCard. + * If no ActionCard is found, returns \`null\`. + * + * @returns {ActionCardWrapper | null} + */ +findClosestActionCard(): ActionCardWrapper | null; /** * Returns the wrapper of the first Alert that matches the specified CSS selector. * If no CSS selector is specified, returns the wrapper of the first Alert. @@ -2719,6 +2749,19 @@ findClosestWizard(): WizardWrapper | null; } +ElementWrapper.prototype.findActionCard = function(selector) { + let rootSelector = \`.\${ActionCardWrapper.rootSelector}\`; + if("legacyRootSelector" in ActionCardWrapper && ActionCardWrapper.legacyRootSelector){ + rootSelector = \`:is(.\${ActionCardWrapper.rootSelector}, .\${ActionCardWrapper.legacyRootSelector})\`; + } + // casting to 'any' is needed to avoid this issue with generics + // https://github.com/microsoft/TypeScript/issues/29132 + return (this as any).findComponent(selector ? appendSelector(selector, rootSelector) : rootSelector, ActionCardWrapper); +}; + +ElementWrapper.prototype.findAllActionCards = function(selector) { + return this.findAllComponents(ActionCardWrapper, selector); +}; ElementWrapper.prototype.findAlert = function(selector) { let rootSelector = \`.\${AlertWrapper.rootSelector}\`; if("legacyRootSelector" in AlertWrapper && AlertWrapper.legacyRootSelector){ @@ -3890,6 +3933,11 @@ ElementWrapper.prototype.findAllWizards = function(selector) { return this.findAllComponents(WizardWrapper, selector); }; +ElementWrapper.prototype.findClosestActionCard = function() { + // casting to 'any' is needed to avoid this issue with generics + // https://github.com/microsoft/TypeScript/issues/29132 + return (this as any).findClosestComponent(ActionCardWrapper); +}; ElementWrapper.prototype.findClosestAlert = function() { // casting to 'any' is needed to avoid this issue with generics // https://github.com/microsoft/TypeScript/issues/29132 @@ -4359,6 +4407,7 @@ import { appendSelector } from '@cloudscape-design/test-utils-core/utils'; export { ElementWrapper }; +import ActionCardWrapper from './action-card'; import AlertWrapper from './alert'; import AnchorNavigationWrapper from './anchor-navigation'; import AnnotationWrapper from './annotation'; @@ -4451,6 +4500,7 @@ import TutorialPanelWrapper from './tutorial-panel'; import WizardWrapper from './wizard'; +export { ActionCardWrapper }; export { AlertWrapper }; export { AnchorNavigationWrapper }; export { AnnotationWrapper }; @@ -4545,6 +4595,23 @@ export { WizardWrapper }; declare module '@cloudscape-design/test-utils-core/dist/selectors' { interface ElementWrapper { +/** + * Returns a wrapper that matches the ActionCards with the specified CSS selector. + * If no CSS selector is specified, returns a wrapper that matches ActionCards. + * + * @param {string} [selector] CSS Selector + * @returns {ActionCardWrapper} + */ +findActionCard(selector?: string): ActionCardWrapper; + +/** + * Returns a multi-element wrapper that matches ActionCards with the specified CSS selector. + * If no CSS selector is specified, returns a multi-element wrapper that matches ActionCards. + * + * @param {string} [selector] CSS Selector + * @returns {MultiElementWrapper} + */ +findAllActionCards(selector?: string): MultiElementWrapper; /** * Returns a wrapper that matches the Alerts with the specified CSS selector. * If no CSS selector is specified, returns a wrapper that matches Alerts. @@ -6079,6 +6146,19 @@ findAllWizards(selector?: string): MultiElementWrapper; } +ElementWrapper.prototype.findActionCard = function(selector) { + let rootSelector = \`.\${ActionCardWrapper.rootSelector}\`; + if("legacyRootSelector" in ActionCardWrapper && ActionCardWrapper.legacyRootSelector){ + rootSelector = \`:is(.\${ActionCardWrapper.rootSelector}, .\${ActionCardWrapper.legacyRootSelector})\`; + } + // casting to 'any' is needed to avoid this issue with generics + // https://github.com/microsoft/TypeScript/issues/29132 + return (this as any).findComponent(selector ? appendSelector(selector, rootSelector) : rootSelector, ActionCardWrapper); +}; + +ElementWrapper.prototype.findAllActionCards = function(selector) { + return this.findAllComponents(ActionCardWrapper, selector); +}; ElementWrapper.prototype.findAlert = function(selector) { let rootSelector = \`.\${AlertWrapper.rootSelector}\`; if("legacyRootSelector" in AlertWrapper && AlertWrapper.legacyRootSelector){ diff --git a/src/dropdown/__tests__/__snapshots__/styles.test.tsx.snap b/src/dropdown/__tests__/__snapshots__/styles.test.tsx.snap index e365408648..229889d3f7 100644 --- a/src/dropdown/__tests__/__snapshots__/styles.test.tsx.snap +++ b/src/dropdown/__tests__/__snapshots__/styles.test.tsx.snap @@ -6,9 +6,9 @@ exports[`getDropdownStyles handles all possible style configurations 2`] = `unde exports[`getDropdownStyles handles all possible style configurations 3`] = ` { - "--awsui-dropdown-content-border-color-6b9ypa": "rgb(0, 0, 0)", - "--awsui-dropdown-content-border-radius-6b9ypa": "8px", - "--awsui-dropdown-content-border-width-6b9ypa": "2px", + "--awsui-dropdown-content-border-color-8rcw2s": "rgb(0, 0, 0)", + "--awsui-dropdown-content-border-radius-8rcw2s": "8px", + "--awsui-dropdown-content-border-width-8rcw2s": "2px", "background": "rgb(255, 255, 255)", } `; diff --git a/src/input/__tests__/__snapshots__/styles.test.tsx.snap b/src/input/__tests__/__snapshots__/styles.test.tsx.snap index 85c1053781..f6150dca21 100644 --- a/src/input/__tests__/__snapshots__/styles.test.tsx.snap +++ b/src/input/__tests__/__snapshots__/styles.test.tsx.snap @@ -2,30 +2,30 @@ exports[`getInputStyles handles all possible style configurations 1`] = ` { - "--awsui-style-background-default-6b9ypa": undefined, - "--awsui-style-background-disabled-6b9ypa": undefined, - "--awsui-style-background-focus-6b9ypa": undefined, - "--awsui-style-background-hover-6b9ypa": undefined, - "--awsui-style-background-readonly-6b9ypa": undefined, - "--awsui-style-border-color-default-6b9ypa": undefined, - "--awsui-style-border-color-disabled-6b9ypa": undefined, - "--awsui-style-border-color-focus-6b9ypa": undefined, - "--awsui-style-border-color-hover-6b9ypa": undefined, - "--awsui-style-border-color-readonly-6b9ypa": undefined, - "--awsui-style-box-shadow-default-6b9ypa": undefined, - "--awsui-style-box-shadow-disabled-6b9ypa": undefined, - "--awsui-style-box-shadow-focus-6b9ypa": undefined, - "--awsui-style-box-shadow-hover-6b9ypa": undefined, - "--awsui-style-box-shadow-readonly-6b9ypa": undefined, - "--awsui-style-color-default-6b9ypa": undefined, - "--awsui-style-color-disabled-6b9ypa": undefined, - "--awsui-style-color-focus-6b9ypa": undefined, - "--awsui-style-color-hover-6b9ypa": undefined, - "--awsui-style-color-readonly-6b9ypa": undefined, - "--awsui-style-placeholder-color-6b9ypa": undefined, - "--awsui-style-placeholder-font-size-6b9ypa": undefined, - "--awsui-style-placeholder-font-style-6b9ypa": undefined, - "--awsui-style-placeholder-font-weight-6b9ypa": undefined, + "--awsui-style-background-default-8rcw2s": undefined, + "--awsui-style-background-disabled-8rcw2s": undefined, + "--awsui-style-background-focus-8rcw2s": undefined, + "--awsui-style-background-hover-8rcw2s": undefined, + "--awsui-style-background-readonly-8rcw2s": undefined, + "--awsui-style-border-color-default-8rcw2s": undefined, + "--awsui-style-border-color-disabled-8rcw2s": undefined, + "--awsui-style-border-color-focus-8rcw2s": undefined, + "--awsui-style-border-color-hover-8rcw2s": undefined, + "--awsui-style-border-color-readonly-8rcw2s": undefined, + "--awsui-style-box-shadow-default-8rcw2s": undefined, + "--awsui-style-box-shadow-disabled-8rcw2s": undefined, + "--awsui-style-box-shadow-focus-8rcw2s": undefined, + "--awsui-style-box-shadow-hover-8rcw2s": undefined, + "--awsui-style-box-shadow-readonly-8rcw2s": undefined, + "--awsui-style-color-default-8rcw2s": undefined, + "--awsui-style-color-disabled-8rcw2s": undefined, + "--awsui-style-color-focus-8rcw2s": undefined, + "--awsui-style-color-hover-8rcw2s": undefined, + "--awsui-style-color-readonly-8rcw2s": undefined, + "--awsui-style-placeholder-color-8rcw2s": undefined, + "--awsui-style-placeholder-font-size-8rcw2s": undefined, + "--awsui-style-placeholder-font-style-8rcw2s": undefined, + "--awsui-style-placeholder-font-weight-8rcw2s": undefined, "borderRadius": undefined, "borderWidth": undefined, "fontSize": undefined, @@ -37,30 +37,30 @@ exports[`getInputStyles handles all possible style configurations 1`] = ` exports[`getInputStyles handles all possible style configurations 2`] = ` { - "--awsui-style-background-default-6b9ypa": undefined, - "--awsui-style-background-disabled-6b9ypa": undefined, - "--awsui-style-background-focus-6b9ypa": undefined, - "--awsui-style-background-hover-6b9ypa": undefined, - "--awsui-style-background-readonly-6b9ypa": undefined, - "--awsui-style-border-color-default-6b9ypa": undefined, - "--awsui-style-border-color-disabled-6b9ypa": undefined, - "--awsui-style-border-color-focus-6b9ypa": undefined, - "--awsui-style-border-color-hover-6b9ypa": undefined, - "--awsui-style-border-color-readonly-6b9ypa": undefined, - "--awsui-style-box-shadow-default-6b9ypa": undefined, - "--awsui-style-box-shadow-disabled-6b9ypa": undefined, - "--awsui-style-box-shadow-focus-6b9ypa": undefined, - "--awsui-style-box-shadow-hover-6b9ypa": undefined, - "--awsui-style-box-shadow-readonly-6b9ypa": undefined, - "--awsui-style-color-default-6b9ypa": undefined, - "--awsui-style-color-disabled-6b9ypa": undefined, - "--awsui-style-color-focus-6b9ypa": undefined, - "--awsui-style-color-hover-6b9ypa": undefined, - "--awsui-style-color-readonly-6b9ypa": undefined, - "--awsui-style-placeholder-color-6b9ypa": undefined, - "--awsui-style-placeholder-font-size-6b9ypa": undefined, - "--awsui-style-placeholder-font-style-6b9ypa": undefined, - "--awsui-style-placeholder-font-weight-6b9ypa": undefined, + "--awsui-style-background-default-8rcw2s": undefined, + "--awsui-style-background-disabled-8rcw2s": undefined, + "--awsui-style-background-focus-8rcw2s": undefined, + "--awsui-style-background-hover-8rcw2s": undefined, + "--awsui-style-background-readonly-8rcw2s": undefined, + "--awsui-style-border-color-default-8rcw2s": undefined, + "--awsui-style-border-color-disabled-8rcw2s": undefined, + "--awsui-style-border-color-focus-8rcw2s": undefined, + "--awsui-style-border-color-hover-8rcw2s": undefined, + "--awsui-style-border-color-readonly-8rcw2s": undefined, + "--awsui-style-box-shadow-default-8rcw2s": undefined, + "--awsui-style-box-shadow-disabled-8rcw2s": undefined, + "--awsui-style-box-shadow-focus-8rcw2s": undefined, + "--awsui-style-box-shadow-hover-8rcw2s": undefined, + "--awsui-style-box-shadow-readonly-8rcw2s": undefined, + "--awsui-style-color-default-8rcw2s": undefined, + "--awsui-style-color-disabled-8rcw2s": undefined, + "--awsui-style-color-focus-8rcw2s": undefined, + "--awsui-style-color-hover-8rcw2s": undefined, + "--awsui-style-color-readonly-8rcw2s": undefined, + "--awsui-style-placeholder-color-8rcw2s": undefined, + "--awsui-style-placeholder-font-size-8rcw2s": undefined, + "--awsui-style-placeholder-font-style-8rcw2s": undefined, + "--awsui-style-placeholder-font-weight-8rcw2s": undefined, "borderRadius": undefined, "borderWidth": undefined, "fontSize": undefined, @@ -72,30 +72,30 @@ exports[`getInputStyles handles all possible style configurations 2`] = ` exports[`getInputStyles handles all possible style configurations 3`] = ` { - "--awsui-style-background-default-6b9ypa": "#ffffff", - "--awsui-style-background-disabled-6b9ypa": "#f0f0f0", - "--awsui-style-background-focus-6b9ypa": "#ffffff", - "--awsui-style-background-hover-6b9ypa": "#fafafa", - "--awsui-style-background-readonly-6b9ypa": "#ffffff", - "--awsui-style-border-color-default-6b9ypa": "#cccccc", - "--awsui-style-border-color-disabled-6b9ypa": "#e0e0e0", - "--awsui-style-border-color-focus-6b9ypa": "#0073bb", - "--awsui-style-border-color-hover-6b9ypa": "#999999", - "--awsui-style-border-color-readonly-6b9ypa": "#e0e0e0", - "--awsui-style-box-shadow-default-6b9ypa": "none", - "--awsui-style-box-shadow-disabled-6b9ypa": "none", - "--awsui-style-box-shadow-focus-6b9ypa": "0 0 0 2px #0073bb", - "--awsui-style-box-shadow-hover-6b9ypa": "0 1px 2px rgba(0,0,0,0.1)", - "--awsui-style-box-shadow-readonly-6b9ypa": "none", - "--awsui-style-color-default-6b9ypa": "#000000", - "--awsui-style-color-disabled-6b9ypa": "#999999", - "--awsui-style-color-focus-6b9ypa": "#000000", - "--awsui-style-color-hover-6b9ypa": "#000000", - "--awsui-style-color-readonly-6b9ypa": "#000000", - "--awsui-style-placeholder-color-6b9ypa": "#999999", - "--awsui-style-placeholder-font-size-6b9ypa": "14px", - "--awsui-style-placeholder-font-style-6b9ypa": "italic", - "--awsui-style-placeholder-font-weight-6b9ypa": "400", + "--awsui-style-background-default-8rcw2s": "#ffffff", + "--awsui-style-background-disabled-8rcw2s": "#f0f0f0", + "--awsui-style-background-focus-8rcw2s": "#ffffff", + "--awsui-style-background-hover-8rcw2s": "#fafafa", + "--awsui-style-background-readonly-8rcw2s": "#ffffff", + "--awsui-style-border-color-default-8rcw2s": "#cccccc", + "--awsui-style-border-color-disabled-8rcw2s": "#e0e0e0", + "--awsui-style-border-color-focus-8rcw2s": "#0073bb", + "--awsui-style-border-color-hover-8rcw2s": "#999999", + "--awsui-style-border-color-readonly-8rcw2s": "#e0e0e0", + "--awsui-style-box-shadow-default-8rcw2s": "none", + "--awsui-style-box-shadow-disabled-8rcw2s": "none", + "--awsui-style-box-shadow-focus-8rcw2s": "0 0 0 2px #0073bb", + "--awsui-style-box-shadow-hover-8rcw2s": "0 1px 2px rgba(0,0,0,0.1)", + "--awsui-style-box-shadow-readonly-8rcw2s": "none", + "--awsui-style-color-default-8rcw2s": "#000000", + "--awsui-style-color-disabled-8rcw2s": "#999999", + "--awsui-style-color-focus-8rcw2s": "#000000", + "--awsui-style-color-hover-8rcw2s": "#000000", + "--awsui-style-color-readonly-8rcw2s": "#000000", + "--awsui-style-placeholder-color-8rcw2s": "#999999", + "--awsui-style-placeholder-font-size-8rcw2s": "14px", + "--awsui-style-placeholder-font-style-8rcw2s": "italic", + "--awsui-style-placeholder-font-weight-8rcw2s": "400", "borderRadius": "4px", "borderWidth": "1px", "fontSize": "14px", diff --git a/src/item-card/__tests__/__snapshots__/styles.test.tsx.snap b/src/item-card/__tests__/__snapshots__/styles.test.tsx.snap index ecc1b65139..d58d1db72f 100644 --- a/src/item-card/__tests__/__snapshots__/styles.test.tsx.snap +++ b/src/item-card/__tests__/__snapshots__/styles.test.tsx.snap @@ -42,11 +42,11 @@ exports[`getRootStyles handles all possible style configurations 2`] = `{}`; exports[`getRootStyles handles all possible style configurations 3`] = ` { - "--awsui-style-item-card-background-default-6b9ypa": "#ffffff", - "--awsui-style-item-card-border-color-default-6b9ypa": "#e0e0e0", - "--awsui-style-item-card-border-radius-6b9ypa": "8px", - "--awsui-style-item-card-border-width-default-6b9ypa": "1px", - "--awsui-style-item-card-box-shadow-default-6b9ypa": "0 1px 3px rgba(0,0,0,0.1)", + "--awsui-style-item-card-background-default-8rcw2s": "#ffffff", + "--awsui-style-item-card-border-color-default-8rcw2s": "#e0e0e0", + "--awsui-style-item-card-border-radius-8rcw2s": "8px", + "--awsui-style-item-card-border-width-default-8rcw2s": "1px", + "--awsui-style-item-card-box-shadow-default-8rcw2s": "0 1px 3px rgba(0,0,0,0.1)", "borderRadius": "8px", } `; diff --git a/src/segmented-control/__tests__/__snapshots__/styles.test.tsx.snap b/src/segmented-control/__tests__/__snapshots__/styles.test.tsx.snap index 9cfed12417..41cff09797 100644 --- a/src/segmented-control/__tests__/__snapshots__/styles.test.tsx.snap +++ b/src/segmented-control/__tests__/__snapshots__/styles.test.tsx.snap @@ -20,17 +20,17 @@ exports[`getSegmentedControlRootStyles handles all possible style configurations exports[`getSegmentedControlSegmentStyles handles all possible style configurations 1`] = ` { - "--awsui-style-background-active-6b9ypa": undefined, - "--awsui-style-background-default-6b9ypa": undefined, - "--awsui-style-background-disabled-6b9ypa": undefined, - "--awsui-style-background-hover-6b9ypa": undefined, - "--awsui-style-color-active-6b9ypa": undefined, - "--awsui-style-color-default-6b9ypa": undefined, - "--awsui-style-color-disabled-6b9ypa": undefined, - "--awsui-style-color-hover-6b9ypa": undefined, - "--awsui-style-focus-ring-border-color-6b9ypa": undefined, - "--awsui-style-focus-ring-border-radius-6b9ypa": undefined, - "--awsui-style-focus-ring-border-width-6b9ypa": undefined, + "--awsui-style-background-active-8rcw2s": undefined, + "--awsui-style-background-default-8rcw2s": undefined, + "--awsui-style-background-disabled-8rcw2s": undefined, + "--awsui-style-background-hover-8rcw2s": undefined, + "--awsui-style-color-active-8rcw2s": undefined, + "--awsui-style-color-default-8rcw2s": undefined, + "--awsui-style-color-disabled-8rcw2s": undefined, + "--awsui-style-color-hover-8rcw2s": undefined, + "--awsui-style-focus-ring-border-color-8rcw2s": undefined, + "--awsui-style-focus-ring-border-radius-8rcw2s": undefined, + "--awsui-style-focus-ring-border-width-8rcw2s": undefined, "borderRadius": undefined, "fontSize": undefined, "paddingBlock": undefined, @@ -40,17 +40,17 @@ exports[`getSegmentedControlSegmentStyles handles all possible style configurati exports[`getSegmentedControlSegmentStyles handles all possible style configurations 2`] = ` { - "--awsui-style-background-active-6b9ypa": undefined, - "--awsui-style-background-default-6b9ypa": undefined, - "--awsui-style-background-disabled-6b9ypa": undefined, - "--awsui-style-background-hover-6b9ypa": undefined, - "--awsui-style-color-active-6b9ypa": undefined, - "--awsui-style-color-default-6b9ypa": undefined, - "--awsui-style-color-disabled-6b9ypa": undefined, - "--awsui-style-color-hover-6b9ypa": undefined, - "--awsui-style-focus-ring-border-color-6b9ypa": undefined, - "--awsui-style-focus-ring-border-radius-6b9ypa": undefined, - "--awsui-style-focus-ring-border-width-6b9ypa": undefined, + "--awsui-style-background-active-8rcw2s": undefined, + "--awsui-style-background-default-8rcw2s": undefined, + "--awsui-style-background-disabled-8rcw2s": undefined, + "--awsui-style-background-hover-8rcw2s": undefined, + "--awsui-style-color-active-8rcw2s": undefined, + "--awsui-style-color-default-8rcw2s": undefined, + "--awsui-style-color-disabled-8rcw2s": undefined, + "--awsui-style-color-hover-8rcw2s": undefined, + "--awsui-style-focus-ring-border-color-8rcw2s": undefined, + "--awsui-style-focus-ring-border-radius-8rcw2s": undefined, + "--awsui-style-focus-ring-border-width-8rcw2s": undefined, "borderRadius": undefined, "fontSize": undefined, "paddingBlock": undefined, @@ -60,17 +60,17 @@ exports[`getSegmentedControlSegmentStyles handles all possible style configurati exports[`getSegmentedControlSegmentStyles handles all possible style configurations 3`] = ` { - "--awsui-style-background-active-6b9ypa": "#0073bb", - "--awsui-style-background-default-6b9ypa": "#ffffff", - "--awsui-style-background-disabled-6b9ypa": "#f0f0f0", - "--awsui-style-background-hover-6b9ypa": "#fafafa", - "--awsui-style-color-active-6b9ypa": "#ffffff", - "--awsui-style-color-default-6b9ypa": "#000000", - "--awsui-style-color-disabled-6b9ypa": "#999999", - "--awsui-style-color-hover-6b9ypa": "#000000", - "--awsui-style-focus-ring-border-color-6b9ypa": "#0073bb", - "--awsui-style-focus-ring-border-radius-6b9ypa": "8px", - "--awsui-style-focus-ring-border-width-6b9ypa": "2px", + "--awsui-style-background-active-8rcw2s": "#0073bb", + "--awsui-style-background-default-8rcw2s": "#ffffff", + "--awsui-style-background-disabled-8rcw2s": "#f0f0f0", + "--awsui-style-background-hover-8rcw2s": "#fafafa", + "--awsui-style-color-active-8rcw2s": "#ffffff", + "--awsui-style-color-default-8rcw2s": "#000000", + "--awsui-style-color-disabled-8rcw2s": "#999999", + "--awsui-style-color-hover-8rcw2s": "#000000", + "--awsui-style-focus-ring-border-color-8rcw2s": "#0073bb", + "--awsui-style-focus-ring-border-radius-8rcw2s": "8px", + "--awsui-style-focus-ring-border-width-8rcw2s": "2px", "borderRadius": "6px", "fontSize": "14px", "paddingBlock": "8px", diff --git a/src/slider/__tests__/__snapshots__/styles.test.tsx.snap b/src/slider/__tests__/__snapshots__/styles.test.tsx.snap index 2ee8224105..9b0bf16287 100644 --- a/src/slider/__tests__/__snapshots__/styles.test.tsx.snap +++ b/src/slider/__tests__/__snapshots__/styles.test.tsx.snap @@ -2,36 +2,36 @@ exports[`getSliderStyles handles all possible style configurations 1`] = ` { - "--awsui-style-slider-handle-background-active-6b9ypa": undefined, - "--awsui-style-slider-handle-background-default-6b9ypa": undefined, - "--awsui-style-slider-handle-background-hover-6b9ypa": undefined, - "--awsui-style-slider-handle-border-radius-6b9ypa": undefined, - "--awsui-style-slider-range-background-active-6b9ypa": undefined, - "--awsui-style-slider-range-background-default-6b9ypa": undefined, - "--awsui-style-slider-track-background-color-6b9ypa": undefined, + "--awsui-style-slider-handle-background-active-8rcw2s": undefined, + "--awsui-style-slider-handle-background-default-8rcw2s": undefined, + "--awsui-style-slider-handle-background-hover-8rcw2s": undefined, + "--awsui-style-slider-handle-border-radius-8rcw2s": undefined, + "--awsui-style-slider-range-background-active-8rcw2s": undefined, + "--awsui-style-slider-range-background-default-8rcw2s": undefined, + "--awsui-style-slider-track-background-color-8rcw2s": undefined, } `; exports[`getSliderStyles handles all possible style configurations 2`] = ` { - "--awsui-style-slider-handle-background-active-6b9ypa": undefined, - "--awsui-style-slider-handle-background-default-6b9ypa": undefined, - "--awsui-style-slider-handle-background-hover-6b9ypa": undefined, - "--awsui-style-slider-handle-border-radius-6b9ypa": undefined, - "--awsui-style-slider-range-background-active-6b9ypa": undefined, - "--awsui-style-slider-range-background-default-6b9ypa": undefined, - "--awsui-style-slider-track-background-color-6b9ypa": undefined, + "--awsui-style-slider-handle-background-active-8rcw2s": undefined, + "--awsui-style-slider-handle-background-default-8rcw2s": undefined, + "--awsui-style-slider-handle-background-hover-8rcw2s": undefined, + "--awsui-style-slider-handle-border-radius-8rcw2s": undefined, + "--awsui-style-slider-range-background-active-8rcw2s": undefined, + "--awsui-style-slider-range-background-default-8rcw2s": undefined, + "--awsui-style-slider-track-background-color-8rcw2s": undefined, } `; exports[`getSliderStyles handles all possible style configurations 3`] = ` { - "--awsui-style-slider-handle-background-active-6b9ypa": "#1d4ed8", - "--awsui-style-slider-handle-background-default-6b9ypa": "#3b82f6", - "--awsui-style-slider-handle-background-hover-6b9ypa": "#2563eb", - "--awsui-style-slider-handle-border-radius-6b9ypa": "50%", - "--awsui-style-slider-range-background-active-6b9ypa": "#2563eb", - "--awsui-style-slider-range-background-default-6b9ypa": "#3b82f6", - "--awsui-style-slider-track-background-color-6b9ypa": "#dbeafe", + "--awsui-style-slider-handle-background-active-8rcw2s": "#1d4ed8", + "--awsui-style-slider-handle-background-default-8rcw2s": "#3b82f6", + "--awsui-style-slider-handle-background-hover-8rcw2s": "#2563eb", + "--awsui-style-slider-handle-border-radius-8rcw2s": "50%", + "--awsui-style-slider-range-background-active-8rcw2s": "#2563eb", + "--awsui-style-slider-range-background-default-8rcw2s": "#3b82f6", + "--awsui-style-slider-track-background-color-8rcw2s": "#dbeafe", } `; diff --git a/src/tabs/__tests__/__snapshots__/styles.test.tsx.snap b/src/tabs/__tests__/__snapshots__/styles.test.tsx.snap index 369fed8a6e..9155efef08 100644 --- a/src/tabs/__tests__/__snapshots__/styles.test.tsx.snap +++ b/src/tabs/__tests__/__snapshots__/styles.test.tsx.snap @@ -2,21 +2,21 @@ exports[`getTabStyles transforms tab styles to CSS properties 1`] = ` { - "--awsui-style-background-active-6b9ypa": "#bfdbfe", - "--awsui-style-background-default-6b9ypa": "#dbeafe", - "--awsui-style-background-disabled-6b9ypa": "#f3f4f6", - "--awsui-style-background-hover-6b9ypa": "#eff6ff", - "--awsui-style-border-color-active-6b9ypa": "#1d4ed8", - "--awsui-style-border-color-default-6b9ypa": "#3b82f6", - "--awsui-style-border-color-disabled-6b9ypa": "#93c5fd", - "--awsui-style-border-color-hover-6b9ypa": "#2563eb", - "--awsui-style-color-active-6b9ypa": "#1e3a8a", - "--awsui-style-color-default-6b9ypa": "#1e40af", - "--awsui-style-color-disabled-6b9ypa": "#93c5fd", - "--awsui-style-color-hover-6b9ypa": "#1e40af", - "--awsui-style-focus-ring-border-color-6b9ypa": "#3b82f6", - "--awsui-style-focus-ring-border-radius-6b9ypa": "4px", - "--awsui-style-focus-ring-border-width-6b9ypa": "2px", + "--awsui-style-background-active-8rcw2s": "#bfdbfe", + "--awsui-style-background-default-8rcw2s": "#dbeafe", + "--awsui-style-background-disabled-8rcw2s": "#f3f4f6", + "--awsui-style-background-hover-8rcw2s": "#eff6ff", + "--awsui-style-border-color-active-8rcw2s": "#1d4ed8", + "--awsui-style-border-color-default-8rcw2s": "#3b82f6", + "--awsui-style-border-color-disabled-8rcw2s": "#93c5fd", + "--awsui-style-border-color-hover-8rcw2s": "#2563eb", + "--awsui-style-color-active-8rcw2s": "#1e3a8a", + "--awsui-style-color-default-8rcw2s": "#1e40af", + "--awsui-style-color-disabled-8rcw2s": "#93c5fd", + "--awsui-style-color-hover-8rcw2s": "#1e40af", + "--awsui-style-focus-ring-border-color-8rcw2s": "#3b82f6", + "--awsui-style-focus-ring-border-radius-8rcw2s": "4px", + "--awsui-style-focus-ring-border-width-8rcw2s": "2px", "borderRadius": "4px", "borderWidth": "2px", "fontSize": "16px", @@ -28,10 +28,10 @@ exports[`getTabStyles transforms tab styles to CSS properties 1`] = ` exports[`getTabStyles transforms tab styles to CSS properties 2`] = ` { - "--awsui-style-tabs-active-indicator-border-radius-6b9ypa": "2px", - "--awsui-style-tabs-active-indicator-color-6b9ypa": "#1d4ed8", - "--awsui-style-tabs-active-indicator-width-6b9ypa": "3px", - "--awsui-style-tabs-separator-color-6b9ypa": "#cbd5e1", - "--awsui-style-tabs-separator-width-6b9ypa": "2px", + "--awsui-style-tabs-active-indicator-border-radius-8rcw2s": "2px", + "--awsui-style-tabs-active-indicator-color-8rcw2s": "#1d4ed8", + "--awsui-style-tabs-active-indicator-width-8rcw2s": "3px", + "--awsui-style-tabs-separator-color-8rcw2s": "#cbd5e1", + "--awsui-style-tabs-separator-width-8rcw2s": "2px", } `; diff --git a/src/text-filter/__tests__/__snapshots__/styles.test.tsx.snap b/src/text-filter/__tests__/__snapshots__/styles.test.tsx.snap index daf5208fe6..c3349c5a82 100644 --- a/src/text-filter/__tests__/__snapshots__/styles.test.tsx.snap +++ b/src/text-filter/__tests__/__snapshots__/styles.test.tsx.snap @@ -2,30 +2,30 @@ exports[`getTextFilterStyles handles all possible style configurations 1`] = ` { - "--awsui-style-background-default-6b9ypa": undefined, - "--awsui-style-background-disabled-6b9ypa": undefined, - "--awsui-style-background-focus-6b9ypa": undefined, - "--awsui-style-background-hover-6b9ypa": undefined, - "--awsui-style-background-readonly-6b9ypa": undefined, - "--awsui-style-border-color-default-6b9ypa": undefined, - "--awsui-style-border-color-disabled-6b9ypa": undefined, - "--awsui-style-border-color-focus-6b9ypa": undefined, - "--awsui-style-border-color-hover-6b9ypa": undefined, - "--awsui-style-border-color-readonly-6b9ypa": undefined, - "--awsui-style-box-shadow-default-6b9ypa": undefined, - "--awsui-style-box-shadow-disabled-6b9ypa": undefined, - "--awsui-style-box-shadow-focus-6b9ypa": undefined, - "--awsui-style-box-shadow-hover-6b9ypa": undefined, - "--awsui-style-box-shadow-readonly-6b9ypa": undefined, - "--awsui-style-color-default-6b9ypa": undefined, - "--awsui-style-color-disabled-6b9ypa": undefined, - "--awsui-style-color-focus-6b9ypa": undefined, - "--awsui-style-color-hover-6b9ypa": undefined, - "--awsui-style-color-readonly-6b9ypa": undefined, - "--awsui-style-placeholder-color-6b9ypa": undefined, - "--awsui-style-placeholder-font-size-6b9ypa": undefined, - "--awsui-style-placeholder-font-style-6b9ypa": undefined, - "--awsui-style-placeholder-font-weight-6b9ypa": undefined, + "--awsui-style-background-default-8rcw2s": undefined, + "--awsui-style-background-disabled-8rcw2s": undefined, + "--awsui-style-background-focus-8rcw2s": undefined, + "--awsui-style-background-hover-8rcw2s": undefined, + "--awsui-style-background-readonly-8rcw2s": undefined, + "--awsui-style-border-color-default-8rcw2s": undefined, + "--awsui-style-border-color-disabled-8rcw2s": undefined, + "--awsui-style-border-color-focus-8rcw2s": undefined, + "--awsui-style-border-color-hover-8rcw2s": undefined, + "--awsui-style-border-color-readonly-8rcw2s": undefined, + "--awsui-style-box-shadow-default-8rcw2s": undefined, + "--awsui-style-box-shadow-disabled-8rcw2s": undefined, + "--awsui-style-box-shadow-focus-8rcw2s": undefined, + "--awsui-style-box-shadow-hover-8rcw2s": undefined, + "--awsui-style-box-shadow-readonly-8rcw2s": undefined, + "--awsui-style-color-default-8rcw2s": undefined, + "--awsui-style-color-disabled-8rcw2s": undefined, + "--awsui-style-color-focus-8rcw2s": undefined, + "--awsui-style-color-hover-8rcw2s": undefined, + "--awsui-style-color-readonly-8rcw2s": undefined, + "--awsui-style-placeholder-color-8rcw2s": undefined, + "--awsui-style-placeholder-font-size-8rcw2s": undefined, + "--awsui-style-placeholder-font-style-8rcw2s": undefined, + "--awsui-style-placeholder-font-weight-8rcw2s": undefined, "borderRadius": undefined, "borderWidth": undefined, "fontSize": undefined, @@ -37,30 +37,30 @@ exports[`getTextFilterStyles handles all possible style configurations 1`] = ` exports[`getTextFilterStyles handles all possible style configurations 2`] = ` { - "--awsui-style-background-default-6b9ypa": undefined, - "--awsui-style-background-disabled-6b9ypa": undefined, - "--awsui-style-background-focus-6b9ypa": undefined, - "--awsui-style-background-hover-6b9ypa": undefined, - "--awsui-style-background-readonly-6b9ypa": undefined, - "--awsui-style-border-color-default-6b9ypa": undefined, - "--awsui-style-border-color-disabled-6b9ypa": undefined, - "--awsui-style-border-color-focus-6b9ypa": undefined, - "--awsui-style-border-color-hover-6b9ypa": undefined, - "--awsui-style-border-color-readonly-6b9ypa": undefined, - "--awsui-style-box-shadow-default-6b9ypa": undefined, - "--awsui-style-box-shadow-disabled-6b9ypa": undefined, - "--awsui-style-box-shadow-focus-6b9ypa": undefined, - "--awsui-style-box-shadow-hover-6b9ypa": undefined, - "--awsui-style-box-shadow-readonly-6b9ypa": undefined, - "--awsui-style-color-default-6b9ypa": undefined, - "--awsui-style-color-disabled-6b9ypa": undefined, - "--awsui-style-color-focus-6b9ypa": undefined, - "--awsui-style-color-hover-6b9ypa": undefined, - "--awsui-style-color-readonly-6b9ypa": undefined, - "--awsui-style-placeholder-color-6b9ypa": undefined, - "--awsui-style-placeholder-font-size-6b9ypa": undefined, - "--awsui-style-placeholder-font-style-6b9ypa": undefined, - "--awsui-style-placeholder-font-weight-6b9ypa": undefined, + "--awsui-style-background-default-8rcw2s": undefined, + "--awsui-style-background-disabled-8rcw2s": undefined, + "--awsui-style-background-focus-8rcw2s": undefined, + "--awsui-style-background-hover-8rcw2s": undefined, + "--awsui-style-background-readonly-8rcw2s": undefined, + "--awsui-style-border-color-default-8rcw2s": undefined, + "--awsui-style-border-color-disabled-8rcw2s": undefined, + "--awsui-style-border-color-focus-8rcw2s": undefined, + "--awsui-style-border-color-hover-8rcw2s": undefined, + "--awsui-style-border-color-readonly-8rcw2s": undefined, + "--awsui-style-box-shadow-default-8rcw2s": undefined, + "--awsui-style-box-shadow-disabled-8rcw2s": undefined, + "--awsui-style-box-shadow-focus-8rcw2s": undefined, + "--awsui-style-box-shadow-hover-8rcw2s": undefined, + "--awsui-style-box-shadow-readonly-8rcw2s": undefined, + "--awsui-style-color-default-8rcw2s": undefined, + "--awsui-style-color-disabled-8rcw2s": undefined, + "--awsui-style-color-focus-8rcw2s": undefined, + "--awsui-style-color-hover-8rcw2s": undefined, + "--awsui-style-color-readonly-8rcw2s": undefined, + "--awsui-style-placeholder-color-8rcw2s": undefined, + "--awsui-style-placeholder-font-size-8rcw2s": undefined, + "--awsui-style-placeholder-font-style-8rcw2s": undefined, + "--awsui-style-placeholder-font-weight-8rcw2s": undefined, "borderRadius": undefined, "borderWidth": undefined, "fontSize": undefined, @@ -72,30 +72,30 @@ exports[`getTextFilterStyles handles all possible style configurations 2`] = ` exports[`getTextFilterStyles handles all possible style configurations 3`] = ` { - "--awsui-style-background-default-6b9ypa": "#ffffff", - "--awsui-style-background-disabled-6b9ypa": "#f0f0f0", - "--awsui-style-background-focus-6b9ypa": "#ffffff", - "--awsui-style-background-hover-6b9ypa": "#fafafa", - "--awsui-style-background-readonly-6b9ypa": "#ffffff", - "--awsui-style-border-color-default-6b9ypa": "#cccccc", - "--awsui-style-border-color-disabled-6b9ypa": "#e0e0e0", - "--awsui-style-border-color-focus-6b9ypa": "#0073bb", - "--awsui-style-border-color-hover-6b9ypa": "#999999", - "--awsui-style-border-color-readonly-6b9ypa": "#e0e0e0", - "--awsui-style-box-shadow-default-6b9ypa": "none", - "--awsui-style-box-shadow-disabled-6b9ypa": "none", - "--awsui-style-box-shadow-focus-6b9ypa": "0 0 0 2px #0073bb", - "--awsui-style-box-shadow-hover-6b9ypa": "0 1px 2px rgba(0,0,0,0.1)", - "--awsui-style-box-shadow-readonly-6b9ypa": "none", - "--awsui-style-color-default-6b9ypa": "#000000", - "--awsui-style-color-disabled-6b9ypa": "#999999", - "--awsui-style-color-focus-6b9ypa": "#000000", - "--awsui-style-color-hover-6b9ypa": "#000000", - "--awsui-style-color-readonly-6b9ypa": "#000000", - "--awsui-style-placeholder-color-6b9ypa": "#999999", - "--awsui-style-placeholder-font-size-6b9ypa": "14px", - "--awsui-style-placeholder-font-style-6b9ypa": "italic", - "--awsui-style-placeholder-font-weight-6b9ypa": "400", + "--awsui-style-background-default-8rcw2s": "#ffffff", + "--awsui-style-background-disabled-8rcw2s": "#f0f0f0", + "--awsui-style-background-focus-8rcw2s": "#ffffff", + "--awsui-style-background-hover-8rcw2s": "#fafafa", + "--awsui-style-background-readonly-8rcw2s": "#ffffff", + "--awsui-style-border-color-default-8rcw2s": "#cccccc", + "--awsui-style-border-color-disabled-8rcw2s": "#e0e0e0", + "--awsui-style-border-color-focus-8rcw2s": "#0073bb", + "--awsui-style-border-color-hover-8rcw2s": "#999999", + "--awsui-style-border-color-readonly-8rcw2s": "#e0e0e0", + "--awsui-style-box-shadow-default-8rcw2s": "none", + "--awsui-style-box-shadow-disabled-8rcw2s": "none", + "--awsui-style-box-shadow-focus-8rcw2s": "0 0 0 2px #0073bb", + "--awsui-style-box-shadow-hover-8rcw2s": "0 1px 2px rgba(0,0,0,0.1)", + "--awsui-style-box-shadow-readonly-8rcw2s": "none", + "--awsui-style-color-default-8rcw2s": "#000000", + "--awsui-style-color-disabled-8rcw2s": "#999999", + "--awsui-style-color-focus-8rcw2s": "#000000", + "--awsui-style-color-hover-8rcw2s": "#000000", + "--awsui-style-color-readonly-8rcw2s": "#000000", + "--awsui-style-placeholder-color-8rcw2s": "#999999", + "--awsui-style-placeholder-font-size-8rcw2s": "14px", + "--awsui-style-placeholder-font-style-8rcw2s": "italic", + "--awsui-style-placeholder-font-weight-8rcw2s": "400", "borderRadius": "4px", "borderWidth": "1px", "fontSize": "14px", diff --git a/src/token/__tests__/__snapshots__/styles.test.tsx.snap b/src/token/__tests__/__snapshots__/styles.test.tsx.snap index 6e76a3e401..b2ae810dd6 100644 --- a/src/token/__tests__/__snapshots__/styles.test.tsx.snap +++ b/src/token/__tests__/__snapshots__/styles.test.tsx.snap @@ -2,19 +2,19 @@ exports[`getTokenRootStyles handles all possible style configurations 1`] = ` { - "--awsui-style-focus-ring-border-color-6b9ypa": "#6366f1", - "--awsui-style-focus-ring-border-radius-6b9ypa": "12px", - "--awsui-style-focus-ring-border-width-6b9ypa": "2px", - "--awsui-token-style-background-default-6b9ypa": "#eef2ff", - "--awsui-token-style-background-disabled-6b9ypa": "#f1f5f9", - "--awsui-token-style-background-read-only-6b9ypa": "#f8fafc", - "--awsui-token-style-border-color-default-6b9ypa": "#c7d2fe", - "--awsui-token-style-border-color-disabled-6b9ypa": "#e2e8f0", - "--awsui-token-style-border-color-read-only-6b9ypa": "#cbd5e1", - "--awsui-token-style-dismiss-color-default-6b9ypa": "#6366f1", - "--awsui-token-style-dismiss-color-disabled-6b9ypa": "#cbd5e1", - "--awsui-token-style-dismiss-color-hover-6b9ypa": "#4338ca", - "--awsui-token-style-dismiss-color-read-only-6b9ypa": "#94a3b8", + "--awsui-style-focus-ring-border-color-8rcw2s": "#6366f1", + "--awsui-style-focus-ring-border-radius-8rcw2s": "12px", + "--awsui-style-focus-ring-border-width-8rcw2s": "2px", + "--awsui-token-style-background-default-8rcw2s": "#eef2ff", + "--awsui-token-style-background-disabled-8rcw2s": "#f1f5f9", + "--awsui-token-style-background-read-only-8rcw2s": "#f8fafc", + "--awsui-token-style-border-color-default-8rcw2s": "#c7d2fe", + "--awsui-token-style-border-color-disabled-8rcw2s": "#e2e8f0", + "--awsui-token-style-border-color-read-only-8rcw2s": "#cbd5e1", + "--awsui-token-style-dismiss-color-default-8rcw2s": "#6366f1", + "--awsui-token-style-dismiss-color-disabled-8rcw2s": "#cbd5e1", + "--awsui-token-style-dismiss-color-hover-8rcw2s": "#4338ca", + "--awsui-token-style-dismiss-color-read-only-8rcw2s": "#94a3b8", "borderRadius": "24px", "borderWidth": "2px", "paddingBlock": "4px", From e5cef9f8d9b39761189d78873d562fc25aa62f01 Mon Sep 17 00:00:00 2001 From: Simon Brebeck Date: Sun, 29 Mar 2026 20:33:58 +0200 Subject: [PATCH 3/4] fix: Action card improvements --- .../__snapshots__/styles.test.tsx.snap | 55 +++ .../__tests__/action-card.test.tsx | 357 +++++++----------- src/action-card/__tests__/styles.test.tsx | 127 +++++++ src/action-card/index.tsx | 3 + src/action-card/interfaces.ts | 2 +- src/action-card/internal.tsx | 27 +- src/action-card/test-classes/styles.scss | 20 + src/test-utils/dom/action-card/index.ts | 9 +- style-dictionary/classic/spacing.ts | 2 +- 9 files changed, 365 insertions(+), 237 deletions(-) create mode 100644 src/action-card/__tests__/__snapshots__/styles.test.tsx.snap create mode 100644 src/action-card/__tests__/styles.test.tsx create mode 100644 src/action-card/test-classes/styles.scss diff --git a/src/action-card/__tests__/__snapshots__/styles.test.tsx.snap b/src/action-card/__tests__/__snapshots__/styles.test.tsx.snap new file mode 100644 index 0000000000..ee446a6242 --- /dev/null +++ b/src/action-card/__tests__/__snapshots__/styles.test.tsx.snap @@ -0,0 +1,55 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getContentStyles handles all possible style configurations 1`] = `undefined`; + +exports[`getContentStyles handles all possible style configurations 2`] = `undefined`; + +exports[`getContentStyles handles all possible style configurations 3`] = ` +{ + "paddingBlock": "16px", + "paddingInline": "20px", +} +`; + +exports[`getHeaderStyles handles all possible style configurations 1`] = `undefined`; + +exports[`getHeaderStyles handles all possible style configurations 2`] = `undefined`; + +exports[`getHeaderStyles handles all possible style configurations 3`] = ` +{ + "paddingBlock": "12px", + "paddingInline": "20px", +} +`; + +exports[`getRootStyles handles all possible style configurations 1`] = `undefined`; + +exports[`getRootStyles handles all possible style configurations 2`] = `undefined`; + +exports[`getRootStyles handles all possible style configurations 3`] = ` +{ + "--awsui-style-action-card-background-active-8rcw2s": "#eeeeee", + "--awsui-style-action-card-background-default-8rcw2s": "#ffffff", + "--awsui-style-action-card-background-disabled-8rcw2s": "#fafafa", + "--awsui-style-action-card-background-hover-8rcw2s": "#f5f5f5", + "--awsui-style-action-card-border-color-active-8rcw2s": "#9e9e9e", + "--awsui-style-action-card-border-color-default-8rcw2s": "#e0e0e0", + "--awsui-style-action-card-border-color-disabled-8rcw2s": "#eeeeee", + "--awsui-style-action-card-border-color-hover-8rcw2s": "#bdbdbd", + "--awsui-style-action-card-border-radius-active-8rcw2s": "8px", + "--awsui-style-action-card-border-radius-default-8rcw2s": "8px", + "--awsui-style-action-card-border-radius-disabled-8rcw2s": "8px", + "--awsui-style-action-card-border-radius-hover-8rcw2s": "8px", + "--awsui-style-action-card-border-width-active-8rcw2s": "2px", + "--awsui-style-action-card-border-width-default-8rcw2s": "1px", + "--awsui-style-action-card-border-width-disabled-8rcw2s": "1px", + "--awsui-style-action-card-border-width-hover-8rcw2s": "2px", + "--awsui-style-action-card-box-shadow-active-8rcw2s": "0 1px 2px rgba(0,0,0,0.2)", + "--awsui-style-action-card-box-shadow-default-8rcw2s": "0 1px 3px rgba(0,0,0,0.1)", + "--awsui-style-action-card-box-shadow-disabled-8rcw2s": "none", + "--awsui-style-action-card-box-shadow-hover-8rcw2s": "0 2px 6px rgba(0,0,0,0.15)", + "--awsui-style-action-card-focus-ring-border-color-8rcw2s": "#0073bb", + "--awsui-style-action-card-focus-ring-border-radius-8rcw2s": "10px", + "--awsui-style-action-card-focus-ring-border-width-8rcw2s": "2px", +} +`; diff --git a/src/action-card/__tests__/action-card.test.tsx b/src/action-card/__tests__/action-card.test.tsx index eee12b8317..75461719a3 100644 --- a/src/action-card/__tests__/action-card.test.tsx +++ b/src/action-card/__tests__/action-card.test.tsx @@ -6,67 +6,94 @@ import { render } from '@testing-library/react'; import ActionCard, { ActionCardProps } from '../../../lib/components/action-card'; import createWrapper from '../../../lib/components/test-utils/dom'; -import styles from '../../../lib/components/action-card/styles.css.js'; - function renderActionCard(props: ActionCardProps = {}) { const renderResult = render(); return createWrapper(renderResult.container).findActionCard()!; } describe('ActionCard Component', () => { - describe('header property', () => { - test('renders header when provided', () => { + describe('button element', () => { + test('renders as a button element with type="button"', () => { + const wrapper = renderActionCard(); + expect(wrapper.getElement().tagName).toBe('BUTTON'); + expect(wrapper.getElement()).toHaveAttribute('type', 'button'); + }); + }); + + describe('header', () => { + test('renders header text when provided', () => { const wrapper = renderActionCard({ header: 'Test Header' }); expect(wrapper.findHeader()!.getElement()).toHaveTextContent('Test Header'); }); + test('renders header as ReactNode', () => { + const wrapper = renderActionCard({ header: Bold Header }); + expect(wrapper.findHeader()!.getElement().querySelector('strong')).toHaveTextContent('Bold Header'); + }); + test('does not render header element when not provided', () => { const wrapper = renderActionCard(); expect(wrapper.findHeader()).toBeNull(); }); }); - describe('description property', () => { + describe('description', () => { test('renders description when provided', () => { const wrapper = renderActionCard({ description: 'Test Description' }); expect(wrapper.findDescription()!.getElement()).toHaveTextContent('Test Description'); }); + test('renders description as ReactNode', () => { + const wrapper = renderActionCard({ description: Italic Desc }); + expect(wrapper.findDescription()!.getElement().querySelector('em')).toHaveTextContent('Italic Desc'); + }); + test('does not render description element when not provided', () => { const wrapper = renderActionCard(); expect(wrapper.findDescription()).toBeNull(); }); }); - describe('children property', () => { + describe('children', () => { test('renders children content when provided', () => { const wrapper = renderActionCard({ children: 'Test Content' }); expect(wrapper.findContent()!.getElement()).toHaveTextContent('Test Content'); }); + test('renders children as ReactNode', () => { + const wrapper = renderActionCard({ children:
Custom Content
}); + expect(wrapper.findContent()!.getElement().querySelector('[data-testid="custom"]')).toHaveTextContent( + 'Custom Content' + ); + }); + test('does not render content element when children not provided', () => { const wrapper = renderActionCard(); expect(wrapper.findContent()).toBeNull(); }); }); - describe('disabled property', () => { - test('renders action card with normal styling by default', () => { + describe('disabled', () => { + test('is not disabled by default', () => { const wrapper = renderActionCard(); - expect(wrapper.isDisabled()).toEqual(false); + expect(wrapper.isDisabled()).toBe(false); expect(wrapper.getElement()).not.toHaveAttribute('disabled'); - expect(wrapper.getElement()).not.toHaveClass(styles.disabled); }); - test('renders action card with disabled styling when true', () => { + test('applies disabled state when true', () => { const wrapper = renderActionCard({ disabled: true }); - expect(wrapper.isDisabled()).toEqual(true); - expect(wrapper.getElement()).toHaveClass(styles.disabled); - expect(wrapper.getElement()).toHaveAttribute('disabled'); + expect(wrapper.isDisabled()).toBe(true); + expect(wrapper.getElement()).not.toHaveAttribute('disabled'); expect(wrapper.getElement()).toHaveAttribute('aria-disabled', 'true'); }); - test('does not call onClick when disabled', () => { + test('remains focusable when disabled', () => { + const wrapper = renderActionCard({ disabled: true }); + wrapper.getElement().focus(); + expect(document.activeElement).toBe(wrapper.getElement()); + }); + + test('does not fire onClick when disabled', () => { const onClickSpy = jest.fn(); const wrapper = renderActionCard({ onClick: onClickSpy, disabled: true }); wrapper.click(); @@ -74,48 +101,69 @@ describe('ActionCard Component', () => { }); }); - describe('onClick property', () => { - test('calls onClick when the action card is clicked', () => { + describe('onClick', () => { + test('calls onClick when clicked', () => { const onClickSpy = jest.fn(); const wrapper = renderActionCard({ onClick: onClickSpy }); wrapper.click(); - expect(onClickSpy).toHaveBeenCalled(); - }); - - test('does not call onClick when disabled', () => { - const onClickSpy = jest.fn(); - const wrapper = renderActionCard({ onClick: onClickSpy, disabled: true }); - wrapper.click(); - expect(onClickSpy).not.toHaveBeenCalled(); + expect(onClickSpy).toHaveBeenCalledTimes(1); }); }); - describe('ariaLabel property', () => { - test('adds aria-label attribute when provided', () => { - const wrapper = renderActionCard({ ariaLabel: 'Test Aria Label' }); - expect(wrapper.getElement()).toHaveAttribute('aria-label', 'Test Aria Label'); + describe('ariaLabel', () => { + test('adds aria-label when provided', () => { + const wrapper = renderActionCard({ ariaLabel: 'Card label' }); + expect(wrapper.getElement()).toHaveAttribute('aria-label', 'Card label'); }); test('does not add aria-label when not provided', () => { const wrapper = renderActionCard(); expect(wrapper.getElement()).not.toHaveAttribute('aria-label'); }); + + test('does not set aria-labelledby when ariaLabel is provided', () => { + const wrapper = renderActionCard({ ariaLabel: 'Card label', header: 'Header' }); + expect(wrapper.getElement()).not.toHaveAttribute('aria-labelledby'); + }); + }); + + describe('aria-labelledby', () => { + test('sets aria-labelledby from header id when no ariaLabel is provided', () => { + const wrapper = renderActionCard({ header: 'Header' }); + const labelledBy = wrapper.getElement().getAttribute('aria-labelledby'); + expect(labelledBy).toBeTruthy(); + const labelEl = wrapper.getElement().querySelector(`#${labelledBy}`); + expect(labelEl).toHaveTextContent('Header'); + }); + + test('does not set aria-labelledby when no header and no ariaLabel', () => { + const wrapper = renderActionCard(); + expect(wrapper.getElement()).not.toHaveAttribute('aria-labelledby'); + }); }); - describe('ariaDescribedby property', () => { - test('adds aria-describedby attribute when provided', () => { - const wrapper = renderActionCard({ ariaDescribedby: 'description-id' }); - expect(wrapper.getElement()).toHaveAttribute('aria-describedby', 'description-id'); + describe('ariaDescribedby', () => { + test('uses provided ariaDescribedby over auto-generated description id', () => { + const wrapper = renderActionCard({ ariaDescribedby: 'custom-id', description: 'Desc' }); + expect(wrapper.getElement()).toHaveAttribute('aria-describedby', 'custom-id'); + }); + + test('auto-generates aria-describedby from description when no ariaDescribedby provided', () => { + const wrapper = renderActionCard({ description: 'Desc' }); + const describedBy = wrapper.getElement().getAttribute('aria-describedby'); + expect(describedBy).toBeTruthy(); + const descEl = wrapper.getElement().querySelector(`#${describedBy}`); + expect(descEl).toHaveTextContent('Desc'); }); - test('does not add aria-describedby when not provided', () => { + test('does not set aria-describedby when neither ariaDescribedby nor description provided', () => { const wrapper = renderActionCard(); expect(wrapper.getElement()).not.toHaveAttribute('aria-describedby'); }); }); describe('focus management', () => { - test('can be focused through the API', () => { + test('can be focused through the ref API', () => { let actionCard: ActionCardProps.Ref | null = null; const renderResult = render( (actionCard = el)} />); const wrapper = createWrapper(renderResult.container); @@ -124,218 +172,81 @@ describe('ActionCard Component', () => { }); }); - describe('button element', () => { - test('renders as a button element', () => { - const wrapper = renderActionCard(); - expect(wrapper.getElement().tagName).toBe('BUTTON'); - }); - - test('has type="button" attribute', () => { - const wrapper = renderActionCard(); - expect(wrapper.getElement()).toHaveAttribute('type', 'button'); + describe('icon', () => { + test('renders icon with aria-hidden when provided', () => { + const wrapper = renderActionCard({ icon: icon }); + const iconEl = wrapper.findIcon(); + expect(iconEl).not.toBeNull(); }); }); - /** - * Preservation Property Tests - * Validates: Requirements 3.1, 3.2, 3.3, 3.4, 3.5, 3.6 - * - * These tests capture the current (correct) behavior that must be preserved after the fix. - * They MUST PASS on the current unfixed code. - */ - describe('Preservation: center alignment and no-icon layout unchanged', () => { - test('center-aligned icon is a direct child of root button, not inside inner-card', () => { - /** - * Validates: Requirements 3.1 - */ + describe('nativeButtonAttributes', () => { + test('passes custom data attributes to the button element', () => { const wrapper = renderActionCard({ - icon: icon, - iconVerticalAlignment: 'center', - header: 'Header', - children: 'Body', + nativeButtonAttributes: { + 'data-testid': 'test-card', + 'aria-controls': 'controlled-element', + }, }); - - const rootEl = wrapper.getElement(); - const innerCard = rootEl.querySelector(`.${styles['inner-card']}`); - expect(innerCard).not.toBeNull(); - - // Icon should be a direct child of root button (sibling of inner-card) - const iconDirectChildOfRoot = Array.from(rootEl.children).find(child => child.classList.contains(styles.icon)); - expect(iconDirectChildOfRoot).toBeDefined(); - - // Icon should NOT be inside inner-card - const iconInsideInnerCard = innerCard!.querySelector(`.${styles.icon}`); - expect(iconInsideInnerCard).toBeNull(); - }); - - test('no-icon card has no icon wrapper element in the DOM', () => { - /** - * Validates: Requirements 3.2 - */ - const wrapper = renderActionCard({ - header: 'Header', - children: 'Body', - }); - - const rootEl = wrapper.getElement(); - const iconEl = rootEl.querySelector(`.${styles.icon}`); - expect(iconEl).toBeNull(); - }); - - test('disabled card with icon has disabled class, aria-disabled, and disabled attribute', () => { - /** - * Validates: Requirements 3.3 - */ - const wrapper = renderActionCard({ - icon: icon, - iconVerticalAlignment: 'center', - header: 'Header', - disabled: true, - }); - - const rootEl = wrapper.getElement(); - expect(rootEl).toHaveClass(styles.disabled); - expect(rootEl).toHaveAttribute('aria-disabled', 'true'); - expect(rootEl).toHaveAttribute('disabled'); - }); - - test('disableHeaderPaddings=true applies no-padding class on header', () => { - /** - * Validates: Requirements 3.4 - */ - const wrapper = renderActionCard({ - header: 'Header', - children: 'Body', - disableHeaderPaddings: true, - }); - - const rootEl = wrapper.getElement(); - const headerEl = rootEl.querySelector(`.${styles.header}`); - expect(headerEl).not.toBeNull(); - expect(headerEl!.classList.contains(styles['no-padding'])).toBe(true); - }); - - test('disableContentPaddings=true applies no-padding class on body', () => { - /** - * Validates: Requirements 3.4 - */ - const wrapper = renderActionCard({ - header: 'Header', - children: 'Body', - disableContentPaddings: true, - }); - - const rootEl = wrapper.getElement(); - const bodyEl = rootEl.querySelector(`.${styles.body}`); - expect(bodyEl).not.toBeNull(); - expect(bodyEl!.classList.contains(styles['no-padding'])).toBe(true); + expect(wrapper.getElement()).toHaveAttribute('data-testid', 'test-card'); + expect(wrapper.getElement()).toHaveAttribute('aria-controls', 'controlled-element'); }); - test('variant="default" applies variant-default class on root', () => { - /** - * Validates: Requirements 3.5 - */ + test('concatenates className with internal classes', () => { const wrapper = renderActionCard({ - header: 'Header', - variant: 'default', + nativeButtonAttributes: { + className: 'my-custom-class', + }, }); - - const rootEl = wrapper.getElement(); - expect(rootEl).toHaveClass(styles['variant-default']); + expect(wrapper.getElement()).toHaveClass('my-custom-class'); }); - test('variant="embedded" applies variant-embedded class on root', () => { - /** - * Validates: Requirements 3.5 - */ + test('chains event handlers', () => { + const mainClick = jest.fn(); + const nativeClick = jest.fn(); const wrapper = renderActionCard({ - header: 'Header', - variant: 'embedded', + onClick: mainClick, + nativeButtonAttributes: { + onClick: nativeClick, + }, }); - - const rootEl = wrapper.getElement(); - expect(rootEl).toHaveClass(styles['variant-embedded']); + wrapper.click(); + expect(mainClick).toHaveBeenCalled(); + expect(nativeClick).toHaveBeenCalled(); }); }); - /** - * Bug Condition Exploration Tests - * Validates: Requirements 1.1, 1.2 - * - * These tests encode the EXPECTED (fixed) behavior for iconVerticalAlignment='top'. - * They are expected to FAIL on unfixed code, proving the bug exists. - * The bug: icon is rendered as a sibling of inner-card at the root level, - * instead of inside inner-card within a header-row wrapper. - */ - describe('Bug Condition: icon alignment when iconVerticalAlignment="top"', () => { - test('icon is rendered inside inner-card within a header-row wrapper, not as a direct child of root', () => { - const wrapper = renderActionCard({ - icon: icon, - iconVerticalAlignment: 'top', - header: 'Header', - children: 'Body', - }); + describe('combined ARIA attributes', () => { + test('sets both aria-labelledby and aria-describedby when header and description are provided', () => { + const wrapper = renderActionCard({ header: 'Header', description: 'Description' }); + const el = wrapper.getElement(); - const rootEl = wrapper.getElement(); - const innerCard = rootEl.querySelector(`.${styles['inner-card']}`); - expect(innerCard).not.toBeNull(); + const labelledBy = el.getAttribute('aria-labelledby'); + expect(labelledBy).toBeTruthy(); + expect(el.querySelector(`#${labelledBy}`)).toHaveTextContent('Header'); - // The icon should be inside inner-card (within a header-row wrapper), NOT a direct child of root - const iconInsideInnerCard = innerCard!.querySelector(`.${styles.icon}`); - expect(iconInsideInnerCard).not.toBeNull(); + const describedBy = el.getAttribute('aria-describedby'); + expect(describedBy).toBeTruthy(); + expect(el.querySelector(`#${describedBy}`)).toHaveTextContent('Description'); - // The icon should NOT be a direct child of the root button - const iconDirectChildOfRoot = Array.from(rootEl.children).find(child => child.classList.contains(styles.icon)); - expect(iconDirectChildOfRoot).toBeUndefined(); + expect(labelledBy).not.toBe(describedBy); }); + }); - test('a header-row wrapper exists inside inner-card containing both header and icon', () => { - const wrapper = renderActionCard({ - icon: icon, - iconVerticalAlignment: 'top', - header: 'Header', - description: 'Description', - children: 'Body', - }); - - const rootEl = wrapper.getElement(); - const innerCard = rootEl.querySelector(`.${styles['inner-card']}`); - expect(innerCard).not.toBeNull(); - - // A header-row wrapper should exist inside inner-card - const headerRow = innerCard!.querySelector(`.${styles['header-row']}`); - expect(headerRow).not.toBeNull(); - - // header-row should contain both the header section and the icon - const headerInRow = headerRow!.querySelector(`.${styles.header}`); - const iconInRow = headerRow!.querySelector(`.${styles.icon}`); - expect(headerInRow).not.toBeNull(); - expect(iconInRow).not.toBeNull(); + describe('description without header', () => { + test('renders description and sets aria-describedby when only description is provided', () => { + const wrapper = renderActionCard({ description: 'Only description' }); + expect(wrapper.findDescription()!.getElement()).toHaveTextContent('Only description'); + expect(wrapper.findHeader()).toBeNull(); - // body should be a sibling of header-row inside inner-card, not inside header-row - const bodyEl = innerCard!.querySelector(`.${styles.body}`); - expect(bodyEl).not.toBeNull(); - expect(headerRow!.contains(bodyEl)).toBe(false); + const describedBy = wrapper.getElement().getAttribute('aria-describedby'); + expect(describedBy).toBeTruthy(); + expect(wrapper.getElement().querySelector(`#${describedBy}`)).toHaveTextContent('Only description'); }); - test('edge case: iconVerticalAlignment="top" with no header/description but with children — icon falls back to root sibling', () => { - const wrapper = renderActionCard({ - icon: icon, - iconVerticalAlignment: 'top', - children: 'Body content', - }); - - const rootEl = wrapper.getElement(); - const innerCard = rootEl.querySelector(`.${styles['inner-card']}`); - expect(innerCard).not.toBeNull(); - - // Without a header, icon should fall back to root sibling (like center alignment) - const iconDirectChildOfRoot = Array.from(rootEl.children).find(child => child.classList.contains(styles.icon)); - expect(iconDirectChildOfRoot).toBeDefined(); - - // Icon should NOT be inside inner-card - const iconInsideInnerCard = innerCard!.querySelector(`.${styles.icon}`); - expect(iconInsideInnerCard).toBeNull(); + test('does not set aria-labelledby when only description is provided', () => { + const wrapper = renderActionCard({ description: 'Only description' }); + expect(wrapper.getElement()).not.toHaveAttribute('aria-labelledby'); }); }); }); diff --git a/src/action-card/__tests__/styles.test.tsx b/src/action-card/__tests__/styles.test.tsx new file mode 100644 index 0000000000..9d3ff9cf93 --- /dev/null +++ b/src/action-card/__tests__/styles.test.tsx @@ -0,0 +1,127 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +import { getContentStyles, getHeaderStyles, getRootStyles } from '../style'; + +jest.mock('../../internal/environment', () => ({ + SYSTEM: 'core', +})); + +const allStyles = { + root: { + background: { + default: '#ffffff', + hover: '#f5f5f5', + active: '#eeeeee', + disabled: '#fafafa', + }, + borderColor: { + default: '#e0e0e0', + hover: '#bdbdbd', + active: '#9e9e9e', + disabled: '#eeeeee', + }, + borderRadius: { + default: '8px', + hover: '8px', + active: '8px', + disabled: '8px', + }, + borderWidth: { + default: '1px', + hover: '2px', + active: '2px', + disabled: '1px', + }, + boxShadow: { + default: '0 1px 3px rgba(0,0,0,0.1)', + hover: '0 2px 6px rgba(0,0,0,0.15)', + active: '0 1px 2px rgba(0,0,0,0.2)', + disabled: 'none', + }, + focusRing: { + borderColor: '#0073bb', + borderRadius: '10px', + borderWidth: '2px', + }, + }, + content: { + paddingBlock: '16px', + paddingInline: '20px', + }, + header: { + paddingBlock: '12px', + paddingInline: '20px', + }, +}; + +describe('getRootStyles', () => { + afterEach(() => { + jest.resetModules(); + }); + + test('handles all possible style configurations', () => { + expect(getRootStyles(undefined)).toMatchSnapshot(); + expect(getRootStyles({})).toMatchSnapshot(); + expect(getRootStyles(allStyles)).toMatchSnapshot(); + }); + + test('returns undefined when SYSTEM is not core', async () => { + jest.resetModules(); + jest.doMock('../../internal/environment', () => ({ + SYSTEM: 'visual-refresh', + })); + + const { getRootStyles: getRootStylesNonCore } = await import('../style'); + + const result = getRootStylesNonCore(allStyles); + expect(result).toBeUndefined(); + }); +}); + +describe('getContentStyles', () => { + afterEach(() => { + jest.resetModules(); + }); + + test('handles all possible style configurations', () => { + expect(getContentStyles(undefined)).toMatchSnapshot(); + expect(getContentStyles({})).toMatchSnapshot(); + expect(getContentStyles(allStyles)).toMatchSnapshot(); + }); + + test('returns undefined when SYSTEM is not core', async () => { + jest.resetModules(); + jest.doMock('../../internal/environment', () => ({ + SYSTEM: 'visual-refresh', + })); + + const { getContentStyles: getContentStylesNonCore } = await import('../style'); + + const result = getContentStylesNonCore(allStyles); + expect(result).toBeUndefined(); + }); +}); + +describe('getHeaderStyles', () => { + afterEach(() => { + jest.resetModules(); + }); + + test('handles all possible style configurations', () => { + expect(getHeaderStyles(undefined)).toMatchSnapshot(); + expect(getHeaderStyles({})).toMatchSnapshot(); + expect(getHeaderStyles(allStyles)).toMatchSnapshot(); + }); + + test('returns undefined when SYSTEM is not core', async () => { + jest.resetModules(); + jest.doMock('../../internal/environment', () => ({ + SYSTEM: 'visual-refresh', + })); + + const { getHeaderStyles: getHeaderStylesNonCore } = await import('../style'); + + const result = getHeaderStylesNonCore(allStyles); + expect(result).toBeUndefined(); + }); +}); diff --git a/src/action-card/index.tsx b/src/action-card/index.tsx index 775b44d609..0078b73f04 100644 --- a/src/action-card/index.tsx +++ b/src/action-card/index.tsx @@ -3,6 +3,7 @@ 'use client'; import React from 'react'; +import InternalIcon from '../icon/internal'; import useBaseComponent from '../internal/hooks/use-base-component'; import { applyDisplayName } from '../internal/utils/apply-display-name'; import { getExternalProps } from '../internal/utils/external-props'; @@ -19,6 +20,7 @@ const ActionCard = React.forwardRef( disableContentPaddings = false, iconVerticalAlignment = 'top', variant = 'default', + icon = , ...props }: ActionCardProps, ref: React.Ref @@ -43,6 +45,7 @@ const ActionCard = React.forwardRef( disableContentPaddings={disableContentPaddings} iconVerticalAlignment={iconVerticalAlignment} variant={variant} + icon={icon} {...externalProps} {...baseComponentProps} /> diff --git a/src/action-card/interfaces.ts b/src/action-card/interfaces.ts index 8d6c4e50d2..d45ac67f14 100644 --- a/src/action-card/interfaces.ts +++ b/src/action-card/interfaces.ts @@ -56,7 +56,7 @@ export interface ActionCardProps extends BaseComponentProps { disableContentPaddings?: boolean; /** - * Displays an icon next to the content. You can use the `iconPosition` and `iconVerticalAlignment` properties to position the icon. + * Displays an icon next to the content. You can use the `iconVerticalAlignment` property to control vertical alignment. */ icon?: React.ReactNode; diff --git a/src/action-card/internal.tsx b/src/action-card/internal.tsx index 3978d2cc8b..71e12c9c06 100644 --- a/src/action-card/internal.tsx +++ b/src/action-card/internal.tsx @@ -14,6 +14,7 @@ import { type ActionCardProps } from './interfaces'; import { getContentStyles, getHeaderStyles, getRootStyles } from './style'; import styles from './styles.css.js'; +import testStyles from './test-classes/styles.css.js'; export type InternalActionCardProps = ActionCardProps & InternalBaseComponentProps; @@ -52,7 +53,7 @@ const InternalActionCard = React.forwardRef( const handleClick = (event: React.MouseEvent) => { if (disabled) { - return; + return event.preventDefault(); } fireCancelableEvent(onClick, {}, event); }; @@ -64,7 +65,7 @@ const InternalActionCard = React.forwardRef( const headerRowEmpty = !header && !description; const iconWrapper = icon && ( -