Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Update Avatar to use createStencil and createComponent #2793

Merged
merged 40 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a04e476
Starting refactor of Avatar component using storybook - WIP as breaki…
Jun 24, 2024
1de3d56
Merge branch 'pre-release/major' into avatarRefactorMajor
Jun 26, 2024
0cd1908
feature(Avatar): updated size prop, variant edits, opacity edits - st…
Jun 26, 2024
222f486
fix: must remove hideMouseFocus or breaks build
Jun 27, 2024
b6ec992
Merge branch 'prerelease/major' into avatarRefactorMajor
mannycarrera4 Jun 28, 2024
3693d05
Update modules/react/avatar/lib/Avatar.tsx
mannycarrera4 Jun 28, 2024
184aea1
fix: Update to clean up stencils
Jul 1, 2024
68e2528
fix: updated stencils to be in one main stencil + modified background…
Jul 7, 2024
8a4ecd0
fix: uncommented iconcolor to fix build
Jul 7, 2024
9e2ad0e
fix: Get sizing working with number
Jul 12, 2024
74d5cec
Merge branch 'pre-release/major' into avatarRefactorMajor
Jul 16, 2024
2beea85
Merge branch 'pre-release/major' into avatarRefactorMajor
Jul 23, 2024
6cf7f57
fix: updated dynamic sizing + objectFit + data-element naming
Jul 23, 2024
fdd478c
fix: update stories/spec + typing/overload
Jul 24, 2024
b71f22e
fix: updated dynamic size to include px/rem
Jul 24, 2024
90aef66
fix: modified background to be similar to old API
Jul 24, 2024
458f03e
Merge branch 'pre-release/major' into avatarRefactorMajor
Jul 24, 2024
2c2739b
fix: updated story for dynamic sizing to add px to rem conversion fnx
Jul 24, 2024
a8513fe
fix: update readme + use cases - OUTSTANDING: pillAvatar
Jul 26, 2024
025af9b
fix: remove elementType and replace with css is(button)
Jul 26, 2024
cd12461
fix: updated story to match mdx
Jul 26, 2024
9bf8b2a
fix: updated docs to match other components
Jul 29, 2024
ef3912f
Apply suggestions from code review
mannycarrera4 Jul 29, 2024
a9a650b
fix: onClick removal + size update
Jul 29, 2024
625ed23
Merge branch 'avatarRefactorMajor' of https://github.com/kaconant/can…
Jul 29, 2024
ebee18f
fix: Clean up docs and code
Jul 29, 2024
8b79ede
Merge branch 'avatarRefactorMajor' of https://github.com/kaconant/can…
Jul 29, 2024
1788110
fix: Clean up examples
Jul 29, 2024
df2205c
Merge branch 'pre-release/major' into avatarRefactorMajor
Jul 30, 2024
241952e
Merge branch 'prerelease/major' into avatarRefactorMajor
mannycarrera4 Jul 30, 2024
a5571f9
test: Update selector in test
Jul 30, 2024
9dcfa13
test: Update test to go to correct story
Jul 31, 2024
3877318
docs: Add info to upgrade guide for avatar
Aug 1, 2024
a23e1a8
Update modules/react/avatar/README.md
kaconant Aug 6, 2024
a56df73
Update modules/react/avatar/stories/avatar/Avatar.stories.mdx
kaconant Aug 6, 2024
7683413
Update modules/react/avatar/stories/avatar/Avatar.stories.mdx
kaconant Aug 6, 2024
12ffabf
Update modules/react/avatar/stories/avatar/Avatar.stories.mdx
kaconant Aug 6, 2024
54d4c68
chore: update story for image
Aug 6, 2024
e41df0c
fix: updated links for objectfit
Aug 7, 2024
e116f27
fix: missing word in avatar interface
Aug 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
321 changes: 193 additions & 128 deletions modules/react/avatar/lib/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,12 @@
import React, {useState} from 'react';
import {Property} from 'csstype';
import {styled, focusRing, hideMouseFocus} from '@workday/canvas-kit-react/common';
import isPropValid from '@emotion/is-prop-valid';
import {borderRadius, colors} from '@workday/canvas-kit-react/tokens';
import {SystemIconCircle, SystemIconCircleSize} from '@workday/canvas-kit-react/icon';
import {createComponent, focusRing, hideMouseFocus} from '@workday/canvas-kit-react/common';
mannycarrera4 marked this conversation as resolved.
Show resolved Hide resolved
import {cssVar, createStencil, calc} from '@workday/canvas-kit-styling';
import {mergeStyles} from '@workday/canvas-kit-react/layout';
import {borderRadius} from '@workday/canvas-kit-react/tokens';
import {SystemIconCircleSize, SystemIcon, systemIconStencil} from '@workday/canvas-kit-react/icon';
import {userIcon} from '@workday/canvas-system-icons-web';

