feat: add tooltip in user & workspace setting (#4260)

This commit is contained in:
Qi
2023-09-13 12:37:25 +08:00
committed by GitHub
parent 5e0fd0b839
commit 32ee946670
5 changed files with 86 additions and 56 deletions
@@ -1,12 +1,15 @@
import { FlexWrapper, Input, toast, Wrapper } from '@affine/component'; import { FlexWrapper, Input, Wrapper } from '@affine/component';
import { pushNotificationAtom } from '@affine/component/notification-center';
import { WorkspaceAvatar } from '@affine/component/workspace-avatar'; import { WorkspaceAvatar } from '@affine/component/workspace-avatar';
import type { AffineOfficialWorkspace } from '@affine/env/workspace'; import type { AffineOfficialWorkspace } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CameraIcon, DoneIcon } from '@blocksuite/icons'; import { CameraIcon } from '@blocksuite/icons';
import { IconButton } from '@toeverything/components/button'; import { Button } from '@toeverything/components/button';
import { Tooltip } from '@toeverything/components/tooltip';
import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url'; import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url';
import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name'; import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name';
import clsx from 'clsx'; import clsx from 'clsx';
import { useSetAtom } from 'jotai';
import { import {
type KeyboardEvent, type KeyboardEvent,
startTransition, startTransition,
@@ -24,6 +27,7 @@ export interface ProfilePanelProps extends WorkspaceSettingDetailProps {
export const ProfilePanel = ({ workspace, isOwner }: ProfilePanelProps) => { export const ProfilePanel = ({ workspace, isOwner }: ProfilePanelProps) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const pushNotification = useSetAtom(pushNotificationAtom);
const [, update] = useBlockSuiteWorkspaceAvatarUrl( const [, update] = useBlockSuiteWorkspaceAvatarUrl(
workspace.blockSuiteWorkspace workspace.blockSuiteWorkspace
@@ -34,13 +38,18 @@ export const ProfilePanel = ({ workspace, isOwner }: ProfilePanelProps) => {
); );
const [input, setInput] = useState<string>(name); const [input, setInput] = useState<string>(name);
const [tooltipContainer, setTooltipContainer] =
useState<HTMLDivElement | null>(null);
const handleUpdateWorkspaceName = useCallback( const handleUpdateWorkspaceName = useCallback(
(name: string) => { (name: string) => {
setName(name); setName(name);
toast(t['Update workspace name success']()); pushNotification({
title: t['Update workspace name success'](),
type: 'success',
});
}, },
[setName, t] [pushNotification, setName, t]
); );
const handleSetInput = useCallback((value: string) => { const handleSetInput = useCallback((value: string) => {
@@ -64,23 +73,34 @@ export const ProfilePanel = ({ workspace, isOwner }: ProfilePanelProps) => {
return ( return (
<div className={style.profileWrapper}> <div className={style.profileWrapper}>
<div className={clsx(style.avatarWrapper, { disable: !isOwner })}> <Tooltip
<Upload content={t['Click to replace photo']()}
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg" portalOptions={{
fileChange={update} container: tooltipContainer,
data-testid="upload-avatar" }}
>
<div
className={clsx(style.avatarWrapper, { disable: !isOwner })}
ref={setTooltipContainer}
> >
<> <Upload
<div className="camera-icon-wrapper"> accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
<CameraIcon /> fileChange={update}
</div> data-testid="upload-avatar"
<WorkspaceAvatar >
size={56} <>
workspace={workspace.blockSuiteWorkspace} <div className="camera-icon-wrapper">
/> <CameraIcon />
</> </div>
</Upload> <WorkspaceAvatar
</div> size={56}
workspace={workspace.blockSuiteWorkspace}
/>
</>
</Upload>
</div>
</Tooltip>
<Wrapper marginLeft={20}> <Wrapper marginLeft={20}>
<div className={style.label}>{t['Workspace Name']()}</div> <div className={style.label}>{t['Workspace Name']()}</div>
<FlexWrapper alignItems="center" flexGrow="1"> <FlexWrapper alignItems="center" flexGrow="1">
@@ -97,16 +117,15 @@ export const ProfilePanel = ({ workspace, isOwner }: ProfilePanelProps) => {
onKeyUp={handleKeyUp} onKeyUp={handleKeyUp}
/> />
{input === workspace.blockSuiteWorkspace.meta.name ? null : ( {input === workspace.blockSuiteWorkspace.meta.name ? null : (
<IconButton <Button
data-testid="save-workspace-name" data-testid="save-workspace-name"
onClick={handleClick} onClick={handleClick}
active={true}
style={{ style={{
marginLeft: '12px', marginLeft: '12px',
}} }}
> >
<DoneIcon /> {t['com.affine.editCollection.save']()}
</IconButton> </Button>
)} )}
</FlexWrapper> </FlexWrapper>
</Wrapper> </Wrapper>
@@ -26,7 +26,6 @@ export const avatarWrapper = style({
height: '56px', height: '56px',
borderRadius: '50%', borderRadius: '50%',
position: 'relative', position: 'relative',
overflow: 'hidden',
cursor: 'pointer', cursor: 'pointer',
flexShrink: '0', flexShrink: '0',
selectors: { selectors: {
@@ -43,8 +42,9 @@ globalStyle(`${avatarWrapper}:hover .camera-icon-wrapper`, {
display: 'flex', display: 'flex',
}); });
globalStyle(`${avatarWrapper} .camera-icon-wrapper`, { globalStyle(`${avatarWrapper} .camera-icon-wrapper`, {
width: '100%', width: '56px',
height: '100%', height: '56px',
borderRadius: '50%',
position: 'absolute', position: 'absolute',
display: 'none', display: 'none',
justifyContent: 'center', justifyContent: 'center',
@@ -8,8 +8,9 @@ import { UserAvatar } from '@affine/component/user-avatar';
import { allBlobSizesQuery, uploadAvatarMutation } from '@affine/graphql'; import { allBlobSizesQuery, uploadAvatarMutation } from '@affine/graphql';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useMutation, useQuery } from '@affine/workspace/affine/gql'; import { useMutation, useQuery } from '@affine/workspace/affine/gql';
import { ArrowRightSmallIcon, CameraIcon, DoneIcon } from '@blocksuite/icons'; import { ArrowRightSmallIcon, CameraIcon } from '@blocksuite/icons';
import { Button, IconButton } from '@toeverything/components/button'; import { Button } from '@toeverything/components/button';
import { Tooltip } from '@toeverything/components/tooltip';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { type FC, Suspense, useCallback, useState } from 'react'; import { type FC, Suspense, useCallback, useState } from 'react';
@@ -24,6 +25,8 @@ export const AvatarAndName = () => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const user = useCurrentUser(); const user = useCurrentUser();
const [tooltipContainer, setTooltipContainer] =
useState<HTMLDivElement | null>(null);
const [input, setInput] = useState<string>(user.name); const [input, setInput] = useState<string>(user.name);
const { trigger: avatarTrigger } = useMutation({ const { trigger: avatarTrigger } = useMutation({
@@ -56,25 +59,32 @@ export const AvatarAndName = () => {
spreadCol={false} spreadCol={false}
> >
<FlexWrapper style={{ margin: '12px 0 24px 0' }} alignItems="center"> <FlexWrapper style={{ margin: '12px 0 24px 0' }} alignItems="center">
<div className={style.avatarWrapper}> <Tooltip
<Upload content={t['Click to replace photo']()}
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg" portalOptions={{
fileChange={handleUpdateUserAvatar} container: tooltipContainer,
data-testid="upload-user-avatar" }}
> >
<> <div className={style.avatarWrapper} ref={setTooltipContainer}>
<div className="camera-icon-wrapper"> <Upload
<CameraIcon /> accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
</div> fileChange={handleUpdateUserAvatar}
<UserAvatar data-testid="upload-user-avatar"
size={56} >
name={user.name} <>
url={user.image} <div className="camera-icon-wrapper">
className="avatar" <CameraIcon />
/> </div>
</> <UserAvatar
</Upload> size={56}
</div> name={user.name}
url={user.image}
className="avatar"
/>
</>
</Upload>
</div>
</Tooltip>
<div className={style.profileInputWrapper}> <div className={style.profileInputWrapper}>
<label>{t['com.affine.settings.profile.name']()}</label> <label>{t['com.affine.settings.profile.name']()}</label>
@@ -90,18 +100,17 @@ export const AvatarAndName = () => {
onChange={setInput} onChange={setInput}
/> />
{input && input === user.name ? null : ( {input && input === user.name ? null : (
<IconButton <Button
data-testid="save-user-name" data-testid="save-user-name"
onClick={() => { onClick={() => {
handleUpdateUserName(input); handleUpdateUserName(input);
}} }}
style={{ style={{
color: 'var(--affine-primary-color)',
marginLeft: '12px', marginLeft: '12px',
}} }}
> >
<DoneIcon /> {t['com.affine.editCollection.save']()}
</IconButton> </Button>
)} )}
</FlexWrapper> </FlexWrapper>
</div> </div>
@@ -14,7 +14,6 @@ export const avatarWrapper = style({
height: '56px', height: '56px',
borderRadius: '50%', borderRadius: '50%',
position: 'relative', position: 'relative',
overflow: 'hidden',
cursor: 'pointer', cursor: 'pointer',
flexShrink: '0', flexShrink: '0',
selectors: { selectors: {
@@ -28,8 +27,9 @@ globalStyle(`${avatarWrapper}:hover .camera-icon-wrapper`, {
display: 'flex', display: 'flex',
}); });
globalStyle(`${avatarWrapper} .camera-icon-wrapper`, { globalStyle(`${avatarWrapper} .camera-icon-wrapper`, {
width: '100%', width: '56px',
height: '100%', height: '56px',
borderRadius: '50%',
position: 'absolute', position: 'absolute',
display: 'none', display: 'none',
justifyContent: 'center', justifyContent: 'center',
+2
View File
@@ -571,6 +571,8 @@
"Workspace Settings with name": "{{name}}'s Settings", "Workspace Settings with name": "{{name}}'s Settings",
"Workspace Type": "Workspace Type", "Workspace Type": "Workspace Type",
"You cannot delete the last workspace": "You cannot delete the last workspace", "You cannot delete the last workspace": "You cannot delete the last workspace",
"Click to replace photo": "Click to replace photo",
"Remove photo": "Remove photo",
"Removed successfully": "Removed successfully", "Removed successfully": "Removed successfully",
"Successfully enabled AFFiNE Cloud": "Successfully enabled AFFiNE Cloud" "Successfully enabled AFFiNE Cloud": "Successfully enabled AFFiNE Cloud"
} }