diff --git a/.changeset/fuzzy-rules-pump.md b/.changeset/fuzzy-rules-pump.md new file mode 100644 index 0000000000..9caa4f04f9 --- /dev/null +++ b/.changeset/fuzzy-rules-pump.md @@ -0,0 +1,5 @@ +--- +"@contentful/f36-avatar": patch +--- + +Avatar size and loading skeleton diff --git a/.changeset/hot-ears-crash.md b/.changeset/hot-ears-crash.md new file mode 100644 index 0000000000..2b0d624ac7 --- /dev/null +++ b/.changeset/hot-ears-crash.md @@ -0,0 +1,5 @@ +--- +"@contentful/f36-avatar": feat +--- + +Allow custom size diff --git a/packages/components/avatar/src/Avatar/Avatar.styles.ts b/packages/components/avatar/src/Avatar/Avatar.styles.ts index e3e04f0204..2d0bdb206e 100644 --- a/packages/components/avatar/src/Avatar/Avatar.styles.ts +++ b/packages/components/avatar/src/Avatar/Avatar.styles.ts @@ -1,7 +1,12 @@ import { css } from 'emotion'; import tokens from '@contentful/f36-tokens'; import { type AvatarProps } from './Avatar'; -import { applyMuted, avatarColorMap, type ColorVariant } from './utils'; +import { + applyMuted, + avatarColorMap, + getSizeInPixels, + type ColorVariant, +} from './utils'; export const getColorVariantStyles = (colorVariant: ColorVariant) => { const colorToken: string = avatarColorMap[colorVariant]; @@ -16,14 +21,6 @@ export const getColorVariantStyles = (colorVariant: ColorVariant) => { }; }; -export const convertSizeToPixels = (size: AvatarProps['size']) => - ({ - tiny: '20px', - small: '24px', - medium: '32px', - large: '48px', - }[size]); - const getInitialsFontSize = (sizePixels: string) => Math.round(Number(sizePixels.replace('px', '')) / 2); @@ -37,7 +34,8 @@ export const getAvatarStyles = ({ colorVariant: ColorVariant; }) => { const borderRadius = variant === 'app' ? tokens.borderRadiusSmall : '100%'; - const sizePixels = convertSizeToPixels(size); + const finalSize = getSizeInPixels(size); + const isMuted = colorVariant === 'muted'; return { @@ -50,7 +48,7 @@ export const getAvatarStyles = ({ alignItems: 'center', justifyContent: 'center', fontStretch: 'semi-condensed', - fontSize: `${getInitialsFontSize(sizePixels)}px`, + fontSize: `${getInitialsFontSize(size)}px`, }), image: css({ borderRadius, @@ -58,10 +56,13 @@ export const getAvatarStyles = ({ }), root: css({ borderRadius, - height: sizePixels, + height: finalSize, overflow: 'hidden', position: 'relative', - width: sizePixels, + width: finalSize, + svg: { + borderRadius, + }, '&::after': { borderRadius, bottom: 0, diff --git a/packages/components/avatar/src/Avatar/Avatar.tsx b/packages/components/avatar/src/Avatar/Avatar.tsx index a2397db0c0..d2959cb54b 100644 --- a/packages/components/avatar/src/Avatar/Avatar.tsx +++ b/packages/components/avatar/src/Avatar/Avatar.tsx @@ -9,10 +9,13 @@ import { type WithEnhancedContent, } from '@contentful/f36-tooltip'; -import { convertSizeToPixels, getAvatarStyles } from './Avatar.styles'; -import type { ColorVariant } from './utils'; - -export type Size = 'tiny' | 'small' | 'medium' | 'large'; +import { getAvatarStyles } from './Avatar.styles'; +import { + getSizeInPixels, + type ColorVariant, + type Size, + type SizeInPixel, +} from './utils'; export type Variant = 'app' | 'user'; @@ -23,9 +26,10 @@ export interface AvatarProps extends CommonProps { */ isLoading?: boolean; /** + * Use the available sizes or a numerical custom one, e.g. '52px' * @default 'medium' */ - size?: Size; + size?: Size | SizeInPixel; initials?: string; src?: ImageProps['src']; /** @@ -64,8 +68,8 @@ function _Avatar( ) { // Only render the fallback when `src` is undefined or an empty string const isFallback = Boolean(!isLoading && !src); - const styles = getAvatarStyles({ size, variant, colorVariant }); - const sizePixels = convertSizeToPixels(size); + const finalSize = getSizeInPixels(size); + const styles = getAvatarStyles({ size: finalSize, variant, colorVariant }); const content = (
)} {!!icon && {icon}} diff --git a/packages/components/avatar/src/Avatar/utils.ts b/packages/components/avatar/src/Avatar/utils.ts index a7e1277f86..b3758df525 100644 --- a/packages/components/avatar/src/Avatar/utils.ts +++ b/packages/components/avatar/src/Avatar/utils.ts @@ -1,5 +1,11 @@ import tokens from '@contentful/f36-tokens'; +import { AvatarProps } from './Avatar'; + +export const SIZES = ['tiny', 'small', 'medium', 'large'] as const; +export type Size = (typeof SIZES)[number]; +export type SizeInPixel = `${number}px`; + export type ColorVariant = keyof typeof avatarColorMap; export const avatarColorMap = { @@ -33,3 +39,40 @@ export function applyMuted(color: string): string { // Eventually we should use `color-mix` // return `color-mix(in srgb, ${color}, ${tokens.colorWhite} 50%)`; } + +/** + * Type guard for size variants + * + * @param size + * @returns true/false if the size is a valid size variant + */ +export const isSizeVariant = (size: string): size is Size => { + return SIZES.includes(size as Size); +}; + +/** + * Converts the variant size to pixels + * + * @param size + * @returns the variant size value in pixels + */ +export const convertSizeToPixels = (size: AvatarProps['size']): SizeInPixel => { + const sizes: Record = { + tiny: '20px', + small: '24px', + medium: '32px', + large: '48px', + }; + + return sizes[size]; +}; + +/** + * Utility function to convert the given size variant/custom size to pixels + * + * @param size + * @returns The variant or custom size in pixels, e.g. '32px' + */ +export function getSizeInPixels(size: AvatarProps['size']): SizeInPixel { + return isSizeVariant(size) ? convertSizeToPixels(size) : size; +} diff --git a/packages/components/avatar/src/AvatarGroup/AvatarGroup.styles.ts b/packages/components/avatar/src/AvatarGroup/AvatarGroup.styles.ts index 43d44dc263..5c70958350 100644 --- a/packages/components/avatar/src/AvatarGroup/AvatarGroup.styles.ts +++ b/packages/components/avatar/src/AvatarGroup/AvatarGroup.styles.ts @@ -1,7 +1,7 @@ import { css } from 'emotion'; import tokens from '@contentful/f36-tokens'; import { type AvatarProps } from '../Avatar/'; -import { convertSizeToPixels } from '../Avatar/Avatar.styles'; +import { convertSizeToPixels } from '../Avatar/utils'; export const getAvatarGroupStyles = (size: AvatarProps['size']) => { return { diff --git a/packages/components/avatar/stories/Avatar.stories.tsx b/packages/components/avatar/stories/Avatar.stories.tsx index 1a4814ebb7..703c95f6ab 100644 --- a/packages/components/avatar/stories/Avatar.stories.tsx +++ b/packages/components/avatar/stories/Avatar.stories.tsx @@ -49,6 +49,17 @@ export const Overview: Story = (args) => { size="large" icon={} /> + } + /> + } + /> = (args) => { gap="spacingS" marginBottom="spacingM" > - - - + + + - - - - - + + + + + + + + + + With a broken source, the loading skeleton is also rendered + + + + + + + + + + + + + @@ -187,7 +221,7 @@ export const BorderColors: Story = (args) => { {/* prettier-ignore */} {/* prettier-ignore */} - + {/* prettier-ignore */} {/* prettier-ignore */}