export enum AvatarVariant {
Light,
Dark,
}

export interface AvatarProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
/**
* The variant of the Avatar default state. Accepts `Light` or `Dark`.
* @default AvatarVariant.Light
*/
variant?: AvatarVariant;
/**
* The size of the Avatar.
* @default SystemIconCircleSize.m
*/
size?: SystemIconCircleSize | number;
/**
* The alt text of the Avatar image. This prop is also used for the aria-label
* @default Avatar
*/
altText?: string;
/**
* The url of the Avatar image.
*/
url?: string;
/**
* The alternative container type for the button. Uses Emotion's special `as` prop.
* Will render an `div` tag instead of a `button` when defined.
*/
as?: 'div';
/**
* The object-fit CSS property sets how the content of a replaced element,
* such as an `<img>` or `<video>`, should be resized to fit its container.
* See [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit).
* If your image is not a square, you can use this property to ensure the image is rendered properly.
*/
objectFit?: Property.ObjectFit;
}
import {base, system} from '@workday/canvas-tokens-web';

/**
* Used to get the props of the div version of an avatar
Expand All @@ -57,89 +20,186 @@ type AvatarDivProps = Omit<AvatarProps, keyof React.ButtonHTMLAttributes<HTMLBut
type AvatarOverload = {
(props: {as: 'div'} & AvatarDivProps & {ref?: React.Ref<HTMLElement>}): React.ReactElement;
(props: Omit<AvatarProps, 'as'> & {ref?: React.Ref<HTMLButtonElement>}): React.ReactElement;
Variant: typeof AvatarVariant;
Variant: 'light' | 'dark';
Size: typeof SystemIconCircleSize;
};

const fadeTransition = 'opacity 150ms linear';
export interface AvatarProps {
/**
* The variant of the Avatar default state. Accepts `Light` or `Dark`.
* @default 'light'
*/
variant?: 'light' | 'dark';
/**
* The size of the Avatar.
* @default `medium`
*/
size?: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge' | 'extraExtraLarge';
/**
* The alt text of the Avatar image. This prop is also used for the aria-label
* @default Avatar
*/
altText?: string;
background?: string;
url?: string;
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
kaconant marked this conversation as resolved.
Show resolved Hide resolved
objectFit?: Property.ObjectFit;
}

