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;
+ }
}