mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-22 19:11:32 +03:00
feat: modify default avatar (#2034)
This commit is contained in:
parent
f8d1513bb6
commit
4a1c15c1e9
@ -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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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;
|
@ -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',
|
||||||
});
|
});
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user