const StyledContainer = styled('button', {
shouldForwardProp: prop => isPropValid(prop) && prop !== 'size',
})<Pick<AvatarProps, 'size' | 'onClick'>>(
{
background: colors.soap200,
const avatarContainerStencil = createStencil({
base: {
background: base.soap200,
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: 0,
border: 0,
boxSizing: 'border-box',
overflow: 'hidden',
borderRadius: borderRadius.circle,
cursor: 'default',
borderRadius: system.shape.round,
'&:not([disabled])': {
'&:focus': {
outline: 'none',
...focusRing({separation: 2}),
},
},
...hideMouseFocus,
// NOTE: Why does this break the build?
// ...hideMouseFocus,
},
({size}) => ({
height: size,
width: size,
}),
({onClick}) => ({
cursor: onClick ? 'pointer' : 'default',
})
);

const StyledStack = styled('span')<Pick<AvatarProps, 'size'>>(
{
position: 'absolute',
top: 0,
left: 0,
modifiers: {
variant: {
light: {
backgroundColor: system.color.bg.alt.default,
},
dark: {
backgroundColor: system.color.bg.primary.default,
[systemIconStencil.vars.color]: system.color.fg.inverse,
},
},
size: {
extraSmall: {
width: system.space.x4,
height: system.space.x4,
},
small: {
width: system.space.x6,
height: system.space.x6,
},
medium: {
width: system.space.x8,
height: system.space.x8,
},
large: {
width: system.space.x10,
height: system.space.x10,
},
extraLarge: {
width: system.space.x16,
height: system.space.x16,
},
extraExtraLarge: {
width: calc.multiply(system.space.x10, 3),
height: calc.multiply(system.space.x10, 3),
},
},
hasOnClick: {
true: {
cursor: 'pointer',
},
false: {
cursor: 'default',
},
},
},
({size}) => ({
height: size,
width: size,
})
);
});

const StyledIcon = styled(SystemIconCircle)<{isImageLoaded: boolean}>(
{
transition: fadeTransition,
const avatarIconStencil = createStencil({
vars: {size: ''},
base: ({size}) => ({
transition: 'opacity 150ms linear',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: cssVar(size, system.space.x8),
height: cssVar(size, system.space.x8),
}),
modifiers: {
variant: {
light: {
backgroundColor: system.color.bg.alt.default,
},
dark: {
backgroundColor: system.color.bg.primary.default,
[systemIconStencil.vars.color]: system.color.fg.inverse,
},
},
size: {
extraSmall: {
[systemIconStencil.vars.size]: calc.multiply(system.space.x4, 0.625),
},
small: {
[systemIconStencil.vars.size]: calc.multiply(system.space.x6, 0.625),
},
medium: {
[systemIconStencil.vars.size]: calc.multiply(system.space.x8, 0.625),
},
large: {
[systemIconStencil.vars.size]: calc.multiply(system.space.x10, 0.625),
},
extraLarge: {
[systemIconStencil.vars.size]: calc.multiply(system.space.x16, 0.625),
},
extraExtraLarge: {
[systemIconStencil.vars.size]: calc.multiply(calc.multiply(system.space.x10, 3), 0.625),
},
},
isImageLoaded: {
true: {
opacity: 0,
},
false: {
opacity: 1,
},
},
},
defaultModifiers: {
isImageLoaded: 'false',
},
({isImageLoaded}) => ({
opacity: isImageLoaded ? 0 : 1,
})
);
});

const StyledImage = styled('img')<{isLoaded: boolean; objectFit?: Property.ObjectFit}>(
{
// NOTE: Objectfit not working
const avatarImageStencil = createStencil({
vars: {objectFit: ''},
base: ({objectFit}) => ({
position: 'absolute',
width: '100%',
height: '100%',
borderRadius: borderRadius.circle,
transition: fadeTransition,
transition: 'opacity 150ms linear',
objectFit: cssVar(objectFit, 'container'),
}),
modifiers: {
isImageLoaded: {
true: {
opacity: 1,
},
false: {
opacity: 0,
},
},
},
({isLoaded, objectFit}) => ({
opacity: isLoaded ? 1 : 0,
objectFit,
})
);
defaultModifiers: {
isImageLoaded: 'false',
},
});

export const Avatar: AvatarOverload = React.forwardRef(
(
{
variant = AvatarVariant.Light,
size = SystemIconCircleSize.m,
altText = 'Avatar',
url,
onClick,
objectFit,
...elemProps
}: AvatarProps,
ref: React.Ref<HTMLButtonElement>
export const Avatar: AvatarOverload = createComponent('button')({
displayName: 'Avatar',
Component: (
{variant, size, altText, url, onClick, objectFit, background, ...elemProps}: AvatarProps,
ref,
Element
) => {
const [imageLoaded, setImageLoaded] = useState(false);

Expand All @@ -153,40 +213,45 @@ export const Avatar: AvatarOverload = React.forwardRef(
setImageLoaded(false);
}, [url]);

const background = variant === AvatarVariant.Dark ? colors.blueberry400 : colors.soap300;
// NOTE: Background color isn't dynamic - only default is variant light or dark

return (
<StyledContainer
size={size}
<Element
ref={ref}
aria-label={altText}
onClick={onClick}
mannycarrera4 marked this conversation as resolved.
Show resolved Hide resolved
disabled={onClick ? false : true}
ref={ref}
{...elemProps}
{...mergeStyles(elemProps, [
avatarContainerStencil({
variant,
size,
hasOnClick: onClick !== undefined,
}),
])}
>
<StyledStack size={size}>
<StyledIcon
icon={userIcon}
background={background}
size={size}
isImageLoaded={imageLoaded}
/>
</StyledStack>
<SystemIcon
icon={userIcon}
{...mergeStyles(elemProps, [
avatarIconStencil({
variant,
isImageLoaded: imageLoaded,
size,
}),
])}
/>
{url && (
<StyledStack size={size}>
<StyledImage
src={url}
alt={altText}
onLoad={loadImage}
isLoaded={imageLoaded}
objectFit={objectFit}
/>
</StyledStack>
<img
{...mergeStyles(elemProps, [
avatarImageStencil({
isImageLoaded: imageLoaded,
objectFit,
}),
])}
src={url}
alt={altText}
onLoad={loadImage}
/>
)}
</StyledContainer>
</Element>
);
}
) as any; // AvatarProps and forwardRef signatures are incompatible, so we must force cast

Avatar.Variant = AvatarVariant;
Avatar.Size = SystemIconCircleSize;
},
});
Loading
Loading