feat(ios): cleanup ui (#15535)

This commit is contained in:
DarkSky
2026-08-27 17:53:36 +08:00
committed by GitHub
parent 612923b55c
commit ca056ae7b9
23 changed files with 376 additions and 1176 deletions
@@ -1,5 +1,6 @@
import { subHeadlineRegular } from '@toeverything/theme/typography';
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css';
import { globalStyle, style } from '@vanilla-extract/css';
export const header = style({
display: 'flex',
@@ -44,8 +45,35 @@ export const docTitleContainer = style({
lineHeight: '50px',
fontWeight: 700,
padding: '38px 0',
selectors: {
'[data-mobile] &': {
display: 'flex',
alignItems: 'baseline',
gap: 8,
fontSize: 28,
lineHeight: '34px',
fontWeight: 700,
letterSpacing: 0.38,
padding: '24px 0 20px',
},
},
});
globalStyle(`[data-mobile] ${docTitleContainer} > span:not(:first-child)`, {
fontSize: 15,
fontWeight: 600,
letterSpacing: -0.23,
margin: 0,
padding: 0,
lineHeight: '20px',
color: cssVarV2('text/secondary'),
});
globalStyle(
`[data-mobile] ${docTitleContainer} > [data-testid="date-today-label"]`,
{ color: cssVarV2('text/emphasis') }
);
export const placeholder = style({
height: 200,
width: '100%',
@@ -55,6 +83,14 @@ export const placeholder = style({
justifyContent: 'center',
border: `1px dashed ${cssVarV2.layer.insideBorder.border}`,
borderRadius: 8,
selectors: {
'[data-mobile] &': {
minHeight: 180,
height: 'auto',
border: 0,
borderRadius: 0,
},
},
});
export const placeholderIcon = style({
@@ -68,6 +104,13 @@ export const placeholderIcon = style({
justifyContent: 'center',
fontSize: 20,
marginBottom: 4,
selectors: {
'[data-mobile] &': {
width: 40,
height: 40,
marginBottom: 12,
},
},
});
export const placeholderText = style({
@@ -75,4 +118,29 @@ export const placeholderText = style({
lineHeight: '22px',
marginBottom: 16,
color: cssVarV2.text.tertiary,
selectors: {
'[data-mobile] &': {
fontSize: 17,
lineHeight: '22px',
fontWeight: 600,
letterSpacing: -0.43,
marginBottom: 0,
color: cssVarV2('text/primary'),
},
},
});
export const placeholderDescription = style([
subHeadlineRegular,
{
display: 'none',
color: cssVarV2('text/secondary'),
selectors: {
'[data-mobile] &': {
display: 'block',
marginTop: 4,
marginBottom: 20,
},
},
},
]);
@@ -17,7 +17,7 @@ import {
WorkbenchService,
} from '@affine/core/modules/workbench';
import { useI18n } from '@affine/i18n';
import { TodayIcon } from '@blocksuite/icons/rc';
import { PlusIcon, TodayIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import dayjs from 'dayjs';
import type { Location } from 'history';
@@ -64,8 +64,13 @@ export const JournalPlaceholder = ({ dateString }: { dateString: string }) => {
<div className={styles.placeholderText}>
{t['com.affine.journal.placeholder.title']()}
</div>
<div className={styles.placeholderDescription}>
{t['com.affine.journal.placeholder.description']()}
</div>
<Button
variant="primary"
size={BUILD_CONFIG.isMobileEdition ? 'extraLarge' : undefined}
prefix={BUILD_CONFIG.isMobileEdition ? <PlusIcon /> : undefined}
onClick={createJournal}
data-testid="confirm-create-journal-button"
>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

@@ -12,7 +12,6 @@ import { useCallback, useEffect } from 'react';
import { AboutGroup } from './about';
import { AppearanceGroup } from './appearance';
import teamPeople from './assets/team-people.png';
import { DevicesGroup } from './devices';
import { ExperimentalFeatureSetting } from './experimental';
import { SettingGroup } from './group';
@@ -20,7 +19,7 @@ import { OthersGroup } from './others';
import { DeleteAccount } from './others/delete-account';
import { RowLayout } from './row.layout';
import * as styles from './style.css';
import { UserSubscription } from './subscription';
import { PlansGroup } from './subscription';
import { SwipeDialog } from './swipe-dialog';
import { UserProfile } from './user-profile';
import { UserUsage } from './user-usage';
@@ -31,7 +30,6 @@ const AFFINE_MOBILE_STORE_URL = BUILD_CONFIG.isIOS
? 'https://play.google.com/store/apps/details?id=app.affine.pro'
: undefined;
const AFFINE_DOWNLOAD_URL = 'https://affine.pro/download';
const AFFINE_TEAM_URL = 'https://affine.pro/teamhub';
const SupportGroup = () => {
const t = useI18n();
@@ -80,29 +78,6 @@ const SupportGroup = () => {
);
};
const TeamPromotionCard = () => {
const t = useI18n();
const urlService = useService(UrlService);
return (
<button
type="button"
className={styles.promoCard}
onClick={() => urlService.openExternal(AFFINE_TEAM_URL)}
>
<span className={styles.promoCardContent}>
<span className={styles.promoCardTitle}>
{t['com.affine.mobile.setting.promo.title']()}
</span>
<span className={styles.promoCardDescription}>
{t['com.affine.mobile.setting.promo.description']()}
</span>
</span>
<img className={styles.promoCardArt} src={teamPeople} alt="" />
</button>
);
};
const DangerZoneGroup = ({
onDeleteFinished,
}: {
@@ -143,14 +118,13 @@ const MobileSetting = ({
return (
<div className={styles.root}>
<UserSubscription />
<UserProfile />
<UserUsage />
<PlansGroup />
{status === 'authenticated' ? <DevicesGroup /> : null}
<AppearanceGroup />
<AboutGroup />
<ExperimentalFeatureSetting />
<TeamPromotionCard />
<SupportGroup />
<OthersGroup />
<DangerZoneGroup onDeleteFinished={onDeleteFinished} />
@@ -8,13 +8,21 @@ import * as styles from './style.css';
export const RowLayout = ({
label,
description,
prefix,
children,
href,
onClick,
className,
emphasized,
}: PropsWithChildren<{
label: ReactNode;
description?: ReactNode;
prefix?: ReactNode;
href?: string;
onClick?: () => void;
className?: string;
emphasized?: boolean;
}>) => {
const isLinkRow = !!href && !onClick;
const isButtonRow = !!onClick;
@@ -42,7 +50,19 @@ export const RowLayout = ({
const content = (
<>
<div className={styles.baseSettingItemName}>{label}</div>
{prefix ? <div className={styles.rowPrefix}>{prefix}</div> : null}
<div className={styles.rowText}>
<div
className={clsx(styles.baseSettingItemName, {
[styles.emphasizedSettingItemName]: emphasized,
})}
>
{label}
</div>
{description ? (
<div className={styles.rowDescription}>{description}</div>
) : null}
</div>
<div className={styles.baseSettingItemAction}>
{children ??
(isInteractive ? (
@@ -55,9 +75,11 @@ export const RowLayout = ({
return (
<ConfigModal.Row
data-testid="setting-row"
className={clsx(styles.baseSettingItem, {
[styles.interactiveRow]: isInteractive,
})}
className={clsx(
styles.baseSettingItem,
{ [styles.interactiveRow]: isInteractive },
className
)}
onClick={isButtonRow ? handleTrigger : undefined}
onKeyDown={isButtonRow ? handleKeyDown : undefined}
role={isButtonRow ? 'button' : undefined}
@@ -1,4 +1,8 @@
import { bodyEmphasized, bodyRegular } from '@toeverything/theme/typography';
import {
bodyEmphasized,
bodyRegular,
footnoteRegular,
} from '@toeverything/theme/typography';
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css';
@@ -58,6 +62,40 @@ export const baseSettingItemName = style([
},
]);
export const emphasizedSettingItemName = style([bodyEmphasized]);
export const rowText = style({
minWidth: 0,
flex: 1,
display: 'flex',
flexDirection: 'column',
gap: 2,
});
export const rowDescription = style([
footnoteRegular,
{
color: cssVarV2('text/secondary'),
display: '-webkit-box',
overflow: 'hidden',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: 2,
},
]);
export const rowPrefix = style({
width: 32,
height: 32,
flex: '0 0 auto',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 8,
fontSize: 18,
color: cssVarV2('icon/primary'),
background: cssVarV2('layer/background/secondary'),
});
export const baseSettingItemAction = style([
bodyRegular,
{
@@ -98,94 +136,6 @@ export const linkIcon = style({
color: cssVarV2('icon/secondary'),
});
export const promoCard = style({
position: 'relative',
overflow: 'hidden',
border: '0.5px solid rgba(255,255,255,0.14)',
borderRadius: 30,
padding: '16px 20px 14px',
width: '100%',
minHeight: 116,
display: 'flex',
alignItems: 'flex-start',
justifyContent: 'flex-start',
boxSizing: 'border-box',
textAlign: 'left',
backgroundColor: cssVarV2('button/primary'),
backgroundImage:
'linear-gradient(180deg, rgba(255,255,255,0.10) 0%, rgba(255,255,255,0.04) 34%, rgba(255,255,255,0.02) 100%)',
color: cssVarV2('button/pureWhiteText'),
cursor: 'pointer',
isolation: 'isolate',
transition: 'transform 180ms ease, box-shadow 180ms ease',
boxShadow:
'0 10px 20px rgba(13, 40, 99, 0.12), inset 0 1px 0 rgba(255,255,255,0.12)',
selectors: {
'&::before': {
content: '""',
position: 'absolute',
inset: 0,
background:
'linear-gradient(180deg, rgba(255,255,255,0.10) 0%, rgba(255,255,255,0) 48%)',
pointerEvents: 'none',
zIndex: 0,
},
'&:active': {
transform: 'scale(0.995)',
boxShadow:
'0 6px 12px rgba(13, 40, 99, 0.1), inset 0 1px 0 rgba(255,255,255,0.1)',
},
},
});
export const promoCardContent = style({
position: 'relative',
zIndex: 2,
display: 'flex',
flexDirection: 'column',
gap: 6,
width: '100%',
maxWidth: 'none',
paddingRight: 0,
});
export const promoCardTitle = style({
display: 'block',
paddingRight: 72,
fontSize: 20,
lineHeight: '26px',
fontWeight: 600,
color: cssVarV2('button/pureWhiteText'),
whiteSpace: 'nowrap',
textShadow: '0 0.5px 1px rgba(7, 48, 121, 0.12)',
});
export const promoCardDescription = style({
display: 'block',
width: '100%',
boxSizing: 'border-box',
maxWidth: 'none',
paddingRight: 96,
fontSize: 16,
lineHeight: '21px',
color: cssVarV2('button/pureWhiteText'),
opacity: 0.94,
textShadow: '0 0.5px 1px rgba(7, 48, 121, 0.08)',
});
export const promoCardArt = style({
position: 'absolute',
right: 14,
bottom: 8,
width: 80,
height: 'auto',
objectFit: 'contain',
pointerEvents: 'none',
zIndex: 1,
filter: 'drop-shadow(0 6px 12px rgba(7, 48, 121, 0.12))',
opacity: 0.9,
});
export const dangerZoneTitle = style({
color: cssVarV2('status/error'),
});
@@ -1,18 +1,23 @@
import { Button } from '@affine/component';
import { AuthService, ServerService } from '@affine/core/modules/cloud';
import { GlobalDialogService } from '@affine/core/modules/dialogs';
import { NativePaywallService } from '@affine/core/modules/paywall';
import { UrlService } from '@affine/core/modules/url';
import { useI18n } from '@affine/i18n';
import { DiamondIcon, MultiPeopleIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import { useCallback } from 'react';
import proDiamond from '../assets/pro-diamond.png';
import { SettingGroup } from '../group';
import { RowLayout } from '../row.layout';
import * as styles from './styles.css';
export const UserSubscription = () => {
const AFFINE_TEAM_URL = 'https://affine.pro/teamhub';
export const PlansGroup = () => {
const serverService = useService(ServerService);
const authService = useService(AuthService);
const globalDialogService = useService(GlobalDialogService);
const urlService = useService(UrlService);
const nativePaywallProvider =
useService(NativePaywallService).getNativePaywallProvider();
const t = useI18n();
@@ -31,30 +36,28 @@ export const UserSubscription = () => {
void nativePaywallProvider?.showPaywall('Pro').catch(console.error);
}, [globalDialogService, loggedIn, nativePaywallProvider]);
if (!nativePaywallProvider || supported === false) {
return null;
}
return (
<div className={styles.root}>
<div className={styles.content}>
<div className={styles.headerRow}>
<div className={styles.perkIconWrapper}>
<img className={styles.perkIcon} src={proDiamond} alt="" />
</div>
<div className={styles.textBlock}>
<div className={styles.title}>
{t['com.affine.mobile.setting.subscription.title']()}
</div>
<div className={styles.description}>
{t['com.affine.mobile.setting.subscription.description']()}
</div>
</div>
</div>
</div>
<Button className={styles.button} variant="primary" onClick={handleOpen}>
{t['com.affine.mobile.setting.subscription.button']()}
</Button>
</div>
<SettingGroup title={t['com.affine.mobile.setting.plans.title']()}>
{nativePaywallProvider && supported !== false ? (
<RowLayout
className={styles.planRow}
emphasized
prefix={<DiamondIcon />}
label={t['com.affine.mobile.setting.subscription.title']()}
description={t[
'com.affine.mobile.setting.subscription.description'
]()}
onClick={handleOpen}
/>
) : null}
<RowLayout
className={styles.planRow}
emphasized
prefix={<MultiPeopleIcon />}
label={t['com.affine.mobile.setting.promo.title']()}
description={t['com.affine.mobile.setting.promo.description']()}
onClick={() => urlService.openExternal(AFFINE_TEAM_URL)}
/>
</SettingGroup>
);
};
@@ -1,76 +1,5 @@
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css';
export const root = style({
display: 'flex',
flexDirection: 'column',
gap: 18,
borderRadius: 24,
padding: '24px',
backgroundColor: cssVarV2('layer/background/primary'),
boxSizing: 'border-box',
});
export const content = style({
display: 'flex',
flexDirection: 'column',
alignItems: 'stretch',
});
export const headerRow = style({
display: 'flex',
alignItems: 'center',
gap: 16,
width: '100%',
});
export const perkIconWrapper = style({
width: 42,
height: 42,
borderRadius: '50%',
backgroundColor: cssVarV2('layer/background/secondary'),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
});
export const textBlock = style({
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
gap: 8,
minWidth: 0,
flex: 1,
});
export const title = style({
fontSize: '18px',
lineHeight: '22px',
fontWeight: 600,
color: cssVarV2('text/primary'),
textAlign: 'left',
});
export const perkIcon = style({
width: 18,
height: 18,
flexShrink: 0,
objectFit: 'contain',
});
export const description = style({
fontSize: '14px',
lineHeight: '19px',
fontWeight: 400,
color: cssVarV2('text/secondary'),
maxWidth: 250,
});
export const button = style({
width: '100%',
minHeight: 48,
fontSize: '15px',
fontWeight: 600,
borderRadius: 999,
export const planRow = style({
minHeight: 68,
});
@@ -1,3 +1,4 @@
import { bodyEmphasized } from '@toeverything/theme/typography';
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css';
@@ -13,13 +14,10 @@ export const header = style({
backgroundColor: cssVarV2('layer/background/primary'),
});
export const headerTitle = style({
color: cssVarV2('text/primary'),
fontSize: 17,
lineHeight: '22px',
fontWeight: 600,
letterSpacing: -0.43,
});
export const headerTitle = style([
bodyEmphasized,
{ color: cssVarV2('text/primary') },
]);
export const journalDatePicker = style({
backgroundColor: cssVarV2('layer/background/primary'),
@@ -1,3 +1,7 @@
import {
headlineRegular,
subHeadlineRegular,
} from '@toeverything/theme/typography';
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css';
@@ -14,10 +18,10 @@ export const emptyState = style({
});
export const illustration = style({
width: 96,
height: 96,
width: 80,
height: 80,
objectFit: 'contain',
marginBottom: 24,
marginBottom: 16,
userSelect: 'none',
});
@@ -25,31 +29,27 @@ export const copy = style({
width: '100%',
maxWidth: 280,
textAlign: 'center',
marginBottom: 28,
marginBottom: 20,
});
export const title = style({
margin: 0,
fontSize: 21,
lineHeight: '28px',
fontWeight: 700,
color: cssVarV2('text/primary'),
});
export const title = style([
headlineRegular,
{
margin: 0,
color: cssVarV2('text/primary'),
},
]);
export const description = style({
margin: '10px 0 0',
fontSize: 18,
lineHeight: '24px',
fontWeight: 400,
color: cssVarV2('text/secondary'),
});
export const description = style([
subHeadlineRegular,
{
margin: '6px 0 0',
color: cssVarV2('text/secondary'),
},
]);
export const actionButton = style({
minWidth: 164,
borderRadius: 10,
fontSize: 20,
fontWeight: 600,
boxShadow: `0 8px 18px ${cssVarV2('layer/insideBorder/border')}`,
borderRadius: 8,
});
export const actionIcon = style({
@@ -1,3 +1,4 @@
import { bodyRegular } from '@toeverything/theme/typography';
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css';
@@ -11,6 +12,7 @@ export const header = style({
top: 0,
backgroundColor: cssVarV2('layer/background/mobile/primary'),
zIndex: 1,
borderBottom: `0.5px solid ${cssVarV2('layer/insideBorder/border')}`,
});
export const headerSpace = style([basicHeader]);
export const headerContent = style([
@@ -26,18 +28,32 @@ export const headerContent = style([
export const tabs = style({
height: 44,
gap: 16,
gap: 20,
display: 'flex',
alignItems: 'center',
});
export const tab = style({
fontSize: 20,
fontWeight: 600,
lineHeight: '28px',
color: cssVarV2('text/tertiary'),
selectors: {
'&[data-active="true"]': {
color: cssVarV2('text/primary'),
export const tab = style([
bodyRegular,
{
position: 'relative',
height: 44,
display: 'flex',
alignItems: 'center',
color: cssVarV2('tab/fontColor/default'),
selectors: {
'&[data-active="true"]': {
fontWeight: 600,
color: cssVarV2('tab/fontColor/active'),
},
'&[data-active="true"]::after': {
content: '""',
position: 'absolute',
right: 0,
bottom: 0,
left: 0,
height: 2,
background: cssVarV2('tab/divider/indicator'),
},
},
},
});
]);