From db40cd35c60f75a8485906dab873ac9fe2f197e2 Mon Sep 17 00:00:00 2001 From: Qi <474021214@qq.com> Date: Wed, 28 Jun 2023 22:45:33 +0800 Subject: [PATCH] feat: migrate workspace setting with new design to setting modal (#2900) Co-authored-by: Alex Yang --- .../delete-leave-workspace/delete/index.tsx | 112 +++++++++ .../delete-leave-workspace/delete/style.ts | 76 ++++++ .../delete-leave-workspace/index.tsx | 58 +++++ .../delete-leave-workspace/leave/index.tsx | 50 ++++ .../delete-leave-workspace/leave/style.ts | 45 ++++ .../new-workspace-setting-detail/export.tsx | 34 +++ .../new-workspace-setting-detail/index.tsx | 67 +++++- .../members/index.tsx | 160 +++++++++++++ .../members/invite-member-modal/index.tsx | 222 ++++++++++++++++++ .../members/style.css.ts | 54 +++++ .../new-workspace-setting-detail/profile.tsx | 87 +++++++ .../new-workspace-setting-detail/publish.tsx | 180 ++++++++++++++ .../new-workspace-setting-detail/storage.tsx | 87 +++++++ .../new-workspace-setting-detail/style.css.ts | 67 ++++++ .../components/affine/setting-modal/config.ts | 2 +- .../general-setting/about/index.tsx | 25 +- .../general-setting/appearance/index.tsx | 25 +- .../general-setting/appearance/style.css.ts | 2 +- .../general-setting/shortcuts/index.tsx | 20 +- .../components/affine/setting-modal/index.tsx | 104 +++----- .../setting-modal/setting-sidebar/index.tsx | 10 +- .../setting-sidebar/style.css.ts | 9 +- apps/web/src/layouts/workspace-layout.tsx | 6 +- .../components/setting-components/index.tsx | 4 + .../components/setting-components/modal.tsx | 42 ++++ .../setting-components}/setting-header.tsx | 0 .../setting-components}/setting-row.tsx | 16 +- .../setting-components}/share.css.ts | 19 +- .../setting-components}/wrapper.tsx | 9 +- .../src/components/user-avatar/index.tsx | 37 +++ .../src/components/user-avatar/style.css.ts | 31 +++ packages/component/src/ui/button/utils.ts | 4 +- packages/i18n/src/resources/en.json | 17 +- 33 files changed, 1540 insertions(+), 141 deletions(-) create mode 100644 apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/delete/index.tsx create mode 100644 apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/delete/style.ts create mode 100644 apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/index.tsx create mode 100644 apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/leave/index.tsx create mode 100644 apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/leave/style.ts create mode 100644 apps/web/src/components/affine/new-workspace-setting-detail/export.tsx create mode 100644 apps/web/src/components/affine/new-workspace-setting-detail/members/index.tsx create mode 100644 apps/web/src/components/affine/new-workspace-setting-detail/members/invite-member-modal/index.tsx create mode 100644 apps/web/src/components/affine/new-workspace-setting-detail/members/style.css.ts create mode 100644 apps/web/src/components/affine/new-workspace-setting-detail/profile.tsx create mode 100644 apps/web/src/components/affine/new-workspace-setting-detail/publish.tsx create mode 100644 apps/web/src/components/affine/new-workspace-setting-detail/storage.tsx create mode 100644 apps/web/src/components/affine/new-workspace-setting-detail/style.css.ts create mode 100644 packages/component/src/components/setting-components/index.tsx create mode 100644 packages/component/src/components/setting-components/modal.tsx rename {apps/web/src/components/affine/setting-modal/common => packages/component/src/components/setting-components}/setting-header.tsx (100%) rename {apps/web/src/components/affine/setting-modal/common => packages/component/src/components/setting-components}/setting-row.tsx (54%) rename {apps/web/src/components/affine/setting-modal/common => packages/component/src/components/setting-components}/share.css.ts (82%) rename {apps/web/src/components/affine/setting-modal/common => packages/component/src/components/setting-components}/wrapper.tsx (67%) create mode 100644 packages/component/src/components/user-avatar/index.tsx create mode 100644 packages/component/src/components/user-avatar/style.css.ts diff --git a/apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/delete/index.tsx b/apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/delete/index.tsx new file mode 100644 index 0000000000..ad290bbdc0 --- /dev/null +++ b/apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/delete/index.tsx @@ -0,0 +1,112 @@ +import { Button, Input, Modal, ModalCloseButton } from '@affine/component'; +import { WorkspaceFlavour } from '@affine/env/workspace'; +import { Trans } from '@affine/i18n'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name'; +import { useCallback, useState } from 'react'; + +import type { AffineOfficialWorkspace } from '../../../../../shared'; +import { toast } from '../../../../../utils'; +import { + StyledButtonContent, + StyledInputContent, + StyledModalHeader, + StyledModalWrapper, + StyledTextContent, + StyledWorkspaceName, +} from './style'; + +interface WorkspaceDeleteProps { + open: boolean; + onClose: () => void; + workspace: AffineOfficialWorkspace; + onDeleteWorkspace: () => Promise; +} + +export const WorkspaceDeleteModal = ({ + open, + onClose, + workspace, + onDeleteWorkspace, +}: WorkspaceDeleteProps) => { + const [workspaceName] = useBlockSuiteWorkspaceName( + workspace.blockSuiteWorkspace ?? null + ); + const [deleteStr, setDeleteStr] = useState(''); + const allowDelete = deleteStr === workspaceName; + const t = useAFFiNEI18N(); + + const handleDelete = useCallback(() => { + onDeleteWorkspace() + .then(() => { + toast(t['Successfully deleted'](), { + portal: document.body, + }); + }) + .catch(() => { + // ignore error + }); + }, [onDeleteWorkspace, t]); + + return ( + + + + {t['Delete Workspace']()}? + {workspace.flavour === WorkspaceFlavour.LOCAL ? ( + + + Deleting ( + + {{ workspace: workspaceName } as any} + + ) cannot be undone, please proceed with caution. All contents will + be lost. + + + ) : ( + + + Deleting ( + + {{ workspace: workspaceName } as any} + + ) will delete both local and cloud data, this operation cannot be + undone, please proceed with caution. + + + )} + + { + if (ref) { + setTimeout(() => ref.focus(), 0); + } + }} + onChange={setDeleteStr} + data-testid="delete-workspace-input" + placeholder={t['Placeholder of delete workspace']()} + value={deleteStr} + width={315} + height={42} + /> + + + + + + + + ); +}; diff --git a/apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/delete/style.ts b/apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/delete/style.ts new file mode 100644 index 0000000000..e214baf703 --- /dev/null +++ b/apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/delete/style.ts @@ -0,0 +1,76 @@ +import { styled } from '@affine/component'; + +export const StyledModalWrapper = styled('div')(() => { + return { + position: 'relative', + padding: '0px', + width: '560px', + background: 'var(--affine-white)', + borderRadius: '12px', + // height: '312px', + }; +}); + +export const StyledModalHeader = styled('div')(() => { + return { + margin: '44px 0px 12px 0px', + width: '560px', + fontWeight: '600', + fontSize: '20px;', + textAlign: 'center', + }; +}); + +// export const StyledModalContent = styled('div')(({ theme }) => {}); + +export const StyledTextContent = styled('div')(() => { + return { + margin: 'auto', + width: '425px', + fontFamily: 'Avenir Next', + fontStyle: 'normal', + fontWeight: '400', + fontSize: '18px', + lineHeight: '26px', + textAlign: 'left', + }; +}); + +export const StyledInputContent = styled('div')(() => { + return { + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + margin: '24px 0', + fontSize: 'var(--affine-font-base)', + }; +}); + +export const StyledButtonContent = styled('div')(() => { + return { + marginBottom: '42px', + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + }; +}); + +export const StyledWorkspaceName = styled('span')(() => { + return { + fontWeight: '600', + }; +}); + +// export const StyledCancelButton = styled(Button)(({ theme }) => { +// return { +// width: '100px', +// justifyContent: 'center', +// }; +// }); + +// export const StyledDeleteButton = styled(Button)(({ theme }) => { +// return { +// width: '100px', +// justifyContent: 'center', +// }; +// }); diff --git a/apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/index.tsx b/apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/index.tsx new file mode 100644 index 0000000000..40d133ccbd --- /dev/null +++ b/apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/index.tsx @@ -0,0 +1,58 @@ +import { + SettingRow, +} from '@affine/component/setting-components'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { ArrowRightSmallIcon } from '@blocksuite/icons'; +import { type FC, useState } from 'react'; + +import { useIsWorkspaceOwner } from '../../../../hooks/affine/use-is-workspace-owner'; +import type { AffineOfficialWorkspace } from '../../../../shared'; +import type { WorkspaceSettingDetailProps } from '../index'; +import { WorkspaceDeleteModal } from './delete'; +import { WorkspaceLeave } from './leave'; + +export const DeleteLeaveWorkspace: FC<{ + workspace: AffineOfficialWorkspace; + onDeleteWorkspace: WorkspaceSettingDetailProps['onDeleteWorkspace']; +}> = ({ workspace, onDeleteWorkspace }) => { + const t = useAFFiNEI18N(); + const isOwner = useIsWorkspaceOwner(workspace); + + const [showDelete, setShowDelete] = useState(false); + const [showLeave, setShowLeave] = useState(false); + return ( + <> + + {isOwner ? t['Delete Workspace']() : t['Leave Workspace']()} + + } + desc={t['None yet']()} + style={{ cursor: 'pointer' }} + onClick={() => { + setShowDelete(true); + }} + > + + + {isOwner ? ( + { + setShowDelete(false); + }} + workspace={workspace} + /> + ) : ( + { + setShowLeave(false); + }} + /> + )} + + ); +}; diff --git a/apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/leave/index.tsx b/apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/leave/index.tsx new file mode 100644 index 0000000000..ed6dabdf3f --- /dev/null +++ b/apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/leave/index.tsx @@ -0,0 +1,50 @@ +import { Modal } from '@affine/component'; +import { ModalCloseButton } from '@affine/component'; +import { Button } from '@affine/component'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; + +import { + StyledButtonContent, + StyledModalHeader, + StyledModalWrapper, + StyledTextContent, +} from './style'; + +interface WorkspaceDeleteProps { + open: boolean; + onClose: () => void; +} + +export const WorkspaceLeave = ({ open, onClose }: WorkspaceDeleteProps) => { + // const { leaveWorkSpace } = useWorkspaceHelper(); + const t = useAFFiNEI18N(); + const handleLeave = async () => { + // await leaveWorkSpace(); + onClose(); + }; + + return ( + + + + {t['Leave Workspace']()} + + {t['Leave Workspace Description']()} + + + + + + + + ); +}; diff --git a/apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/leave/style.ts b/apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/leave/style.ts new file mode 100644 index 0000000000..64254a5aa0 --- /dev/null +++ b/apps/web/src/components/affine/new-workspace-setting-detail/delete-leave-workspace/leave/style.ts @@ -0,0 +1,45 @@ +import { styled } from '@affine/component'; + +export const StyledModalWrapper = styled('div')(() => { + return { + position: 'relative', + padding: '0px', + width: '460px', + background: 'var(--affine-white)', + borderRadius: '12px', + }; +}); + +export const StyledModalHeader = styled('div')(() => { + return { + margin: '44px 0px 12px 0px', + width: '460px', + fontWeight: '600', + fontSize: '20px;', + textAlign: 'center', + }; +}); + +// export const StyledModalContent = styled('div')(({ theme }) => {}); + +export const StyledTextContent = styled('div')(() => { + return { + margin: 'auto', + width: '425px', + fontFamily: 'Avenir Next', + fontStyle: 'normal', + fontWeight: '400', + fontSize: '18px', + lineHeight: '26px', + textAlign: 'center', + }; +}); + +export const StyledButtonContent = styled('div')(() => { + return { + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + margin: '0px 0 32px 0', + }; +}); diff --git a/apps/web/src/components/affine/new-workspace-setting-detail/export.tsx b/apps/web/src/components/affine/new-workspace-setting-detail/export.tsx new file mode 100644 index 0000000000..dff681e818 --- /dev/null +++ b/apps/web/src/components/affine/new-workspace-setting-detail/export.tsx @@ -0,0 +1,34 @@ +import { Button, toast } from '@affine/component'; +import { SettingRow } from '@affine/component/setting-components'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import type { FC } from 'react'; + +import type { AffineOfficialWorkspace } from '../../../shared'; + +export const ExportPanel: FC<{ + workspace: AffineOfficialWorkspace; +}> = ({ workspace }) => { + const workspaceId = workspace.id; + const t = useAFFiNEI18N(); + return ( + <> + + + + + ); +}; diff --git a/apps/web/src/components/affine/new-workspace-setting-detail/index.tsx b/apps/web/src/components/affine/new-workspace-setting-detail/index.tsx index 4ee8cd1d7a..6c8c3a55b6 100644 --- a/apps/web/src/components/affine/new-workspace-setting-detail/index.tsx +++ b/apps/web/src/components/affine/new-workspace-setting-detail/index.tsx @@ -1,11 +1,23 @@ +import { + SettingHeader, + SettingRow, + SettingWrapper, +} from '@affine/component/setting-components'; import type { WorkspaceFlavour, WorkspaceRegistry, } from '@affine/env/workspace'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name'; import type { FC } from 'react'; import type { AffineOfficialWorkspace } from '../../../shared'; +import { DeleteLeaveWorkspace } from './delete-leave-workspace'; +import { ExportPanel } from './export'; +import { MembersPanel } from './members'; +import { ProfilePanel } from './profile'; +import { PublishPanel } from './publish'; +import { StoragePanel } from './storage'; export type WorkspaceSettingDetailProps = { workspace: AffineOfficialWorkspace; @@ -22,15 +34,54 @@ export type WorkspaceSettingDetailProps = { export const WorkspaceSettingDetail: FC = ({ workspace, + onDeleteWorkspace, + ...props }) => { - const [workspaceName] = useBlockSuiteWorkspaceName( - workspace.blockSuiteWorkspace ?? null - ); - return ( -
-

New Workspace Setting Coming Soon!

+ const t = useAFFiNEI18N(); + const [name] = useBlockSuiteWorkspaceName(workspace.blockSuiteWorkspace); - {workspaceName} -
+ return ( + <> + + + + + + + + + + + {environment.isDesktop ? ( + + + + + ) : null} + + + + + ); }; diff --git a/apps/web/src/components/affine/new-workspace-setting-detail/members/index.tsx b/apps/web/src/components/affine/new-workspace-setting-detail/members/index.tsx new file mode 100644 index 0000000000..734fb1349d --- /dev/null +++ b/apps/web/src/components/affine/new-workspace-setting-detail/members/index.tsx @@ -0,0 +1,160 @@ +import { Button, IconButton, Menu, MenuItem } from '@affine/component'; +import { SettingRow } from '@affine/component/setting-components'; +import { UserAvatar } from '@affine/component/user-avatar'; +import { Unreachable } from '@affine/env/constant'; +import type { AffineLegacyCloudWorkspace } from '@affine/env/workspace'; +import { WorkspaceFlavour } from '@affine/env/workspace'; +import { PermissionType } from '@affine/env/workspace/legacy-cloud'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { DeleteTemporarilyIcon, MoreVerticalIcon } from '@blocksuite/icons'; +import type { FC } from 'react'; +import React, { useCallback, useState } from 'react'; + +import { useMembers } from '../../../../hooks/affine/use-members'; +import type { AffineOfficialWorkspace } from '../../../../shared'; +import { toast } from '../../../../utils'; +import type { WorkspaceSettingDetailProps } from '../index'; +import { fakeWrapper } from '../style.css'; +import { InviteMemberModal } from './invite-member-modal'; +import * as style from './style.css'; + +export type AffineRemoteMembersProps = WorkspaceSettingDetailProps & { + workspace: AffineLegacyCloudWorkspace; +}; +export type MemberPanelProps = WorkspaceSettingDetailProps & { + workspace: AffineOfficialWorkspace; +}; + +const MemberList: FC<{ + workspace: AffineLegacyCloudWorkspace; +}> = ({ workspace }) => { + const t = useAFFiNEI18N(); + const { members, removeMember } = useMembers(workspace.id); + + if (members.length) { + return null; + } + + return ( +
    + {members + .sort((b, a) => a.type - b.type) + .map(member => { + const { id, name, email, avatar_url } = { + name: '', + email: '', + avatar_url: '', + ...member, + }; + return ( +
  • +
    + +
    +

    {name}

    +

    {email}

    +
    +
    +
    +
    + {member.accepted + ? member.type !== PermissionType.Owner + ? t['Member']() + : t['Owner']() + : t['Pending']()} +
    + + { + await removeMember(Number(id)); + toast( + t['Member has been removed']({ + name, + }) + ); + }} + icon={} + > + {t['Remove from workspace']()} + + + } + placement="bottom" + disablePortal={true} + trigger="click" + > + + + + +
    +
  • + ); + })} +
