feat: modify default avatar (#2034)

This commit is contained in:
Qi 2023-04-20 17:41:29 +08:00 committed by GitHub
parent f8d1513bb6
commit 4a1c15c1e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 200 additions and 49 deletions

View File

@ -42,6 +42,7 @@ export const StyledAvatar = styled('div')(
backgroundColor: 'rgba(60, 61, 63, 0.5)', backgroundColor: 'rgba(60, 61, 63, 0.5)',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
zIndex: 10,
}, },
}; };
} }

View File

@ -0,0 +1,69 @@
import clsx from 'clsx';
import { useMemo, useRef, useState } from 'react';
import {
DefaultAvatarBottomItemStyle,
DefaultAvatarBottomItemWithAnimationStyle,
DefaultAvatarContainerStyle,
DefaultAvatarMiddleItemStyle,
DefaultAvatarMiddleItemWithAnimationStyle,
DefaultAvatarTopItemStyle,
} from './index.css';
const colorsSchema = [
['#FF0000', '#FF00E5', '#FFAE73'],
['#FF5C00', '#FFC700', '#FFE073'],
['#FFDA16', '#FFFBA6', '#FFBE73'],
['#8CD317', '#FCFF5C', '#67CAE9'],
['#28E19F', '#89FFC6', '#39A880'],
['#35B7E0', '#77FFCE', '#5076FF'],
['#3D39FF', '#77BEFF', '#3502FF'],
['#BD08EB', '#755FFF', '#6967E4'],
];
export const DefaultAvatar = ({ name: propsName }: { name: string }) => {
// Sometimes name is ' '
const name = propsName || 'A';
const colors = useMemo(() => {
const index = name[0].toUpperCase().charCodeAt(0);
return colorsSchema[index % colorsSchema.length];
}, [name]);
const timer = useRef<ReturnType<typeof setTimeout>>();
const [topColor, middleColor, bottomColor] = colors;
const [isHover, setIsHover] = useState(false);
return (
<div
className={DefaultAvatarContainerStyle}
onMouseEnter={() => {
timer.current = setTimeout(() => {
setIsHover(true);
}, 300);
}}
onMouseLeave={() => {
clearTimeout(timer.current);
setIsHover(false);
}}
>
<div
className={DefaultAvatarTopItemStyle}
style={{ background: bottomColor }}
></div>
<div
className={clsx(DefaultAvatarMiddleItemStyle, {
[DefaultAvatarMiddleItemWithAnimationStyle]: isHover,
})}
style={{ background: middleColor }}
></div>
<div
className={clsx(DefaultAvatarBottomItemStyle, {
[DefaultAvatarBottomItemWithAnimationStyle]: isHover,
})}
style={{ background: topColor }}
></div>
</div>
);
};
export default DefaultAvatar;

View File

