From 4a1c15c1e9d29a6aec88f66bc0e427d1c5d4cd1d Mon Sep 17 00:00:00 2001 From: Qi <474021214@qq.com> Date: Thu, 20 Apr 2023 17:41:29 +0800 Subject: [PATCH] feat: modify default avatar (#2034) --- .../panel/general/style.ts | 1 + .../workspace-avatar/DefaultAvatar.tsx | 69 +++++++++ .../components/workspace-avatar/index.css.ts | 134 ++++++++++++++++-- .../src/components/workspace-avatar/index.tsx | 42 +----- tests/parallels/local-first-avatar.spec.ts | 3 - 5 files changed, 200 insertions(+), 49 deletions(-) create mode 100644 packages/component/src/components/workspace-avatar/DefaultAvatar.tsx diff --git a/apps/web/src/components/affine/workspace-setting-detail/panel/general/style.ts b/apps/web/src/components/affine/workspace-setting-detail/panel/general/style.ts index e941271d2..aa7249387 100644 --- a/apps/web/src/components/affine/workspace-setting-detail/panel/general/style.ts +++ b/apps/web/src/components/affine/workspace-setting-detail/panel/general/style.ts @@ -42,6 +42,7 @@ export const StyledAvatar = styled('div')( backgroundColor: 'rgba(60, 61, 63, 0.5)', justifyContent: 'center', alignItems: 'center', + zIndex: 10, }, }; } diff --git a/packages/component/src/components/workspace-avatar/DefaultAvatar.tsx b/packages/component/src/components/workspace-avatar/DefaultAvatar.tsx new file mode 100644 index 000000000..68185202b --- /dev/null +++ b/packages/component/src/components/workspace-avatar/DefaultAvatar.tsx @@ -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>(); + + const [topColor, middleColor, bottomColor] = colors; + const [isHover, setIsHover] = useState(false); + + return ( +
{ + timer.current = setTimeout(() => { + setIsHover(true); + }, 300); + }} + onMouseLeave={() => { + clearTimeout(timer.current); + setIsHover(false); + }} + > +
+
+
+
+ ); +}; +export default DefaultAvatar; diff --git a/packages/component/src/components/workspace-avatar/index.css.ts b/packages/component/src/components/workspace-avatar/index.css.ts index 9e4473fe9..edb626672 100644 --- a/packages/component/src/components/workspace-avatar/index.css.ts +++ b/packages/component/src/components/workspace-avatar/index.css.ts @@ -1,4 +1,4 @@ -import { style } from '@vanilla-extract/css'; +import { keyframes, style } from '@vanilla-extract/css'; export const avatarStyle = style({ width: '100%', height: '100%', @@ -16,16 +16,130 @@ export const avatarImageStyle = style({ 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%', height: '100%', - border: '1px solid #fff', - textAlign: 'center', - color: '#fff', + position: 'relative', borderRadius: '50%', - display: 'inline-flex', - lineHeight: '1', - justifyContent: 'center', - alignItems: 'center', - userSelect: 'none', + overflow: 'hidden', +}); + +export const DefaultAvatarMiddleItemStyle = style({ + 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', }); diff --git a/packages/component/src/components/workspace-avatar/index.tsx b/packages/component/src/components/workspace-avatar/index.tsx index 7bab7a8e9..f8e5c5016 100644 --- a/packages/component/src/components/workspace-avatar/index.tsx +++ b/packages/component/src/components/workspace-avatar/index.tsx @@ -10,28 +10,8 @@ import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite- import clsx from 'clsx'; import type React from 'react'; -import { avatarImageStyle, avatarStyle, avatarTextStyle } 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; -} +import { DefaultAvatar } from './DefaultAvatar'; +import { avatarImageStyle, avatarStyle } from './index.css'; export type WorkspaceAvatarProps = { size?: number; @@ -64,13 +44,8 @@ export const BlockSuiteWorkspaceAvatar: React.FC = ({ }} > - - {name.substring(0, 1)} + + ); @@ -99,13 +74,8 @@ export const WorkspaceAvatar: React.FC = ({ width: size, }} > - - A + + ); diff --git a/tests/parallels/local-first-avatar.spec.ts b/tests/parallels/local-first-avatar.spec.ts index 607526845..d1c0bb862 100644 --- a/tests/parallels/local-first-avatar.spec.ts +++ b/tests/parallels/local-first-avatar.spec.ts @@ -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-card').nth(0).click(); 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-card').nth(1).click(); const blobUrl = await page