+ ); +}; + +export const AffineRemoteMembers: FC = ({ + workspace, +}) => { + const t = useAFFiNEI18N(); + const { members } = useMembers(workspace.id); + + const [isInviteModalShow, setIsInviteModalShow] = useState(false); + + return ( + <> + + + + + { + setIsInviteModalShow(false); + }, [])} + onInviteSuccess={useCallback(() => { + setIsInviteModalShow(false); + }, [])} + workspaceId={workspace.id} + open={isInviteModalShow} + /> + + ); +}; +export const FakeMembers: FC = () => { + const t = useAFFiNEI18N(); + return ( +
+ + + +
+ ); +}; + +export const MembersPanel: FC = props => { + switch (props.workspace.flavour) { + case WorkspaceFlavour.AFFINE: { + const workspace = props.workspace as AffineLegacyCloudWorkspace; + return ; + } + case WorkspaceFlavour.LOCAL: { + return ; + } + } + throw new Unreachable(); +}; diff --git a/apps/web/src/components/affine/new-workspace-setting-detail/members/invite-member-modal/index.tsx b/apps/web/src/components/affine/new-workspace-setting-detail/members/invite-member-modal/index.tsx new file mode 100644 index 0000000000..909b1a4853 --- /dev/null +++ b/apps/web/src/components/affine/new-workspace-setting-detail/members/invite-member-modal/index.tsx @@ -0,0 +1,222 @@ +import { + Button, + Input, + Modal, + ModalCloseButton, + ModalWrapper, + MuiAvatar, + styled, +} from '@affine/component'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { EmailIcon } from '@blocksuite/icons'; +import type React from 'react'; +import { Suspense, useCallback, useState } from 'react'; + +import { useMembers } from '../../../../../hooks/affine/use-members'; +import { useUsersByEmail } from '../../../../../hooks/affine/use-users-by-email'; + +interface LoginModalProps { + open: boolean; + onClose: () => void; + workspaceId: string; + onInviteSuccess: () => void; +} + +const gmailReg = + /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@(gmail|example)\.(com|org)$/; + +const Result: React.FC<{ + workspaceId: string; + queryEmail: string; +}> = ({ workspaceId, queryEmail }) => { + const users = useUsersByEmail(workspaceId, queryEmail); + const firstUser = users?.at(0) ?? null; + if (!firstUser || !firstUser.email) { + return null; + } + return ( + + + {firstUser.avatar_url ? ( + + ) : ( + + + + )} + {firstUser.email} + {/*
invited
*/} +
+
+ ); +}; + +export const InviteMemberModal = ({ + open, + onClose, + onInviteSuccess, + workspaceId, +}: LoginModalProps) => { + const { inviteMember } = useMembers(workspaceId); + const [email, setEmail] = useState(''); + const [showMemberPreview, setShowMemberPreview] = useState(false); + const t = useAFFiNEI18N(); + const inputChange = useCallback((value: string) => { + setEmail(value); + }, []); + return ( +
+ + +
+ { + onClose(); + setEmail(''); + }} + /> +
+ + {t['Invite Members']()} + + { + setShowMemberPreview(true); + }, [])} + onBlur={useCallback(() => { + setShowMemberPreview(false); + }, [])} + placeholder={t['Invite placeholder']()} + /> + {showMemberPreview && gmailReg.test(email) && ( + + + + )} + + +
+ +
+
+
+
+ ); +}; + +const Header = styled('div')({ + position: 'relative', + height: '44px', +}); + +const Content = styled('div')({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', +}); + +const ContentTitle = styled('h1')({ + fontSize: '20px', + lineHeight: '28px', + fontWeight: 600, + textAlign: 'center', + paddingBottom: '16px', +}); + +const Footer = styled('div')({ + height: '102px', + margin: '32px 0', + textAlign: 'center', +}); + +const InviteBox = styled('div')({ + position: 'relative', +}); + +const Members = styled('div')(() => { + return { + position: 'absolute', + width: '100%', + background: 'var(--affine-background-primary-color)', + textAlign: 'left', + zIndex: 1, + borderRadius: '0px 10px 10px 10px', + height: '56px', + padding: '8px 12px', + input: { + '&::placeholder': { + color: 'var(--affine-placeholder-color)', + }, + }, + }; +}); + +// const NoFind = styled('div')(({ theme }) => { +// return { +// color: 'var(--affine-icon-color)', +// fontSize: 'var(--affine-font-sm)', +// lineHeight: '40px', +// userSelect: 'none', +// width: '100%', +// }; +// }); + +const Member = styled('div')(() => { + return { + color: 'var(--affine-icon-color)', + fontSize: 'var(--affine-font-sm)', + lineHeight: '40px', + userSelect: 'none', + display: 'flex', + }; +}); + +const MemberIcon = styled('div')(() => { + return { + width: '40px', + height: '40px', + borderRadius: '50%', + color: 'var(--affine-primary-color)', + background: '#F5F5F5', + textAlign: 'center', + lineHeight: '45px', + // icon size + fontSize: '20px', + overflow: 'hidden', + img: { + width: '100%', + height: '100%', + }, + }; +}); + +const Email = styled('div')(() => { + return { + flex: '1', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + marginLeft: '8px', + }; +}); diff --git a/apps/web/src/components/affine/new-workspace-setting-detail/members/style.css.ts b/apps/web/src/components/affine/new-workspace-setting-detail/members/style.css.ts new file mode 100644 index 0000000000..9ac5ba1e58 --- /dev/null +++ b/apps/web/src/components/affine/new-workspace-setting-detail/members/style.css.ts @@ -0,0 +1,54 @@ +import { globalStyle, style } from '@vanilla-extract/css'; + +export const memberList = style({ + marginTop: '12px', +}); + +globalStyle(`${memberList} .member-list-item`, { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', +}); +globalStyle(`${memberList} .member-list-item:not(:last-of-type)`, { + marginBottom: '8px', +}); + +globalStyle(`${memberList} .left-col`, { + display: 'flex', + alignItems: 'center', + width: '60%', +}); +globalStyle(`${memberList} .right-col`, { + display: 'flex', + justifyContent: 'flex-end', + alignItems: 'center', + width: '35%', +}); +globalStyle(`${memberList} .user-info-wrapper`, { + flexGrow: 1, + marginLeft: '12px', + overflow: 'hidden', +}); + +globalStyle(`${memberList} .user-info-wrapper p`, { + width: '100%', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', +}); + +globalStyle(`${memberList} .user-name`, { + fontSize: 'var(--affine-font-sm)', +}); +globalStyle(`${memberList} .email`, { + fontSize: 'var(--affine-font-xs)', + color: 'var(--affine-text-secondary-color)', +}); +globalStyle(`${memberList} .user-identity`, { + fontSize: 'var(--affine-font-sm)', + marginRight: '15px', + flexGrow: '1', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', +}); diff --git a/apps/web/src/components/affine/new-workspace-setting-detail/profile.tsx b/apps/web/src/components/affine/new-workspace-setting-detail/profile.tsx new file mode 100644 index 0000000000..e84c4e560c --- /dev/null +++ b/apps/web/src/components/affine/new-workspace-setting-detail/profile.tsx @@ -0,0 +1,87 @@ +import { IconButton, Input, toast } from '@affine/component'; +import { WorkspaceAvatar } from '@affine/component/workspace-avatar'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { DoneIcon } from '@blocksuite/icons'; +import { useBlockSuiteWorkspaceAvatarUrl } from '@toeverything/hooks/use-block-suite-workspace-avatar-url'; +import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name'; +import { type FC, useCallback, useState } from 'react'; + +import { useIsWorkspaceOwner } from '../../../hooks/affine/use-is-workspace-owner'; +import type { AffineOfficialWorkspace } from '../../../shared'; +import { Upload } from '../../pure/file-upload'; +import { CameraIcon } from '../workspace-setting-detail/panel/general/icons'; +import * as style from './style.css'; + +export const ProfilePanel: FC<{ + workspace: AffineOfficialWorkspace; +}> = ({ workspace }) => { + const t = useAFFiNEI18N(); + const isOwner = useIsWorkspaceOwner(workspace); + + const [, update] = useBlockSuiteWorkspaceAvatarUrl( + workspace.blockSuiteWorkspace + ); + const [name, setName] = useBlockSuiteWorkspaceName( + workspace.blockSuiteWorkspace + ); + + const [input, setInput] = useState(name); + + const handleUpdateWorkspaceName = useCallback( + (name: string) => { + setName(name); + toast(t['Update workspace name success']()); + }, + [setName, t] + ); + + return ( +
+
+ {isOwner ? ( + + <> +
+ +
+ + +
+ ) : ( + + )} +
+
+ + {input === workspace.blockSuiteWorkspace.meta.name ? null : ( + { + handleUpdateWorkspaceName(input); + }} + style={{ + color: 'var(--affine-primary-color)', + marginLeft: '12px', + }} + > + + + )} +
+
+ ); +}; diff --git a/apps/web/src/components/affine/new-workspace-setting-detail/publish.tsx b/apps/web/src/components/affine/new-workspace-setting-detail/publish.tsx new file mode 100644 index 0000000000..5a2ba3ba93 --- /dev/null +++ b/apps/web/src/components/affine/new-workspace-setting-detail/publish.tsx @@ -0,0 +1,180 @@ +import { Button, FlexWrapper, Switch } from '@affine/component'; +import { SettingRow } from '@affine/component/setting-components'; +import { Unreachable } from '@affine/env/constant'; +import type { + AffineLegacyCloudWorkspace, + LocalWorkspace, +} from '@affine/env/workspace'; +import { WorkspaceFlavour } from '@affine/env/workspace'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name'; +import type { FC } from 'react'; +import { useCallback, useEffect, useState } from 'react'; + +import { useToggleWorkspacePublish } from '../../../hooks/affine/use-toggle-workspace-publish'; +import type { AffineOfficialWorkspace } from '../../../shared'; +import { toast } from '../../../utils'; +import { EnableAffineCloudModal } from '../enable-affine-cloud-modal'; +import { TmpDisableAffineCloudModal } from '../tmp-disable-affine-cloud-modal'; +import type { WorkspaceSettingDetailProps } from './index'; +import * as style from './style.css'; + +export type PublishPanelProps = WorkspaceSettingDetailProps & { + workspace: AffineOfficialWorkspace; +}; +export type PublishPanelLocalProps = WorkspaceSettingDetailProps & { + workspace: LocalWorkspace; +}; +export type PublishPanelAffineProps = WorkspaceSettingDetailProps & { + workspace: AffineLegacyCloudWorkspace; +}; + +const PublishPanelAffine: FC = props => { + const { workspace } = props; + const t = useAFFiNEI18N(); + const toggleWorkspacePublish = useToggleWorkspacePublish(workspace); + + const [origin, setOrigin] = useState(''); + const shareUrl = origin + '/public-workspace/' + workspace.id; + + useEffect(() => { + setOrigin( + typeof window !== 'undefined' && window.location.origin + ? window.location.origin + : '' + ); + }, []); + + const copyUrl = useCallback(async () => { + await navigator.clipboard.writeText(shareUrl); + toast(t['Copied link to clipboard']()); + }, [shareUrl, t]); + return ( + <> + + toggleWorkspacePublish(checked)} + /> + + + + + + + ); +}; + +const FakePublishPanelAffine: FC<{ + workspace: AffineOfficialWorkspace; +}> = ({ workspace }) => { + const t = useAFFiNEI18N(); + const [origin, setOrigin] = useState(''); + const shareUrl = origin + '/public-workspace/' + workspace.id; + + useEffect(() => { + setOrigin( + typeof window !== 'undefined' && window.location.origin + ? window.location.origin + : '' + ); + }, []); + return ( +
+ + + + + + + +
+ ); +}; +const PublishPanelLocal: FC = ({ + workspace, + onTransferWorkspace, +}) => { + const t = useAFFiNEI18N(); + const [name] = useBlockSuiteWorkspaceName(workspace.blockSuiteWorkspace); + + const [open, setOpen] = useState(false); + + return ( + <> + + + + + {runtimeConfig.enableLegacyCloud ? ( + { + setOpen(false); + }} + onConfirm={() => { + onTransferWorkspace( + WorkspaceFlavour.LOCAL, + WorkspaceFlavour.AFFINE, + workspace + ); + setOpen(false); + }} + /> + ) : ( + { + setOpen(false); + }} + /> + )} + + ); +}; + +export const PublishPanel: FC = props => { + if (props.workspace.flavour === WorkspaceFlavour.AFFINE) { + return ; + } else if (props.workspace.flavour === WorkspaceFlavour.LOCAL) { + return ; + } + throw new Unreachable(); +}; diff --git a/apps/web/src/components/affine/new-workspace-setting-detail/storage.tsx b/apps/web/src/components/affine/new-workspace-setting-detail/storage.tsx new file mode 100644 index 0000000000..381063cbb0 --- /dev/null +++ b/apps/web/src/components/affine/new-workspace-setting-detail/storage.tsx @@ -0,0 +1,87 @@ +import { Button, toast } from '@affine/component'; +import { SettingRow } from '@affine/component/setting-components'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { type FC, useCallback, useEffect, useState } from 'react'; + +import type { AffineOfficialWorkspace } from '../../../shared'; + +const useShowOpenDBFile = (workspaceId: string) => { + const [show, setShow] = useState(false); + useEffect(() => { + if (window.apis && window.events && environment.isDesktop) { + window.apis?.workspace + .getMeta(workspaceId) + .then(meta => { + setShow(!!meta.secondaryDBPath); + }) + .catch(err => { + console.error(err); + }); + return window.events.workspace.onMetaChange((newMeta: any) => { + if (newMeta.workspaceId === workspaceId) { + const meta = newMeta.meta; + setShow(!!meta.secondaryDBPath); + } + }); + } + }, [workspaceId]); + return show; +}; + +export const StoragePanel: FC<{ + workspace: AffineOfficialWorkspace; +}> = ({ workspace }) => { + const workspaceId = workspace.id; + const t = useAFFiNEI18N(); + const showOpenFolder = useShowOpenDBFile(workspaceId); + + const [moveToInProgress, setMoveToInProgress] = useState(false); + const onRevealDBFile = useCallback(() => { + window.apis?.dialog.revealDBFile(workspaceId).catch(err => { + console.error(err); + }); + }, [workspaceId]); + + const handleMoveTo = useCallback(() => { + if (moveToInProgress) { + return; + } + setMoveToInProgress(true); + window.apis?.dialog + .moveDBFile(workspaceId) + .then(result => { + if (!result?.error && !result?.canceled) { + toast(t['Move folder success']()); + } else if (result?.error) { + // @ts-expect-error: result.error is dynamic + toast(t[result.error]()); + } + }) + .catch(() => { + toast(t['UNKNOWN_ERROR']()); + }) + .finally(() => { + setMoveToInProgress(false); + }); + }, [moveToInProgress, t, workspaceId]); + + if (!showOpenFolder) { + return null; + } + return ( + + + + + ); +}; diff --git a/apps/web/src/components/affine/new-workspace-setting-detail/style.css.ts b/apps/web/src/components/affine/new-workspace-setting-detail/style.css.ts new file mode 100644 index 0000000000..46f1160e2f --- /dev/null +++ b/apps/web/src/components/affine/new-workspace-setting-detail/style.css.ts @@ -0,0 +1,67 @@ +import { globalStyle, style } from '@vanilla-extract/css'; + +export const profileWrapper = style({ + display: 'flex', + alignItems: 'flex-end', + marginTop: '12px', +}); +export const profileHandlerWrapper = style({ + flexGrow: '1', + display: 'flex', + alignItems: 'center', + marginLeft: '20px', +}); + +export const avatarWrapper = style({ + width: '56px', + height: '56px', + borderRadius: '50%', + position: 'relative', + overflow: 'hidden', + cursor: 'pointer', + flexShrink: '0', + selectors: { + '&.disable': { + cursor: 'default', + pointerEvents: 'none', + }, + }, +}); +globalStyle(`${avatarWrapper}:hover .camera-icon-wrapper`, { + display: 'flex', +}); +globalStyle(`${avatarWrapper} .camera-icon-wrapper`, { + width: '100%', + height: '100%', + position: 'absolute', + display: 'none', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(60, 61, 63, 0.5)', + zIndex: '1', +}); + +export const urlButton = style({ + width: 'calc(100% - 64px - 15px)', +}); +globalStyle(`${urlButton} span`, { + width: '100%', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', +}); + +export const fakeWrapper = style({ + position: 'relative', + selectors: { + '&::after': { + content: '""', + width: '100%', + height: '100%', + position: 'absolute', + left: 0, + top: 0, + background: 'var(--affine-white-60)', + }, + }, +}); diff --git a/apps/web/src/components/affine/setting-modal/config.ts b/apps/web/src/components/affine/setting-modal/config.ts index 2c794cc4dd..c7e2b37c4e 100644 --- a/apps/web/src/components/affine/setting-modal/config.ts +++ b/apps/web/src/components/affine/setting-modal/config.ts @@ -1,2 +1,2 @@ // Some settings are not implemented yet, but need to show in the setting modal when boss is watching. -export const IS_EXHIBITION = false; +export const IS_EXHIBITION = true; diff --git a/apps/web/src/components/affine/setting-modal/general-setting/about/index.tsx b/apps/web/src/components/affine/setting-modal/general-setting/about/index.tsx index e35dae368f..72a8dfa6d3 100644 --- a/apps/web/src/components/affine/setting-modal/general-setting/about/index.tsx +++ b/apps/web/src/components/affine/setting-modal/general-setting/about/index.tsx @@ -1,14 +1,13 @@ import { Switch } from '@affine/component'; import { relatedLinks } from '@affine/component/contact-modal'; -import { isDesktop } from '@affine/env/constant'; +import { SettingHeader } from '@affine/component/setting-components'; +import { SettingRow } from '@affine/component/setting-components'; +import { SettingWrapper } from '@affine/component/setting-components'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { ArrowRightSmallIcon, OpenInNewIcon } from '@blocksuite/icons'; import { useCallback } from 'react'; import { type AppSetting, useAppSetting } from '../../../../../atoms/settings'; -import { SettingHeader } from '../../common/setting-header'; -import { SettingRow } from '../../common/setting-row'; -import { Wrapper } from '../../common/wrapper'; import { IS_EXHIBITION } from '../../config'; import { communityItem, communityWrapper, link } from './style.css'; @@ -24,8 +23,8 @@ export const AboutAffine = () => { return ( <> - {IS_EXHIBITION && isDesktop ? ( - + {IS_EXHIBITION && environment.isDesktop ? ( + { > - + ) : null} - + {t['Official Website']()} @@ -76,8 +75,8 @@ export const AboutAffine = () => { {t['AFFiNE Community']()} - - + +
{relatedLinks.map(({ icon, title, link }) => { return ( @@ -94,8 +93,8 @@ export const AboutAffine = () => { ); })}
-
- + + {t['Privacy']()} @@ -104,7 +103,7 @@ export const AboutAffine = () => { {t['Terms of Use']()} - + ); }; diff --git a/apps/web/src/components/affine/setting-modal/general-setting/appearance/index.tsx b/apps/web/src/components/affine/setting-modal/general-setting/appearance/index.tsx index c56fdcec83..d5b2ab5765 100644 --- a/apps/web/src/components/affine/setting-modal/general-setting/appearance/index.tsx +++ b/apps/web/src/components/affine/setting-modal/general-setting/appearance/index.tsx @@ -1,5 +1,7 @@ import { RadioButton, RadioButtonGroup, Switch } from '@affine/component'; -import { isDesktop } from '@affine/env/constant'; +import { SettingHeader } from '@affine/component/setting-components'; +import { SettingRow } from '@affine/component/setting-components'; +import { SettingWrapper } from '@affine/component/setting-components'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useTheme } from 'next-themes'; import { useCallback } from 'react'; @@ -10,9 +12,6 @@ import { windowFrameStyleOptions, } from '../../../../../atoms/settings'; import { LanguageMenu } from '../../../language-menu'; -import { SettingHeader } from '../../common/setting-header'; -import { SettingRow } from '../../common/setting-row'; -import { Wrapper } from '../../common/wrapper'; import { IS_EXHIBITION } from '../../config'; import { DateFormatSetting } from './date-format-setting'; import { settingWrapper } from './style.css'; @@ -56,7 +55,7 @@ export const AppearanceSettings = () => { subtitle={t['Customize your AFFiNE Appearance']()} /> - + { - {IS_EXHIBITION && isDesktop ? ( + {IS_EXHIBITION && environment.isDesktop ? ( { onChange={checked => changeSwitch('fullWidthLayout', checked)} /> - {IS_EXHIBITION && isDesktop ? ( + {IS_EXHIBITION && environment.isDesktop ? ( { ) : null} - + {IS_EXHIBITION ? ( - + { onChange={checked => changeSwitch('startWeekOnMonday', checked)} /> - + ) : null} - {isDesktop ? ( - + {environment.isDesktop ? ( + { } /> - + ) : null} ); diff --git a/apps/web/src/components/affine/setting-modal/general-setting/appearance/style.css.ts b/apps/web/src/components/affine/setting-modal/general-setting/appearance/style.css.ts index a4eba9c4f5..d48509003e 100644 --- a/apps/web/src/components/affine/setting-modal/general-setting/appearance/style.css.ts +++ b/apps/web/src/components/affine/setting-modal/general-setting/appearance/style.css.ts @@ -3,7 +3,7 @@ import { style } from '@vanilla-extract/css'; export const settingWrapper = style({ flexGrow: 1, display: 'flex', - width: '50%', + justifyContent: 'flex-end', minWidth: '150px', maxWidth: '250px', }); diff --git a/apps/web/src/components/affine/setting-modal/general-setting/shortcuts/index.tsx b/apps/web/src/components/affine/setting-modal/general-setting/shortcuts/index.tsx index 76f6656d9c..3817c50c63 100644 --- a/apps/web/src/components/affine/setting-modal/general-setting/shortcuts/index.tsx +++ b/apps/web/src/components/affine/setting-modal/general-setting/shortcuts/index.tsx @@ -1,3 +1,5 @@ +import { SettingHeader } from '@affine/component/setting-components'; +import { SettingWrapper } from '@affine/component/setting-components'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { @@ -6,8 +8,6 @@ import { useMarkdownShortcuts, usePageShortcuts, } from '../../../../../hooks/affine/use-shortcuts'; -import { SettingHeader } from '../../common/setting-header'; -import { Wrapper } from '../../common/wrapper'; import { shortcutRow } from './style.css'; export const Shortcuts = () => { @@ -24,7 +24,7 @@ export const Shortcuts = () => { title={t['Keyboard Shortcuts']()} subtitle={t['Check Keyboard Shortcuts quickly']()} /> - + {Object.entries(generalShortcuts).map(([title, shortcuts]) => { return (
@@ -33,8 +33,8 @@ export const Shortcuts = () => {
); })} -
- + + {Object.entries(pageShortcuts).map(([title, shortcuts]) => { return (
@@ -43,8 +43,8 @@ export const Shortcuts = () => {
); })} -
- + + {Object.entries(edgelessShortcuts).map(([title, shortcuts]) => { return (
@@ -53,8 +53,8 @@ export const Shortcuts = () => {
); })} -
- + + {Object.entries(markdownShortcuts).map(([title, shortcuts]) => { return (
@@ -63,7 +63,7 @@ export const Shortcuts = () => {
); })} -
+ ); }; diff --git a/apps/web/src/components/affine/setting-modal/index.tsx b/apps/web/src/components/affine/setting-modal/index.tsx index 8c9b8b2ffc..4684b76b9e 100644 --- a/apps/web/src/components/affine/setting-modal/index.tsx +++ b/apps/web/src/components/affine/setting-modal/index.tsx @@ -1,4 +1,7 @@ -import { Modal, ModalCloseButton, ModalWrapper } from '@affine/component'; +import { + SettingModal as SettingModalBase, + type SettingModalProps, +} from '@affine/component/setting-components'; import type { AffineLegacyCloudWorkspace, LocalWorkspace, @@ -6,13 +9,11 @@ import type { import { WorkspaceFlavour } from '@affine/env/workspace'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { ContactWithUsIcon } from '@blocksuite/icons'; -import type { NextRouter } from 'next/router'; import type React from 'react'; import { useCallback, useMemo, useState } from 'react'; import { useCurrentWorkspace } from '../../../hooks/current/use-current-workspace'; import { useWorkspaces } from '../../../hooks/use-workspaces'; -import type { BlockSuiteWorkspace } from '../../../shared'; import { AccountSetting } from './account-setting'; import { GeneralSetting, @@ -24,15 +25,7 @@ import { settingContent } from './style.css'; import type { Workspace } from './type'; import { WorkSpaceSetting } from './workspace-setting'; -export type QuickSearchModalProps = { - currentWorkspace?: BlockSuiteWorkspace; - workspaceList?: BlockSuiteWorkspace[]; - open: boolean; - setOpen: (value: boolean) => void; - router: NextRouter; -}; - -export const SettingModal: React.FC = ({ +export const SettingModal: React.FC = ({ open, setOpen, }) => { @@ -55,9 +48,6 @@ export const SettingModal: React.FC = ({ generalKey: generalSettingList[0].key, isAccount: false, }); - const handleClose = useCallback(() => { - setOpen(false); - }, [setOpen]); const onGeneralSettingClick = useCallback((key: GeneralSettingKeys) => { setCurrentRef({ @@ -82,59 +72,41 @@ export const SettingModal: React.FC = ({ }, []); return ( - - - + + - - -
-
-
- {currentRef.workspace ? ( - - ) : null} - {currentRef.generalKey ? ( - - ) : null} - {currentRef.isAccount ? : null} -
- +
+
+
+ {currentRef.workspace ? ( + + ) : null} + {currentRef.generalKey ? ( + + ) : null} + {currentRef.isAccount ? : null} +
+
- - +
+ ); }; diff --git a/apps/web/src/components/affine/setting-modal/setting-sidebar/index.tsx b/apps/web/src/components/affine/setting-modal/setting-sidebar/index.tsx index 90a3d42d56..6deb4b7de2 100644 --- a/apps/web/src/components/affine/setting-modal/setting-sidebar/index.tsx +++ b/apps/web/src/components/affine/setting-modal/setting-sidebar/index.tsx @@ -1,3 +1,4 @@ +import { UserAvatar } from '@affine/component/user-avatar'; import { WorkspaceAvatar } from '@affine/component/workspace-avatar'; import type { AffineLegacyCloudWorkspace, @@ -5,6 +6,7 @@ import type { } from '@affine/env/workspace'; import { useBlockSuiteWorkspaceName } from '@toeverything/hooks/use-block-suite-workspace-name'; import clsx from 'clsx'; +import React from 'react'; import type { GeneralSettingKeys, @@ -84,7 +86,13 @@ export const SettingSidebar = ({
-
+ +
Account NameAccount Name diff --git a/apps/web/src/components/affine/setting-modal/setting-sidebar/style.css.ts b/apps/web/src/components/affine/setting-modal/setting-sidebar/style.css.ts index 04ee3e1d36..2daac22668 100644 --- a/apps/web/src/components/affine/setting-modal/setting-sidebar/style.css.ts +++ b/apps/web/src/components/affine/setting-modal/setting-sidebar/style.css.ts @@ -3,9 +3,7 @@ import { globalStyle, style } from '@vanilla-extract/css'; export const settingSlideBar = style({ width: '25%', maxWidth: '242px', - // TODO: use color variable - // background: 'var(--affine-background-secondary-color)', - backgroundColor: '#F4F4F5', + background: 'var(--affine-background-secondary-color)', padding: '20px 16px', height: '100%', flexShrink: 0, @@ -107,14 +105,9 @@ export const accountButton = style({ }); globalStyle(`${accountButton} .avatar`, { - width: '28px', - height: '28px', border: '1px solid', borderColor: 'var(--affine-white)', - borderRadius: '14px', - flexShrink: '0', marginRight: '10px', - background: 'red', }); globalStyle(`${accountButton} .content`, { flexGrow: '1', diff --git a/apps/web/src/layouts/workspace-layout.tsx b/apps/web/src/layouts/workspace-layout.tsx index 1e4c38d03e..f70a1083bb 100644 --- a/apps/web/src/layouts/workspace-layout.tsx +++ b/apps/web/src/layouts/workspace-layout.tsx @@ -140,11 +140,7 @@ export const Setting: FC = () => { return null; } return ( - + ); }; diff --git a/packages/component/src/components/setting-components/index.tsx b/packages/component/src/components/setting-components/index.tsx new file mode 100644 index 0000000000..d398d5f1d2 --- /dev/null +++ b/packages/component/src/components/setting-components/index.tsx @@ -0,0 +1,4 @@ +export { SettingModal, type SettingModalProps } from './modal'; +export { SettingHeader } from './setting-header'; +export { SettingRow } from './setting-row'; +export { SettingWrapper } from './wrapper'; diff --git a/packages/component/src/components/setting-components/modal.tsx b/packages/component/src/components/setting-components/modal.tsx new file mode 100644 index 0000000000..0290c7c9f5 --- /dev/null +++ b/packages/component/src/components/setting-components/modal.tsx @@ -0,0 +1,42 @@ +import { Modal, ModalCloseButton, ModalWrapper } from '@affine/component'; +import type { FC, PropsWithChildren } from 'react'; +import { useCallback } from 'react'; + +export type SettingModalProps = { + open: boolean; + setOpen: (value: boolean) => void; +}; + +export const SettingModal: FC> = ({ + children, + open, + setOpen, +}) => { + const handleClose = useCallback(() => { + setOpen(false); + }, [setOpen]); + + return ( + + + + {children} + + + ); +}; diff --git a/apps/web/src/components/affine/setting-modal/common/setting-header.tsx b/packages/component/src/components/setting-components/setting-header.tsx similarity index 100% rename from apps/web/src/components/affine/setting-modal/common/setting-header.tsx rename to packages/component/src/components/setting-components/setting-header.tsx diff --git a/apps/web/src/components/affine/setting-modal/common/setting-row.tsx b/packages/component/src/components/setting-components/setting-row.tsx similarity index 54% rename from apps/web/src/components/affine/setting-modal/common/setting-row.tsx rename to packages/component/src/components/setting-components/setting-row.tsx index 0bad48cb2d..6955a06ce3 100644 --- a/apps/web/src/components/affine/setting-modal/common/setting-row.tsx +++ b/packages/component/src/components/setting-components/setting-row.tsx @@ -1,22 +1,30 @@ +import clsx from 'clsx'; import type { CSSProperties, FC, PropsWithChildren, ReactElement } from 'react'; import { settingRow } from './share.css'; export const SettingRow: FC< PropsWithChildren<{ - name: string; + name: string | ReactElement; desc: string | ReactElement; style?: CSSProperties; onClick?: () => void; + spreadCol?: boolean; }> -> = ({ name, desc, children, onClick, style }) => { +> = ({ name, desc, children, onClick, style, spreadCol = true }) => { return ( -
+
{name}
{desc}
-
{children}
+ {spreadCol ?
{children}
: children}
); }; diff --git a/apps/web/src/components/affine/setting-modal/common/share.css.ts b/packages/component/src/components/setting-components/share.css.ts similarity index 82% rename from apps/web/src/components/affine/setting-modal/common/share.css.ts rename to packages/component/src/components/setting-components/share.css.ts index 9348c7120e..bfff7880bf 100644 --- a/apps/web/src/components/affine/setting-modal/common/share.css.ts +++ b/packages/component/src/components/setting-components/share.css.ts @@ -41,14 +41,26 @@ globalStyle(`${wrapper} .title`, { }); export const settingRow = style({ - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', marginBottom: '25px', color: 'var(--affine-text-primary-color)', + borderRadius: '8px', + selectors: { + '&.two-col': { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + }, + '&:last-of-type': { + marginBottom: '0', + }, + }, }); globalStyle(`${settingRow} .left-col`, { + flexShrink: 0, + maxWidth: '100%', +}); +globalStyle(`${settingRow}.two-col .left-col`, { flexShrink: 0, maxWidth: '80%', }); @@ -66,4 +78,5 @@ globalStyle(`${settingRow} .right-col`, { display: 'flex', justifyContent: 'flex-end', paddingLeft: '15px', + flexShrink: 0, }); diff --git a/apps/web/src/components/affine/setting-modal/common/wrapper.tsx b/packages/component/src/components/setting-components/wrapper.tsx similarity index 67% rename from apps/web/src/components/affine/setting-modal/common/wrapper.tsx rename to packages/component/src/components/setting-components/wrapper.tsx index 249360fbf6..3c7b8ff1f2 100644 --- a/apps/web/src/components/affine/setting-modal/common/wrapper.tsx +++ b/packages/component/src/components/setting-components/wrapper.tsx @@ -1,10 +1,11 @@ import type { FC, PropsWithChildren } from 'react'; import { wrapper } from './share.css'; -export const Wrapper: FC> = ({ - title, - children, -}) => { +export const SettingWrapper: FC< + PropsWithChildren<{ + title?: string; + }> +> = ({ title, children }) => { return (
{title ?
{title}
: null} diff --git a/packages/component/src/components/user-avatar/index.tsx b/packages/component/src/components/user-avatar/index.tsx new file mode 100644 index 0000000000..7a842881e8 --- /dev/null +++ b/packages/component/src/components/user-avatar/index.tsx @@ -0,0 +1,37 @@ +import * as Avatar from '@radix-ui/react-avatar'; +import clsx from 'clsx'; +import type { CSSProperties, FC } from 'react'; + +import * as style from './style.css'; + +export type UserAvatar = { + size?: number; + url?: string; + name?: string; + className?: string; + style?: CSSProperties; +}; + +export const UserAvatar: FC = ({ + size = 20, + style: propsStyles = {}, + url, + name, + className, +}) => { + return ( + + + + {name?.slice(0, 1) || 'A'} + + + ); +}; diff --git a/packages/component/src/components/user-avatar/style.css.ts b/packages/component/src/components/user-avatar/style.css.ts new file mode 100644 index 0000000000..837d0b656a --- /dev/null +++ b/packages/component/src/components/user-avatar/style.css.ts @@ -0,0 +1,31 @@ +import { style } from '@vanilla-extract/css'; + +export const avatarRoot = style({ + display: 'inline-flex', + flexShrink: 0, + alignItems: 'center', + justifyContent: 'center', + verticalAlign: 'middle', + overflow: 'hidden', + userSelect: 'none', + borderRadius: '100%', +}); + +export const avatarImage = style({ + width: '100%', + height: '100%', + objectFit: 'cover', + borderRadius: 'inherit', +}); +export const avatarFallback = style({ + width: '100%', + height: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + backgroundColor: 'var(--affine-primary-color)', + color: 'var(--affine-white)', + fontSize: 'var(--affine-font-base)', + lineHeight: '1', + fontWeight: '500', +}); diff --git a/packages/component/src/ui/button/utils.ts b/packages/component/src/ui/button/utils.ts index cb77cd302c..7f7f4de3b4 100644 --- a/packages/component/src/ui/button/utils.ts +++ b/packages/component/src/ui/button/utils.ts @@ -10,14 +10,14 @@ export const SIZE_CONFIG = { fontSize: 16, borderRadius: 4, height: 26, - padding: 24, + padding: 6, }, [SIZE_MIDDLE]: { iconSize: 20, fontSize: 16, borderRadius: 4, height: 32, - padding: 24, + padding: 12, }, [SIZE_DEFAULT]: { iconSize: 24, diff --git a/packages/i18n/src/resources/en.json b/packages/i18n/src/resources/en.json index b0310bcbfc..ee379db2d1 100644 --- a/packages/i18n/src/resources/en.json +++ b/packages/i18n/src/resources/en.json @@ -144,6 +144,7 @@ "Publishing": "Publishing to web requires AFFiNE Cloud service.", "Share with link": "Share with link", "Copy Link": "Copy Link", + "Copy": "Copy", "Publishing Description": "After publishing to the web, everyone can view the content of this workspace through the link.", "Stop publishing": "Stop publishing", "Publish to web": "Publish to web", @@ -245,7 +246,7 @@ "Sync across devices with AFFiNE Cloud": "Sync across devices with AFFiNE Cloud", "Update workspace name success": "Update workspace name success", "Create your own workspace": "Create your own workspace", - "Storage Folder Hint": "Check or change storage location.", + "Storage Folder Hint": "Check or change storage location. Click path to edit location.", "Save": "Save", "Customize": "Customize", "Move folder success": "Move folder success", @@ -359,5 +360,17 @@ "Discover what's new": "Discover what's new", "View the AFFiNE Changelog.": "View the AFFiNE Changelog.", "Privacy": "Privacy", - "Terms of Use": "Terms of Use" + "Terms of Use": "Terms of Use", + "Workspace Settings with name": "{{name}}'s Settings", + "You can customize your workspace here.": "You can customize your workspace here.", + "Info": "Info", + "Storage and Export": "Storage and Export", + "Workspace Profile": "Workspace Profile", + "Only an owner can edit the the Workspace avatar and name.Changes will be shown for everyone.": "Only an owner can edit the the Workspace avatar and name.Changes will be shown for everyone.", + "Storage": "Storage", + "Workspace saved locally": "{{name}} is saved locally", + "Enable cloud hint": "The following functions rely on AFFiNE Cloud. All data is stored on the current device. You can enable AFFiNE Cloud for this workspace to keep data in sync with the cloud.", + "Unpublished hint": "Once published to the web, visitors can view the contents through the provided link.", + "Published hint": "Visitors can view the contents through the provided link.", + "Members hint": "Manage members here, invite new member by email." }