diff --git a/pages/avatar/permutations.page.tsx b/pages/avatar/permutations.page.tsx index bb4f0e4..1d3ce63 100644 --- a/pages/avatar/permutations.page.tsx +++ b/pages/avatar/permutations.page.tsx @@ -1,6 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import SpaceBetween from "@cloudscape-design/components/space-between"; + import { Avatar } from "../../lib/components"; import { TestBed } from "../app/test-bed"; import { ScreenshotArea } from "../screenshot-area"; @@ -38,15 +40,74 @@ export default function AvatarPage() { + + {/* Loading should take prioirty over image */} + +
+ + + {/* Image with tiny width enforce minimum of 28px */} + + + {/* Image with default width of 28px */} + + + {/* Image with custom width should take priority over initials */} + + + {/* Image with custom width and tooltip should take priority over icon */} + + + {/* Icon SVG with custom width not allowed */} + + + {/* Initials with custom width not allowed */} + + + {/* Loading with custom width not allowed */} + + diff --git a/pages/avatar/width-and-image.page.tsx b/pages/avatar/width-and-image.page.tsx new file mode 100644 index 0000000..ce71139 --- /dev/null +++ b/pages/avatar/width-and-image.page.tsx @@ -0,0 +1,31 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { useState } from "react"; + +import Header from "@cloudscape-design/components/header"; +import Input from "@cloudscape-design/components/input"; +import SpaceBetween from "@cloudscape-design/components/space-between"; + +import { Avatar } from "../../lib/components"; + +export default function AvatarImageAndWidth() { + const [url, setURL] = useState( + "https://static1.colliderimages.com/wordpress/wp-content/uploads/2024/08/deadpool-wolverine-hugh-jackman-mask-reveal.jpg", + ); + const [width, setWidth] = useState("100"); + + return ( + +
Input an Image URL and custom size.
+ + + setURL(detail.value)} value={url} /> + + setWidth(detail.value)} value={width} inputMode="numeric" type="number" /> + + + +
+ ); +} diff --git a/src/avatar/interfaces.ts b/src/avatar/interfaces.ts index 0898b9d..d6e03ae 100644 --- a/src/avatar/interfaces.ts +++ b/src/avatar/interfaces.ts @@ -60,6 +60,18 @@ export interface AvatarProps { * If you set both `iconUrl` and `iconSvg`, `iconSvg` will take precedence. */ iconSvg?: React.ReactNode; + + /** + * Specifies the URL of a custom image. If you set both `iconUrl` and `imgUrl`, `imgUrl` will take precedence. + */ + imgUrl?: string; + + /** + * Defines the width of the avatar when using a custom image. + * This value corresponds to the `width` CSS-property and will center and crop images using `object-fit: cover`. + * If no width is provided the avatar will use the default width value. + */ + width?: number; } export namespace AvatarProps { diff --git a/src/avatar/internal.tsx b/src/avatar/internal.tsx index 7a5bb03..cae82a1 100644 --- a/src/avatar/internal.tsx +++ b/src/avatar/internal.tsx @@ -17,11 +17,25 @@ import styles from "./styles.css.js"; export interface InternalAvatarProps extends AvatarProps, InternalBaseComponentProps {} -const AvatarContent = ({ color, loading, initials, iconName, iconSvg, iconUrl, ariaLabel }: AvatarProps) => { +const AvatarContent = ({ + color, + loading, + initials, + iconName, + iconSvg, + iconUrl, + ariaLabel, + width, + imgUrl, +}: AvatarProps) => { if (loading) { return ; } + if (imgUrl) { + return ; + } + if (initials) { const letters = initials.length > 2 ? initials.slice(0, 2) : initials; @@ -32,7 +46,7 @@ const AvatarContent = ({ color, loading, initials, iconName, iconSvg, iconUrl, a return {letters}; } - return ; + return ; }; export default function InternalAvatar({ @@ -44,6 +58,8 @@ export default function InternalAvatar({ iconName, iconSvg, iconUrl, + imgUrl, + width, __internalRootRef = null, ...rest }: InternalAvatarProps) { @@ -51,6 +67,7 @@ export default function InternalAvatar({ const [showTooltip, setShowTooltip] = useState(false); const mergedRef = useMergeRefs(handleRef, __internalRootRef); + const computedSize = imgUrl && width && width > 28 ? width : 28; const tooltipAttributes = { onFocus: () => { @@ -84,6 +101,7 @@ export default function InternalAvatar({ role="img" aria-label={ariaLabel} {...tooltipAttributes} + style={{ height: computedSize, width: computedSize }} > {showTooltip && tooltipText && ( diff --git a/src/avatar/styles.scss b/src/avatar/styles.scss index 7ec4f0d..523adb0 100644 --- a/src/avatar/styles.scss +++ b/src/avatar/styles.scss @@ -10,7 +10,6 @@ $avatar-size: 28px; .root { @include shared.styles-reset; - color: cs.$color-text-avatar; block-size: $avatar-size; inline-size: $avatar-size; @@ -48,4 +47,11 @@ $avatar-size: 28px; block-size: inherit; inline-size: inherit; overflow: hidden; + + img { + @include mixins.border-radius-avatar; + block-size: $avatar-size; + inline-size: $avatar-size; + object-fit: cover; + } }