@ -1,4 +1,4 @@
import { style } from '@vanilla-extract/css'; import { keyframes, style } from '@vanilla-extract/css';
export const avatarStyle = style({ export const avatarStyle = style({
width: '100%', width: '100%',
height: '100%', height: '100%',
@ -16,16 +16,130 @@ export const avatarImageStyle = style({
objectPosition: 'center', objectPosition: 'center',
}); });
export const avatarTextStyle = style({ const bottomAnimation = keyframes({
'0%': {
top: '-44%',
left: '-11%',
transform: 'matrix(-0.29, -0.96, 0.94, -0.35, 0, 0)',
},
'16%': {
left: '-18%',
top: '-51%',
transform: 'matrix(-0.73, -0.69, 0.64, -0.77, 0, 0)',
},
'32%': {
left: '-7%',
top: '-40%',
transform: 'matrix(-0.97, -0.23, 0.16, -0.99, 0, 0)',
},
'48%': {
left: '-15%',
top: '-39%',
transform: 'matrix(-0.88, 0.48, -0.6, -0.8, 0, 0)',
},
'64%': {
left: '-7%',
top: '-40%',
transform: 'matrix(-0.97, -0.23, 0.16, -0.99, 0, 0)',
},
'80%': {
left: '-18%',
top: '-51%',
transform: 'matrix(-0.73, -0.69, 0.64, -0.77, 0, 0)',
},
'100%': {
top: '-44%',
left: '-11%',
transform: 'matrix(-0.29, -0.96, 0.94, -0.35, 0, 0)',
},
});
const middleAnimation = keyframes({
'0%': {
left: '-30px',
top: '-30px',
transform: 'matrix(-0.48, -0.88, 0.8, -0.6, 0, 0)',
},
'16%': {
left: '-37px',
top: '-37px',
transform: 'matrix(-0.86, -0.52, 0.39, -0.92, 0, 0)',
},
'32%': {
left: '-20px',
top: '-10px',
transform: 'matrix(-1, -0.02, -0.12, -0.99, 0, 0)',
},
'48%': {
left: '-27px',
top: '-2px',
transform: 'matrix(-0.88, 0.48, -0.6, -0.8, 0, 0)',
},
'64%': {
left: '-20px',
top: '-10px',
transform: 'matrix(-1, -0.02, -0.12, -0.99, 0, 0)',
},
'80%': {
left: '-37px',
top: '-37px',
transform: 'matrix(-0.86, -0.52, 0.39, -0.92, 0, 0)',
},
'100%': {
left: '-30px',
top: '-30px',
transform: 'matrix(-0.48, -0.88, 0.8, -0.6, 0, 0)',
},
});
export const DefaultAvatarContainerStyle = style({
width: '100%', width: '100%',
height: '100%', height: '100%',
border: '1px solid #fff', position: 'relative',
textAlign: 'center',
color: '#fff',
borderRadius: '50%', borderRadius: '50%',
display: 'inline-flex', overflow: 'hidden',
lineHeight: '1', });
justifyContent: 'center',
alignItems: 'center', export const DefaultAvatarMiddleItemStyle = style({
userSelect: 'none', width: '83%',
height: '81%',
position: 'absolute',
left: '-30%',
top: '-30%',
transform: 'matrix(-0.48, -0.88, 0.8, -0.6, 0, 0)',
opacity: '0.8',
filter: 'blur(12px)',
transformOrigin: 'center center',
animation: `${middleAnimation} 3s ease-in-out forwards infinite`,
animationPlayState: 'paused',
});
export const DefaultAvatarMiddleItemWithAnimationStyle = style({
animationPlayState: 'running',
});
export const DefaultAvatarBottomItemStyle = style({
width: '98%',
height: '97%',
position: 'absolute',
top: '-44%',
left: '-11%',
transform: 'matrix(-0.29, -0.96, 0.94, -0.35, 0, 0)',
opacity: '0.8',
filter: 'blur(12px)',
transformOrigin: 'center center',
willChange: 'left, top, transform',
animation: `${bottomAnimation} 3s ease-in-out forwards infinite`,
animationPlayState: 'paused',
});
export const DefaultAvatarBottomItemWithAnimationStyle = style({
animationPlayState: 'running',
});
export const DefaultAvatarTopItemStyle = style({
width: '104%',
height: '94%',
position: 'absolute',
right: '-30%',
top: '-30%',
opacity: '0.8',
filter: 'blur(12px)',
transform: 'matrix(-0.28, -0.96, 0.93, -0.37, 0, 0)',
transformOrigin: 'center center',
}); });

View File

@ -10,28 +10,8 @@ import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-
import clsx from 'clsx'; import clsx from 'clsx';
import type React from 'react'; import type React from 'react';
import { avatarImageStyle, avatarStyle, avatarTextStyle } from './index.css'; import { DefaultAvatar } from './DefaultAvatar';
import { avatarImageStyle, avatarStyle } from './index.css';
function stringToColour(str: string) {
str = str || 'affine';
let colour = '#';
let hash = 0;
// str to hash
for (
let i = 0;
i < str.length;
hash = str.charCodeAt(i++) + ((hash << 5) - hash)
);
// int/hash to hex
for (
let i = 0;
i < 3;
colour += ('00' + ((hash >> (i++ * 8)) & 0xff).toString(16)).slice(-2)
);
return colour;
}
export type WorkspaceAvatarProps = { export type WorkspaceAvatarProps = {
size?: number; size?: number;
@ -64,13 +44,8 @@ export const BlockSuiteWorkspaceAvatar: React.FC<BlockSuiteWorkspaceAvatar> = ({
}} }}
> >
<RadixAvatar.Image className={avatarImageStyle} src={avatar} alt={name} /> <RadixAvatar.Image className={avatarImageStyle} src={avatar} alt={name} />
<RadixAvatar.Fallback <RadixAvatar.Fallback>
className={avatarTextStyle} <DefaultAvatar name={name}></DefaultAvatar>
style={{
backgroundColor: stringToColour(name),
}}
>
{name.substring(0, 1)}
</RadixAvatar.Fallback> </RadixAvatar.Fallback>
</RadixAvatar.Root> </RadixAvatar.Root>
); );
@ -99,13 +74,8 @@ export const WorkspaceAvatar: React.FC<WorkspaceAvatarProps> = ({
width: size, width: size,
}} }}
> >
<RadixAvatar.Fallback <RadixAvatar.Fallback>
className={avatarTextStyle} <DefaultAvatar name="A"></DefaultAvatar>
style={{
backgroundColor: stringToColour('A'),
}}
>
A
</RadixAvatar.Fallback> </RadixAvatar.Fallback>
</RadixAvatar.Root> </RadixAvatar.Root>
); );

View File

@ -24,9 +24,6 @@ test('should create a page with a local first avatar', async ({ page }) => {
await page.getByTestId('workspace-name').click(); await page.getByTestId('workspace-name').click();
await page.getByTestId('workspace-card').nth(0).click(); await page.getByTestId('workspace-card').nth(0).click();
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
const text = await page.getByTestId('workspace-avatar').textContent();
// default avatar for default workspace
expect(text).toBe('D');
await page.getByTestId('workspace-name').click(); await page.getByTestId('workspace-name').click();
await page.getByTestId('workspace-card').nth(1).click(); await page.getByTestId('workspace-card').nth(1).click();
const blobUrl = await page const blobUrl = await page