mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-17 10:06:17 +08:00
refactor(infra): directory structure (#4615)
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
export { SettingHeader } from './setting-header';
|
||||
export { SettingRow } from './setting-row';
|
||||
export * from './storage-progess';
|
||||
export * from './workspace-detail-skeleton';
|
||||
export * from './workspace-list-skeleton';
|
||||
export { SettingWrapper } from './wrapper';
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { HTMLAttributes, ReactNode } from 'react';
|
||||
|
||||
import { settingHeader } from './share.css';
|
||||
|
||||
interface SettingHeaderProps
|
||||
extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
|
||||
title: ReactNode;
|
||||
subtitle?: ReactNode;
|
||||
}
|
||||
|
||||
export const SettingHeader = ({
|
||||
title,
|
||||
subtitle,
|
||||
...otherProps
|
||||
}: SettingHeaderProps) => {
|
||||
return (
|
||||
<div className={settingHeader} {...otherProps}>
|
||||
<div className="title">{title}</div>
|
||||
<div className="subtitle">{subtitle}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import clsx from 'clsx';
|
||||
import type { CSSProperties, PropsWithChildren, ReactNode } from 'react';
|
||||
|
||||
import { settingRow } from './share.css';
|
||||
|
||||
export type SettingRowProps = PropsWithChildren<{
|
||||
name: ReactNode;
|
||||
desc: ReactNode;
|
||||
style?: CSSProperties;
|
||||
onClick?: () => void;
|
||||
spreadCol?: boolean;
|
||||
'data-testid'?: string;
|
||||
disabled?: boolean;
|
||||
}>;
|
||||
|
||||
export const SettingRow = ({
|
||||
name,
|
||||
desc,
|
||||
children,
|
||||
onClick,
|
||||
style,
|
||||
spreadCol = true,
|
||||
disabled = false,
|
||||
...props
|
||||
}: PropsWithChildren<SettingRowProps>) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(settingRow, {
|
||||
'two-col': spreadCol,
|
||||
disabled,
|
||||
})}
|
||||
style={style}
|
||||
onClick={onClick}
|
||||
data-testid={props['data-testid']}
|
||||
>
|
||||
<div className="left-col">
|
||||
<div className="name">{name}</div>
|
||||
<div className="desc">{desc}</div>
|
||||
</div>
|
||||
{spreadCol ? <div className="right-col">{children}</div> : children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
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',
|
||||
},
|
||||
'&.disabled': {
|
||||
position: 'relative',
|
||||
},
|
||||
'&.disabled::after': {
|
||||
content: '',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: 'rgba(255,255,255,0.5)',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
globalStyle(`${settingRow} .left-col`, {
|
||||
flex: 1,
|
||||
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`, {
|
||||
width: '250px',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
paddingLeft: '15px',
|
||||
flexShrink: 0,
|
||||
});
|
||||
|
||||
export const storageProgressContainer = style({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
export const storageProgressWrapper = style({
|
||||
flexGrow: 1,
|
||||
marginRight: '20px',
|
||||
});
|
||||
|
||||
globalStyle(`${storageProgressWrapper} .storage-progress-desc`, {
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
height: '20px',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 2,
|
||||
});
|
||||
globalStyle(`${storageProgressWrapper} .storage-progress-bar-wrapper`, {
|
||||
height: '8px',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: 'var(--affine-pure-black-10)',
|
||||
overflow: 'hidden',
|
||||
});
|
||||
export const storageProgressBar = style({
|
||||
height: '100%',
|
||||
backgroundColor: 'var(--affine-processing-color)',
|
||||
selectors: {
|
||||
'&.warning': {
|
||||
// Wait for design
|
||||
backgroundColor: '#FF7C09',
|
||||
},
|
||||
'&.danger': {
|
||||
backgroundColor: 'var(--affine-error-color)',
|
||||
},
|
||||
},
|
||||
});
|
||||
export const storageExtendHint = style({
|
||||
borderRadius: '4px',
|
||||
padding: '4px 8px',
|
||||
backgroundColor: 'var(--affine-background-secondary-color)',
|
||||
color: 'var(--affine-text-secondary-color)',
|
||||
fontSize: 'var(--affine-font-xs)',
|
||||
lineHeight: '20px',
|
||||
marginTop: 8,
|
||||
});
|
||||
globalStyle(`${storageExtendHint} a`, {
|
||||
color: 'var(--affine-link-color)',
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import { Tooltip } from '@toeverything/components/tooltip';
|
||||
import clsx from 'clsx';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import * as styles from './share.css';
|
||||
|
||||
export interface StorageProgressProgress {
|
||||
max: number;
|
||||
value: number;
|
||||
onUpgrade: () => void;
|
||||
}
|
||||
|
||||
const transformBytesToMB = (bytes: number) => {
|
||||
return (bytes / 1024 / 1024).toFixed(2);
|
||||
};
|
||||
|
||||
const transformBytesToGB = (bytes: number) => {
|
||||
return (bytes / 1024 / 1024 / 1024).toFixed(2);
|
||||
};
|
||||
|
||||
export const StorageProgress = ({
|
||||
max: upperLimit,
|
||||
value,
|
||||
onUpgrade,
|
||||
}: StorageProgressProgress) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const percent = useMemo(
|
||||
() => Math.round((value / upperLimit) * 100),
|
||||
[upperLimit, value]
|
||||
);
|
||||
|
||||
const used = useMemo(() => transformBytesToMB(value), [value]);
|
||||
const max = useMemo(() => transformBytesToGB(upperLimit), [upperLimit]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.storageProgressContainer}>
|
||||
<div className={styles.storageProgressWrapper}>
|
||||
<div className="storage-progress-desc">
|
||||
<span>{t['com.affine.storage.used.hint']()}</span>
|
||||
<span>
|
||||
{used}MB/{max}GB
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="storage-progress-bar-wrapper">
|
||||
<div
|
||||
className={clsx(styles.storageProgressBar, {
|
||||
warning: percent > 80,
|
||||
danger: percent > 99,
|
||||
})}
|
||||
style={{ width: `${percent}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tooltip content={t['com.affine.storage.disabled.hint']()}>
|
||||
<span tabIndex={0}>
|
||||
<Button disabled onClick={onUpgrade}>
|
||||
{t['com.affine.storage.upgrade']()}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{percent > 80 ? (
|
||||
<div className={styles.storageExtendHint}>
|
||||
{t['com.affine.storage.extend.hint']()}
|
||||
<a
|
||||
href="https://community.affine.pro/c/insider-general/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{t['com.affine.storage.extend.link']()}
|
||||
</a>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { Skeleton } from '@mui/material';
|
||||
|
||||
import { SettingHeader } from './setting-header';
|
||||
import { SettingRow } from './setting-row';
|
||||
import { SettingWrapper } from './wrapper';
|
||||
|
||||
export const WorkspaceDetailSkeleton = () => {
|
||||
return (
|
||||
<>
|
||||
<SettingHeader title={<Skeleton />} subtitle={<Skeleton />} />
|
||||
|
||||
{new Array(3).fill(0).map((_, index) => {
|
||||
return (
|
||||
<SettingWrapper title={<Skeleton />} key={index}>
|
||||
<SettingRow
|
||||
name={<Skeleton />}
|
||||
desc={<Skeleton />}
|
||||
spreadCol={false}
|
||||
></SettingRow>
|
||||
</SettingWrapper>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import { Skeleton } from '@mui/material';
|
||||
|
||||
import { FlexWrapper } from '../../ui/layout';
|
||||
|
||||
export const WorkspaceListItemSkeleton = () => {
|
||||
return (
|
||||
<FlexWrapper
|
||||
alignItems="center"
|
||||
style={{ padding: '0 8px', height: 30, marginBottom: 4 }}
|
||||
>
|
||||
<Skeleton
|
||||
variant="circular"
|
||||
width={14}
|
||||
height={14}
|
||||
style={{ marginRight: 10 }}
|
||||
/>
|
||||
<Skeleton variant="rectangular" height={16} style={{ flexGrow: 1 }} />
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const WorkspaceListSkeleton = () => {
|
||||
return (
|
||||
<>
|
||||
{new Array(5).fill(0).map((_, index) => {
|
||||
return <WorkspaceListItemSkeleton key={index} />;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { PropsWithChildren, ReactNode } from 'react';
|
||||
|
||||
import { wrapper } from './share.css';
|
||||
|
||||
interface SettingWrapperProps {
|
||||
title?: ReactNode;
|
||||
}
|
||||
|
||||
export const SettingWrapper = ({
|
||||
title,
|
||||
children,
|
||||
}: PropsWithChildren<SettingWrapperProps>) => {
|
||||
return (
|
||||
<div className={wrapper}>
|
||||
{title ? <div className="title">{title}</div> : null}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user