feat: migrate workspace setting with new design to setting modal (#2900)

Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
Qi
2023-06-28 22:45:33 +08:00
committed by Alex Yang
parent 5a3e5a1565
commit 4cca8a16ab
33 changed files with 1540 additions and 141 deletions

View File

@@ -0,0 +1,4 @@
export { SettingModal, type SettingModalProps } from './modal';
export { SettingHeader } from './setting-header';
export { SettingRow } from './setting-row';
export { SettingWrapper } from './wrapper';

View File

@@ -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<PropsWithChildren<SettingModalProps>> = ({
children,
open,
setOpen,
}) => {
const handleClose = useCallback(() => {
setOpen(false);
}, [setOpen]);
return (
<Modal
open={open}
onClose={handleClose}
wrapperPosition={['center', 'center']}
data-testid="setting-modal"
>
<ModalWrapper
width={1080}
height={760}
style={{
maxHeight: '85vh',
maxWidth: '70vw',
overflow: 'hidden',
display: 'flex',
backgroundColor: 'var(--affine-white)',
}}
>
<ModalCloseButton top={16} right={20} onClick={handleClose} />
{children}
</ModalWrapper>
</Modal>
);
};

View File

@@ -0,0 +1,14 @@
import type { FC } from 'react';
import { settingHeader } from './share.css';
export const SettingHeader: FC<{ title: string; subtitle?: string }> = ({
title,
subtitle,
}) => {
return (
<div className={settingHeader}>
<div className="title">{title}</div>
<div className="subtitle">{subtitle}</div>
</div>
);
};

View File

@@ -0,0 +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 | ReactElement;
desc: string | ReactElement;
style?: CSSProperties;
onClick?: () => void;
spreadCol?: boolean;
}>
> = ({ name, desc, children, onClick, style, spreadCol = true }) => {
return (
<div
className={clsx(settingRow, {
'two-col': spreadCol,
})}
style={style}
onClick={onClick}
>
<div className="left-col">
<div className="name">{name}</div>
<div className="desc">{desc}</div>
</div>
{spreadCol ? <div className="right-col">{children}</div> : children}
</div>
);
};

View File

@@ -0,0 +1,82 @@
import { globalStyle, style } from '@vanilla-extract/css';
export const settingHeader = style({
height: '68px',
borderBottom: '1px solid var(--affine-border-color)',
marginBottom: '24px',
});
globalStyle(`${settingHeader} .title`, {
fontSize: 'var(--affine-font-base)',
fontWeight: 600,
lineHeight: '24px',
marginBottom: '4px',
});
globalStyle(`${settingHeader} .subtitle`, {
fontSize: 'var(--affine-font-xs)',
lineHeight: '16px',
color: 'var(--affine-text-secondary-color)',
});
export const wrapper = style({
borderBottom: '1px solid var(--affine-border-color)',
paddingBottom: '24px',
marginBottom: '24px',
selectors: {
'&:last-of-type': {
borderBottom: 'none',
paddingBottom: '0',
marginBottom: '0',
},
},
});
globalStyle(`${wrapper} .title`, {
fontSize: 'var(--affine-font-sm)',
fontWeight: 600,
lineHeight: '18px',
color: 'var(--affine-text-secondary-color)',
marginBottom: '16px',
});
export const settingRow = style({
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%',
});
globalStyle(`${settingRow} .name`, {
marginBottom: '2px',
fontSize: 'var(--affine-font-sm)',
fontWeight: 600,
});
globalStyle(`${settingRow} .desc`, {
fontSize: 'var(--affine-font-xs)',
color: 'var(--affine-text-secondary-color)',
});
globalStyle(`${settingRow} .right-col`, {
flexGrow: 1,
display: 'flex',
justifyContent: 'flex-end',
paddingLeft: '15px',
flexShrink: 0,
});

View File

@@ -0,0 +1,15 @@
import type { FC, PropsWithChildren } from 'react';
import { wrapper } from './share.css';
export const SettingWrapper: FC<
PropsWithChildren<{
title?: string;
}>
> = ({ title, children }) => {
return (
<div className={wrapper}>
{title ? <div className="title">{title}</div> : null}
{children}
</div>
);
};

View File

@@ -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<UserAvatar> = ({
size = 20,
style: propsStyles = {},
url,
name,
className,
}) => {
return (
<Avatar.Root
style={{
width: size,
height: size,
...propsStyles,
}}
className={clsx(style.avatarRoot, className)}
>
<Avatar.Image className={style.avatarImage} src={url} alt={name} />
<Avatar.Fallback className={style.avatarFallback} delayMs={600}>
{name?.slice(0, 1) || 'A'}
</Avatar.Fallback>
</Avatar.Root>
);
};

View File

@@ -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',
});

View File

@@ -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,

View File

@@ -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."
}