From 279452b909ab11407cc198996b6f697021e2227b Mon Sep 17 00:00:00 2001 From: Andrew Holloway Date: Mon, 31 Jul 2023 16:46:37 -0500 Subject: [PATCH] fix(Avatar): support display names using emoji and multi-byte --- src/components/Avatar/Avatar.module.css | 2 + src/components/Avatar/Avatar.stories.ts | 61 ++++++++++++++++++- src/components/Avatar/Avatar.tsx | 19 +++++- .../Avatar/__snapshots__/Avatar.test.ts.snap | 30 +++++++++ 4 files changed, 106 insertions(+), 6 deletions(-) diff --git a/src/components/Avatar/Avatar.module.css b/src/components/Avatar/Avatar.module.css index 44e68ee0ed..95dbf4d56c 100644 --- a/src/components/Avatar/Avatar.module.css +++ b/src/components/Avatar/Avatar.module.css @@ -16,6 +16,8 @@ color: var(--eds-theme-color-text-neutral-strong); background-color: var(--eds-theme-color-background-neutral-medium); + + white-space: nowrap; } .avatar:focus-visible { diff --git a/src/components/Avatar/Avatar.stories.ts b/src/components/Avatar/Avatar.stories.ts index 4dea7a06ea..e23b410ca4 100644 --- a/src/components/Avatar/Avatar.stories.ts +++ b/src/components/Avatar/Avatar.stories.ts @@ -19,31 +19,37 @@ export default { type Args = React.ComponentProps; -export const Default: StoryObj = { - args: {}, -}; +export const Default: StoryObj = {}; export const Small: StoryObj = { args: { size: 'sm', + shape: 'circle', + variant: 'icon', }, }; export const Medium: StoryObj = { args: { size: 'md', + shape: 'circle', + variant: 'icon', }, }; export const Large: StoryObj = { args: { size: 'lg', + shape: 'circle', + variant: 'icon', }, }; export const Square: StoryObj = { args: { shape: 'square', + size: 'md', + variant: 'icon', }, }; @@ -51,17 +57,24 @@ export const UsingImage: StoryObj = { args: { variant: 'image', src: `data:image/svg+xml,%3csvg width='38' height='37' viewBox='0 0 38 37' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M19 17.9417C16.4333 17.9417 14.3333 17.125 12.7 15.4917C11.0667 13.8583 10.25 11.7583 10.25 9.19168C10.25 6.62502 11.0667 4.52501 12.7 2.89168C14.3333 1.25835 16.4333 0.441681 19 0.441681C21.5667 0.441681 23.6667 1.25835 25.3 2.89168C26.9333 4.52501 27.75 6.62502 27.75 9.19168C27.75 11.7583 26.9333 13.8583 25.3 15.4917C23.6667 17.125 21.5667 17.9417 19 17.9417ZM0.333344 36.6667V31.1833C0.333344 29.7056 0.702788 28.4417 1.44168 27.3917C2.18057 26.3417 3.13334 25.5445 4.30001 25C6.90557 23.8333 9.40418 22.9583 11.7958 22.375C14.1875 21.7917 16.5889 21.5 19 21.5C21.4111 21.5 23.8028 21.8014 26.175 22.4042C28.5472 23.007 31.0361 23.8722 33.6417 25C34.8472 25.5445 35.8195 26.3417 36.5583 27.3917C37.2972 28.4417 37.6667 29.7056 37.6667 31.1833V36.6667H0.333344Z' fill='%235D6369'/%3e%3c/svg%3e`, + size: 'md', + shape: 'circle', }, }; export const WithCustomLabel: StoryObj = { args: { ariaLabel: 'Custom label for avatar', + size: 'md', + shape: 'circle', + variant: 'icon', }, }; export const UsingInitials: StoryObj = { args: { + size: 'md', + shape: 'circle', variant: 'initials', user: { fullName: 'John Smith', @@ -70,3 +83,45 @@ export const UsingInitials: StoryObj = { }, }, }; + +export const UsingEmoji: StoryObj = { + args: { + shape: 'circle', + variant: 'initials', + user: { + fullName: 'Young Yarn Lad', + displayName: '🧶👦🏽', + id: '12345', + hasArbitraryMetadata: true, + }, + size: 'lg', + }, +}; + +export const WhenImageVariantMissingSource: StoryObj = { + args: { + shape: 'circle', + variant: 'image', + user: { + fullName: 'Young Yarn Lad', + id: '12345', + hasArbitraryMetadata: true, + moreMetadata: 123, + }, + size: 'lg', + }, +}; + +export const UsingMultibyteUnicode: StoryObj = { + args: { + shape: 'circle', + variant: 'image', + user: { + fullName: '你好世界', + id: '12345', + hasArbitraryMetadata: true, + moreMetadata: 123, + }, + size: 'lg', + }, +}; diff --git a/src/components/Avatar/Avatar.tsx b/src/components/Avatar/Avatar.tsx index 7bc2155c7e..25fbc9341a 100644 --- a/src/components/Avatar/Avatar.tsx +++ b/src/components/Avatar/Avatar.tsx @@ -12,6 +12,10 @@ export type UserData = { * User ID associated with the attached user */ id?: string | number; + /** + * The display shortcut for the user name. Can be initials, emoji, or other text symbols (recommended max: 2) + */ + displayName?: string; /** * Additional data for an attached user (email, etc.) */ @@ -56,7 +60,7 @@ function getInitials(fromName: string): string { * - User's name has a middle name or initial: John C. Smith * - User's Name has dashes in it */ - return fromName + const initials = fromName .split(' ') .map((part) => part[0]) .reduce( @@ -65,6 +69,7 @@ function getInitials(fromName: string): string { '', ) .toUpperCase(); + return initials; } /** @@ -94,6 +99,13 @@ export const Avatar = ({ ariaLabel ?? `Avatar for ${user ? '' : 'unknown '}user ${user?.fullName || ''}`; + // use the display name if prop is provided. Otherwise, try to calculate initials + let avatarDisplayName = user ? getInitials(user.fullName) : '??'; + + if (user?.displayName) { + avatarDisplayName = user.displayName; + } + return (
- {variant === 'initials' && (user ? getInitials(user.fullName) : '??')} + {variant === 'initials' && avatarDisplayName} {variant === 'icon' && } - {variant === 'image' && ( + {variant === 'image' && src && ( user )} + {variant === 'image' && !src && avatarDisplayName}
); }; diff --git a/src/components/Avatar/__snapshots__/Avatar.test.ts.snap b/src/components/Avatar/__snapshots__/Avatar.test.ts.snap index 51c08d7227..151157a59f 100644 --- a/src/components/Avatar/__snapshots__/Avatar.test.ts.snap +++ b/src/components/Avatar/__snapshots__/Avatar.test.ts.snap @@ -100,6 +100,16 @@ exports[` Square story renders snapshot 1`] = ` `; +exports[` UsingEmoji story renders snapshot 1`] = ` + +`; + exports[` UsingImage story renders snapshot 1`] = `
UsingInitials story renders snapshot 1`] = `
`; +exports[` UsingMultibyteUnicode story renders snapshot 1`] = ` + +`; + +exports[` WhenImageVariantMissingSource story renders snapshot 1`] = ` + +`; + exports[` WithCustomLabel story renders snapshot 1`] = `