{
- selectionProps?.onChange();
- // Manually move focus to the native input (checkbox or radio button)
- event.currentTarget.querySelector('input')?.focus();
- }
- : undefined
- }
- >
-
-
- {cardDefinition.header ? cardDefinition.header(item) : ''}
-
- {selectionProps && (
-
-
-
- )}
+ {visibleSectionsDefinition.map(({ width = 100, header, content, id }, index) => (
+
+ {header ?
{header}
: ''}
+ {content ?
{content(item)}
: ''}
- {visibleSectionsDefinition.map(({ width = 100, header, content, id }, index) => (
-
- {header ?
{header}
: ''}
- {content ?
{content(item)}
: ''}
-
- ))}
-
-
+ ))}
+
);
})}
diff --git a/src/cards/styles.scss b/src/cards/styles.scss
index b2075f281b..46551f03d5 100644
--- a/src/cards/styles.scss
+++ b/src/cards/styles.scss
@@ -7,47 +7,9 @@
@use '../internal/styles' as styles;
@use '../internal/styles/tokens' as awsui;
-@use '../container/shared' as container;
-@use './motion';
-
-@mixin card-style {
- border-start-start-radius: awsui.$border-radius-container;
- border-start-end-radius: awsui.$border-radius-container;
- border-end-start-radius: awsui.$border-radius-container;
- border-end-end-radius: awsui.$border-radius-container;
- box-sizing: border-box;
-
- &::before {
- @include styles.base-pseudo-element;
- // Reset border color to prevent it from flashing black during card selection animation
- border-color: transparent;
- border-block-start: awsui.$border-container-top-width solid awsui.$color-border-container-top;
- border-start-start-radius: awsui.$border-radius-container;
- border-start-end-radius: awsui.$border-radius-container;
- border-end-start-radius: awsui.$border-radius-container;
- border-end-end-radius: awsui.$border-radius-container;
- z-index: 1;
- }
-
- &::after {
- @include styles.base-pseudo-element;
- border-start-start-radius: awsui.$border-radius-container;
- border-start-end-radius: awsui.$border-radius-container;
- border-end-start-radius: awsui.$border-radius-container;
- border-end-end-radius: awsui.$border-radius-container;
- }
- &:not(.refresh)::after {
- box-shadow: awsui.$shadow-container;
- }
- &.refresh::after {
- border-block: solid awsui.$border-divider-section-width awsui.$color-border-divider-default;
- border-inline: solid awsui.$border-divider-section-width awsui.$color-border-divider-default;
- }
-}
.root {
@include styles.styles-reset();
- @include styles.default-text-style;
}
.header {
@@ -112,48 +74,16 @@
}
.card {
- display: flex;
- overflow-wrap: break-word;
- word-wrap: break-word;
- margin-block: 0;
- margin-inline: 0;
- padding-block: 0;
- padding-inline: 0;
- list-style: none;
- &-inner {
- position: relative;
- background-color: awsui.$color-background-container-content;
- margin-block-start: 0;
- margin-block-end: awsui.$space-grid-gutter;
- margin-inline-start: awsui.$space-grid-gutter;
- margin-inline-end: 0;
- padding-block: awsui.$space-card-vertical;
- padding-inline: awsui.$space-card-horizontal;
- inline-size: 100%;
- min-inline-size: 0;
- @include card-style;
- }
- &-header {
- @include styles.font-heading-m;
- &-inner {
- inline-size: 100%;
- display: inline-block;
- }
- }
- &-selectable {
- > .card-inner > .card-header {
- inline-size: 90%;
- }
- }
- &-selected {
- > .card-inner {
- background-color: awsui.$color-background-item-selected;
- &::before {
- border-block: awsui.$border-item-width solid awsui.$color-border-item-selected;
- border-inline: awsui.$border-item-width solid awsui.$color-border-item-selected;
- }
- }
- }
+ /* Used in test utils */
+}
+
+.card-header-inner {
+ /* Used in test utils */
+ padding-block-start: 4px;
+}
+
+.card-header {
+ @include styles.font-heading-m;
}
.section {
diff --git a/src/internal/components/card/index.tsx b/src/internal/components/card/index.tsx
new file mode 100644
index 0000000000..84d4fa42c7
--- /dev/null
+++ b/src/internal/components/card/index.tsx
@@ -0,0 +1,50 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+import React from 'react';
+import clsx from 'clsx';
+
+import { useVisualRefresh } from '../../hooks/use-visual-mode';
+import { InternalCardProps } from './interfaces';
+
+import styles from './styles.css.js';
+
+export default function Card({
+ action,
+ active,
+ children,
+ className,
+ header,
+ innerMetadataAttributes,
+ metadataAttributes,
+ onClick,
+ onFocus,
+ role,
+ tagName: TagName = 'div',
+ disableContentPaddings,
+}: InternalCardProps) {
+ const isRefresh = useVisualRefresh();
+
+ return (
+
+
+
+
{header}
+ {action &&
{action}
}
+
+
{children}
+
+
+ );
+}
diff --git a/src/internal/components/card/interfaces.ts b/src/internal/components/card/interfaces.ts
new file mode 100644
index 0000000000..acb0e07da5
--- /dev/null
+++ b/src/internal/components/card/interfaces.ts
@@ -0,0 +1,58 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+import React, { FocusEventHandler } from 'react';
+
+import { BaseComponentProps } from '../../base-component';
+
+export interface InternalCardProps extends BaseComponentProps {
+ /**
+ * Specifies an action for the card.
+ * It is recommended to use a button with inline-icon variant.
+ */
+ action?: React.ReactNode;
+
+ /**
+ * Specifies whether the card is in active state.
+ */
+ active?: boolean;
+
+ /**
+ * Optional URL for an image which will be displayed cropped as a background of the card.
+ * When this property is used, a dark gradient is overlayed and the text above defaults to bright colors.
+ * Make sure that any content you place on the card has sufficient contrast with the overlayed image behind.
+ */
+ imageUrl?: string;
+
+ /**
+ * Primary content displayed in the card.
+ */
+ children?: React.ReactNode;
+
+ /**
+ * Heading text.
+ */
+ header?: React.ReactNode;
+
+ /**
+ * Icon which will be displayed at the top of the card,
+ * inline at the start of the content.
+ */
+ icon?: React.ReactNode;
+
+ /**
+ * Called when the user clicks on the card.
+ */
+ onClick?: React.MouseEventHandler
;
+
+ onFocus?: FocusEventHandler;
+
+ role?: string;
+
+ tagName?: 'li' | 'div';
+
+ disableContentPaddings?: boolean;
+
+ metadataAttributes?: Record;
+
+ innerMetadataAttributes?: Record;
+}
diff --git a/src/cards/motion.scss b/src/internal/components/card/motion.scss
similarity index 89%
rename from src/cards/motion.scss
rename to src/internal/components/card/motion.scss
index 385aa1d4b2..4dcf4658d3 100644
--- a/src/cards/motion.scss
+++ b/src/internal/components/card/motion.scss
@@ -3,8 +3,8 @@
SPDX-License-Identifier: Apache-2.0
*/
-@use '../internal/styles' as styles;
-@use '../internal/styles/tokens' as awsui;
+@use '../../styles' as styles;
+@use '../../styles/tokens' as awsui;
.card-inner {
@include styles.with-motion {
diff --git a/src/internal/components/card/styles.scss b/src/internal/components/card/styles.scss
new file mode 100644
index 0000000000..211ba3b3ab
--- /dev/null
+++ b/src/internal/components/card/styles.scss
@@ -0,0 +1,106 @@
+/*
+ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ SPDX-License-Identifier: Apache-2.0
+*/
+
+@use 'sass:math';
+
+@use '../../styles' as styles;
+@use '../../styles/tokens' as awsui;
+@use './motion';
+
+@mixin card-style {
+ border-start-start-radius: awsui.$border-radius-container;
+ border-start-end-radius: awsui.$border-radius-container;
+ border-end-start-radius: awsui.$border-radius-container;
+ border-end-end-radius: awsui.$border-radius-container;
+ box-sizing: border-box;
+
+ &::before {
+ @include styles.base-pseudo-element;
+ // Reset border color to prevent it from flashing black during card selection animation
+ border-color: transparent;
+ border-block-start: awsui.$border-container-top-width solid awsui.$color-border-container-top;
+ border-start-start-radius: awsui.$border-radius-container;
+ border-start-end-radius: awsui.$border-radius-container;
+ border-end-start-radius: awsui.$border-radius-container;
+ border-end-end-radius: awsui.$border-radius-container;
+ z-index: 1;
+ }
+
+ &::after {
+ @include styles.base-pseudo-element;
+ border-start-start-radius: awsui.$border-radius-container;
+ border-start-end-radius: awsui.$border-radius-container;
+ border-end-start-radius: awsui.$border-radius-container;
+ border-end-end-radius: awsui.$border-radius-container;
+ }
+ &:not(.refresh)::after {
+ box-shadow: awsui.$shadow-container;
+ }
+ &.refresh::after {
+ border-block: solid awsui.$border-divider-section-width awsui.$color-border-divider-default;
+ border-inline: solid awsui.$border-divider-section-width awsui.$color-border-divider-default;
+ }
+}
+
+.root {
+ @include styles.styles-reset();
+ display: flex;
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ margin-block: 0;
+ margin-inline: 0;
+ padding-block: 0;
+ padding-inline: 0;
+ list-style: none;
+}
+
+.card-inner {
+ position: relative;
+ background-color: awsui.$color-background-container-content;
+ margin-block-start: 0;
+ margin-block-end: awsui.$space-grid-gutter;
+ margin-inline-start: awsui.$space-grid-gutter;
+ margin-inline-end: 0;
+ inline-size: 100%;
+ min-inline-size: 0;
+ @include card-style;
+}
+
+.header {
+ @include styles.font-heading-s;
+ padding-block-start: 8px;
+ padding-inline: awsui.$space-card-horizontal;
+ display: flex;
+ &-inner {
+ padding-block-start: 4px;
+ inline-size: 100%;
+ display: inline-block;
+ }
+}
+
+.body:not(.no-padding) {
+ padding-block-end: awsui.$space-card-vertical;
+ padding-inline: awsui.$space-card-horizontal;
+}
+
+.with-action {
+ > .card-inner > .card-header {
+ inline-size: 90%;
+ }
+}
+
+.active {
+ > .card-inner {
+ background-color: awsui.$color-background-item-selected;
+ &::before {
+ border-block: awsui.$border-item-width solid awsui.$color-border-item-selected;
+ border-inline: awsui.$border-item-width solid awsui.$color-border-item-selected;
+ }
+ }
+}
+
+.action {
+ flex-shrink: 0;
+}
diff --git a/src/test-utils/dom/cards/index.ts b/src/test-utils/dom/cards/index.ts
index adc16584c3..574a6f7250 100644
--- a/src/test-utils/dom/cards/index.ts
+++ b/src/test-utils/dom/cards/index.ts
@@ -8,6 +8,7 @@ import PaginationWrapper from '../pagination';
import TextFilterWrapper from '../text-filter';
import styles from '../../../cards/styles.selectors.js';
+import cardStyles from '../../../internal/components/card/styles.selectors.js';
import tableStyles from '../../../table/styles.selectors.js';
class CardSectionWrapper extends ComponentWrapper {
@@ -31,7 +32,7 @@ class CardWrapper extends ComponentWrapper {
}
findCardHeader(): ElementWrapper | null {
- return this.findByClassName(styles['card-header-inner']);
+ return this.find(`:is(.${cardStyles['header-inner']}, .${styles['card-header-inner']})`);
}
findSelectionArea(): ElementWrapper | null {
@@ -45,11 +46,11 @@ export default class CardsWrapper extends ComponentWrapper {
private containerWrapper = new ContainerWrapper(this.getElement());
findItems(): Array {
- return this.findAllByClassName(styles.card).map(c => new CardWrapper(c.getElement()));
+ return this.findAll(`:is(.${cardStyles.root}, .${styles.card})`).map(c => new CardWrapper(c.getElement()));
}
findSelectedItems(): Array {
- return this.findAllByClassName(styles['card-selected']).map(c => new CardWrapper(c.getElement()));
+ return this.findAllByClassName(cardStyles.active).map(c => new CardWrapper(c.getElement()));
}
findHeader(): ElementWrapper | null {