diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx
index 1fe7c6ef95..6cb38e898c 100644
--- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx
+++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/index.tsx
@@ -1,115 +1,23 @@
import { RadioButton, RadioButtonGroup } from '@affine/component';
-import { SettingHeader } from '@affine/component/setting-components';
import {
- cancelSubscriptionMutation,
- checkoutMutation,
pricesQuery,
SubscriptionPlan,
SubscriptionRecurring,
- updateSubscriptionMutation,
} from '@affine/graphql';
-import { useMutation, useQuery } from '@affine/workspace/affine/gql';
-import { DoneIcon } from '@blocksuite/icons';
-import { Button } from '@toeverything/components/button';
-import {
- type PropsWithChildren,
- Suspense,
- useCallback,
- useEffect,
- useRef,
- useState,
-} from 'react';
+import { useQuery } from '@affine/workspace/affine/gql';
+import { Suspense, useEffect, useRef, useState } from 'react';
import { useCurrentLoginStatus } from '../../../../../hooks/affine/use-current-login-status';
-import {
- type SubscriptionMutator,
- useUserSubscription,
-} from '../../../../../hooks/use-subscription';
-import { BulledListIcon } from './icons/bulled-list';
+import { useUserSubscription } from '../../../../../hooks/use-subscription';
+import { PlanLayout } from './layout';
+import { type FixedPrice, getPlanDetail, PlanCard } from './plan-card';
+import { PlansSkeleton } from './skeleton';
import * as styles from './style.css';
-interface FixedPrice {
- type: 'fixed';
- plan: SubscriptionPlan;
- price: string;
- yearlyPrice: string;
- discount?: string;
- benefits: string[];
-}
-
-interface DynamicPrice {
- type: 'dynamic';
- plan: SubscriptionPlan;
- contact: boolean;
- benefits: string[];
-}
-
-// TODO: i18n all things
-const planDetail = new Map([
- [
- SubscriptionPlan.Free,
- {
- type: 'fixed',
- plan: SubscriptionPlan.Free,
- price: '0',
- yearlyPrice: '0',
- benefits: [
- 'Unlimited local workspace',
- 'Unlimited login devices',
- 'Unlimited blocks',
- 'AFFiNE Cloud Storage 10GB',
- 'The maximum file size is 10M',
- 'Number of members per Workspace ≤ 3',
- ],
- },
- ],
- [
- SubscriptionPlan.Pro,
- {
- type: 'fixed',
- plan: SubscriptionPlan.Pro,
- price: '1',
- yearlyPrice: '1',
- benefits: [
- 'Unlimited local workspace',
- 'Unlimited login devices',
- 'Unlimited blocks',
- 'AFFiNE Cloud Storage 100GB',
- 'The maximum file size is 500M',
- 'Number of members per Workspace ≤ 10',
- ],
- },
- ],
- [
- SubscriptionPlan.Team,
- {
- type: 'dynamic',
- plan: SubscriptionPlan.Team,
- contact: true,
- benefits: [
- 'Best team workspace for collaboration and knowledge distilling.',
- 'Focusing on what really matters with team project management and automation.',
- 'Pay for seats, fits all team size.',
- ],
- },
- ],
- [
- SubscriptionPlan.Enterprise,
- {
- type: 'dynamic',
- plan: SubscriptionPlan.Enterprise,
- contact: true,
- benefits: [
- 'Solutions & best practices for dedicated needs.',
- 'Embedable & interrogations with IT support.',
- ],
- },
- ],
-]);
-
const Settings = () => {
const [subscription, mutateSubscription] = useUserSubscription();
const loggedIn = useCurrentLoginStatus() === 'authenticated';
+ const planDetail = getPlanDetail();
const scrollWrapper = useRef(null);
const {
@@ -136,7 +44,6 @@ const Settings = () => {
);
const currentPlan = subscription?.plan ?? SubscriptionPlan.Free;
- const currentRecurring = subscription?.recurring;
const yearlyDiscount = (
planDetail.get(SubscriptionPlan.Pro) as FixedPrice | undefined
@@ -168,324 +75,66 @@ const Settings = () => {
};
}, [recurring]);
- return (
- <>
-
- You are current on the {currentPlan} plan. If you have any
- questions, please contact our{' '}
- {/*TODO: add action*/}customer support.
-
- ) : (
-
- This is the Pricing plans of AFFiNE Cloud. You can sign up or sign
- in to your account first.
-
- )
- }
- />
-
-
- {Object.values(SubscriptionRecurring).map(plan => (
-
- {plan}
- {plan === SubscriptionRecurring.Yearly && yearlyDiscount && (
-
- {yearlyDiscount}% off
-
- )}
-
- ))}
-
-
- {Array.from(planDetail.values()).map(detail => {
- const isCurrent =
- loggedIn &&
- detail.plan === currentPlan &&
- (currentPlan === SubscriptionPlan.Free
- ? true
- : currentRecurring === recurring);
- return (
-
-
-
- {detail.plan}{' '}
- {'discount' in detail &&
- recurring === SubscriptionRecurring.Yearly && (
-
- {detail.discount}% off
-
- )}
-
-
-
- {detail.type === 'dynamic' ? (
-
- Coming soon...
-
- ) : (
- <>
-
- $
- {recurring === SubscriptionRecurring.Monthly
- ? detail.price
- : detail.yearlyPrice}
-
-
- per month
-
- >
- )}
-
-
- {
- // branches:
- // if contact => 'Contact Sales'
- // if not signed in:
- // if free => 'Sign up free'
- // else => 'Buy Pro'
- // else
- // if isCurrent => 'Current Plan'
- // else if free => 'Downgrade'
- // else if currentRecurring !== recurring => 'Change to {recurring} Billing'
- // else => 'Upgrade'
- // TODO: should replace with components with proper actions
- detail.type === 'dynamic' ? (
-
- ) : loggedIn ? (
- detail.plan === currentPlan &&
- (currentRecurring === recurring ||
- (!currentRecurring &&
- detail.plan === SubscriptionPlan.Free)) ? (
-
- ) : detail.plan === SubscriptionPlan.Free ? (
-
- ) : currentRecurring !== recurring &&
- currentPlan === detail.plan ? (
-
- ) : (
-
- )
- ) : (
-
- {detail.plan === SubscriptionPlan.Free
- ? 'Sign up free'
- : 'Buy Pro'}
-
- )
- }
-
-
- {detail.benefits.map((content, i) => (
-
-
- {detail.type == 'dynamic' ? (
-
- ) : (
-
- )}
-
- {content}
-
- ))}
-
-
- );
- })}
-
-
- See all plans →{/* TODO: icon */}
-
-
- >
+ const subtitle = loggedIn ? (
+
+ You are current on the {currentPlan} plan. If you have any questions,
+ please contact our {/*TODO: add action*/}customer support.
+
+ ) : (
+
+ This is the Pricing plans of AFFiNE Cloud. You can sign up or sign in to
+ your account first.
+
);
-};
-const Downgrade = ({
- onSubscriptionUpdate,
-}: {
- onSubscriptionUpdate: SubscriptionMutator;
-}) => {
- const { isMutating, trigger } = useMutation({
- mutation: cancelSubscriptionMutation,
- });
+ const getRecurringLabel = (recurring: SubscriptionRecurring) =>
+ ({
+ [SubscriptionRecurring.Monthly]: 'Monthly',
+ [SubscriptionRecurring.Yearly]: 'Annually',
+ })[recurring];
- const downgrade = useCallback(() => {
- trigger(null, {
- onSuccess: data => {
- onSubscriptionUpdate(data.cancelSubscription);
- },
- });
- }, [trigger, onSubscriptionUpdate]);
-
- return (
-
+ {Object.values(SubscriptionRecurring).map(recurring => (
+
+ {getRecurringLabel(recurring)}
+ {recurring === SubscriptionRecurring.Yearly && yearlyDiscount && (
+
+ {yearlyDiscount}% off
+
+ )}
+
+ ))}
+
);
-};
-const Upgrade = ({
- recurring,
- onSubscriptionUpdate,
-}: {
- recurring: SubscriptionRecurring;
- onSubscriptionUpdate: SubscriptionMutator;
-}) => {
- const { isMutating, trigger } = useMutation({
- mutation: checkoutMutation,
- });
-
- const newTabRef = useRef(null);
-
- const onClose = useCallback(() => {
- newTabRef.current = null;
- onSubscriptionUpdate();
- }, [onSubscriptionUpdate]);
-
- const upgrade = useCallback(() => {
- if (newTabRef.current) {
- newTabRef.current.focus();
- } else {
- trigger(
- { recurring },
- {
- onSuccess: data => {
- // FIXME: safari prevents from opening new tab by window api
- // TODO(@xp): what if electron?
- const newTab = window.open(
- data.checkout,
- '_blank',
- 'noopener noreferrer'
- );
-
- if (newTab) {
- newTabRef.current = newTab;
-
- newTab.addEventListener('close', onClose);
- }
- },
- }
- );
- }
- }, [trigger, recurring, onClose]);
-
- useEffect(() => {
- return () => {
- if (newTabRef.current) {
- newTabRef.current.removeEventListener('close', onClose);
- newTabRef.current = null;
- }
- };
- }, [onClose]);
+ const scroll = (
+
+ {Array.from(planDetail.values()).map(detail => {
+ return (
+
+ );
+ })}
+
+ );
return (
-
- );
-};
-
-const ChangeRecurring = ({
- from: _from /* TODO: from can be useful when showing confirmation modal */,
- to,
- onSubscriptionUpdate,
-}: {
- from: SubscriptionRecurring;
- to: SubscriptionRecurring;
- onSubscriptionUpdate: SubscriptionMutator;
-}) => {
- const { isMutating, trigger } = useMutation({
- mutation: updateSubscriptionMutation,
- });
-
- const change = useCallback(() => {
- trigger(
- { recurring: to },
- {
- onSuccess: data => {
- onSubscriptionUpdate(data.updateSubscriptionRecurring);
- },
- }
- );
- }, [trigger, onSubscriptionUpdate, to]);
-
- return (
-
- );
-};
-
-const ContactSales = () => {
- return (
- // TODO: add action
-
- );
-};
-
-const CurrentPlan = () => {
- return ;
-};
-
-const SignupAction = ({ children }: PropsWithChildren) => {
- // TODO: add login action
- return (
-
+
);
};
export const AFFiNECloudPlans = () => {
return (
- // TODO: loading skeleton
// TODO: Error Boundary
-
+ }>
);
diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/layout.css.ts b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/layout.css.ts
new file mode 100644
index 0000000000..72ab6ab5b0
--- /dev/null
+++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/layout.css.ts
@@ -0,0 +1,36 @@
+import { style } from '@vanilla-extract/css';
+
+export const plansLayoutRoot = style({
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '24px',
+});
+
+export const scrollArea = style({
+ marginLeft: 'calc(-1 * var(--setting-modal-gap-x))',
+ paddingLeft: 'var(--setting-modal-gap-x)',
+ width: 'var(--setting-modal-width)',
+ overflowX: 'auto',
+ scrollSnapType: 'x mandatory',
+ paddingBottom: '21px',
+
+ '::-webkit-scrollbar': {
+ display: 'block',
+ height: '5px',
+ background: 'transparent',
+ },
+ '::-webkit-scrollbar-thumb': {
+ background: 'var(--affine-icon-secondary)',
+ borderRadius: '5px',
+ },
+});
+
+export const allPlansLink = style({
+ display: 'flex',
+ alignItems: 'center',
+ gap: '4px',
+ color: 'var(--affine-link-color)',
+ background: 'transparent',
+ borderColor: 'transparent',
+ fontSize: 'var(--affine-font-xs)',
+});
diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/layout.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/layout.tsx
new file mode 100644
index 0000000000..424e944cd0
--- /dev/null
+++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/layout.tsx
@@ -0,0 +1,52 @@
+import { SettingHeader } from '@affine/component/setting-components';
+import { ArrowRightBigIcon } from '@blocksuite/icons';
+import type { HtmlHTMLAttributes, ReactNode } from 'react';
+
+import * as styles from './layout.css';
+
+export interface PlanLayoutProps
+ extends Omit, 'title'> {
+ title?: ReactNode;
+ subtitle: ReactNode;
+ tabs: ReactNode;
+ scroll: ReactNode;
+ footer?: ReactNode;
+ scrollRef?: React.RefObject;
+}
+
+const SeeAllLink = () => (
+
+ See all plans
+ {}
+
+);
+
+export const PlanLayout = ({
+ subtitle,
+ tabs,
+ scroll,
+ title = 'Pricing Plans',
+ footer = ,
+ scrollRef,
+}: PlanLayoutProps) => {
+ return (
+
+ {/* TODO: SettingHeader component shouldn't have margin itself */}
+
+ {tabs}
+
+ {scroll}
+
+ {footer}
+
+ );
+};
diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx
new file mode 100644
index 0000000000..6dd027fe79
--- /dev/null
+++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/plan-card.tsx
@@ -0,0 +1,381 @@
+import type {
+ Subscription,
+ SubscriptionMutator,
+} from '@affine/core/hooks/use-subscription';
+import {
+ cancelSubscriptionMutation,
+ checkoutMutation,
+ SubscriptionPlan,
+ SubscriptionRecurring,
+ updateSubscriptionMutation,
+} from '@affine/graphql';
+import { useMutation } from '@affine/workspace/affine/gql';
+import { DoneIcon } from '@blocksuite/icons';
+import { Button } from '@toeverything/components/button';
+import { type PropsWithChildren, useCallback, useEffect, useRef } from 'react';
+
+import { useCurrentLoginStatus } from '../../../../../hooks/affine/use-current-login-status';
+import { BulledListIcon } from './icons/bulled-list';
+import * as styles from './style.css';
+
+export interface FixedPrice {
+ type: 'fixed';
+ plan: SubscriptionPlan;
+ price: string;
+ yearlyPrice: string;
+ discount?: string;
+ benefits: string[];
+}
+
+export interface DynamicPrice {
+ type: 'dynamic';
+ plan: SubscriptionPlan;
+ contact: boolean;
+ benefits: string[];
+}
+
+interface PlanCardProps {
+ detail: FixedPrice | DynamicPrice;
+ subscription?: Subscription | null;
+ recurring: string;
+ onSubscriptionUpdate: SubscriptionMutator;
+}
+
+export function getPlanDetail() {
+ // const t = useAFFiNEI18N();
+
+ // TODO: i18n all things
+ return new Map([
+ [
+ SubscriptionPlan.Free,
+ {
+ type: 'fixed',
+ plan: SubscriptionPlan.Free,
+ price: '0',
+ yearlyPrice: '0',
+ benefits: [
+ 'Unlimited local workspace',
+ 'Unlimited login devices',
+ 'Unlimited blocks',
+ 'AFFiNE Cloud Storage 10GB',
+ 'The maximum file size is 10M',
+ 'Number of members per Workspace ≤ 3',
+ ],
+ },
+ ],
+ [
+ SubscriptionPlan.Pro,
+ {
+ type: 'fixed',
+ plan: SubscriptionPlan.Pro,
+ price: '1',
+ yearlyPrice: '1',
+ benefits: [
+ 'Unlimited local workspace',
+ 'Unlimited login devices',
+ 'Unlimited blocks',
+ 'AFFiNE Cloud Storage 100GB',
+ 'The maximum file size is 500M',
+ 'Number of members per Workspace ≤ 10',
+ ],
+ },
+ ],
+ [
+ SubscriptionPlan.Team,
+ {
+ type: 'dynamic',
+ plan: SubscriptionPlan.Team,
+ contact: true,
+ benefits: [
+ 'Best team workspace for collaboration and knowledge distilling.',
+ 'Focusing on what really matters with team project management and automation.',
+ 'Pay for seats, fits all team size.',
+ ],
+ },
+ ],
+ [
+ SubscriptionPlan.Enterprise,
+ {
+ type: 'dynamic',
+ plan: SubscriptionPlan.Enterprise,
+ contact: true,
+ benefits: [
+ 'Solutions & best practices for dedicated needs.',
+ 'Embedable & interrogations with IT support.',
+ ],
+ },
+ ],
+ ]);
+}
+
+export const PlanCard = ({
+ detail,
+ subscription,
+ recurring,
+ onSubscriptionUpdate,
+}: PlanCardProps) => {
+ const loggedIn = useCurrentLoginStatus() === 'authenticated';
+ const currentPlan = subscription?.plan ?? SubscriptionPlan.Free;
+ const currentRecurring = subscription?.recurring;
+
+ const isCurrent =
+ loggedIn &&
+ detail.plan === currentPlan &&
+ (currentPlan === SubscriptionPlan.Free
+ ? true
+ : currentRecurring === recurring);
+
+ return (
+
+
+
+ {detail.plan}{' '}
+ {'discount' in detail &&
+ recurring === SubscriptionRecurring.Yearly && (
+
+ {detail.discount}% off
+
+ )}
+
+
+
+ {detail.type === 'dynamic' ? (
+ Coming soon...
+ ) : (
+ <>
+
+ $
+ {recurring === SubscriptionRecurring.Monthly
+ ? detail.price
+ : detail.yearlyPrice}
+
+ per month
+ >
+ )}
+
+
+ {
+ // branches:
+ // if contact => 'Contact Sales'
+ // if not signed in:
+ // if free => 'Sign up free'
+ // else => 'Buy Pro'
+ // else
+ // if isCurrent => 'Current Plan'
+ // else if free => 'Downgrade'
+ // else if currentRecurring !== recurring => 'Change to {recurring} Billing'
+ // else => 'Upgrade'
+ // TODO: should replace with components with proper actions
+ detail.type === 'dynamic' ? (
+
+ ) : loggedIn ? (
+ detail.plan === currentPlan &&
+ (currentRecurring === recurring ||
+ (!currentRecurring && detail.plan === SubscriptionPlan.Free)) ? (
+
+ ) : detail.plan === SubscriptionPlan.Free ? (
+
+ ) : currentRecurring !== recurring &&
+ currentPlan === detail.plan ? (
+
+ ) : (
+
+ )
+ ) : (
+
+ {detail.plan === SubscriptionPlan.Free
+ ? 'Sign up free'
+ : 'Buy Pro'}
+
+ )
+ }
+
+
+ {detail.benefits.map((content, i) => (
+
+
+ {detail.type == 'dynamic' ? (
+
+ ) : (
+
+ )}
+
+
{content}
+
+ ))}
+
+
+ );
+};
+
+const CurrentPlan = () => {
+ return ;
+};
+
+const Downgrade = ({
+ onSubscriptionUpdate,
+}: {
+ onSubscriptionUpdate: SubscriptionMutator;
+}) => {
+ const { isMutating, trigger } = useMutation({
+ mutation: cancelSubscriptionMutation,
+ });
+
+ const downgrade = useCallback(() => {
+ trigger(null, {
+ onSuccess: data => {
+ onSubscriptionUpdate(data.cancelSubscription);
+ },
+ });
+ }, [trigger, onSubscriptionUpdate]);
+
+ return (
+
+ );
+};
+
+const ContactSales = () => {
+ return (
+ // TODO: add action
+
+ );
+};
+
+const Upgrade = ({
+ recurring,
+ onSubscriptionUpdate,
+}: {
+ recurring: SubscriptionRecurring;
+ onSubscriptionUpdate: SubscriptionMutator;
+}) => {
+ const { isMutating, trigger } = useMutation({
+ mutation: checkoutMutation,
+ });
+
+ const newTabRef = useRef(null);
+
+ const onClose = useCallback(() => {
+ newTabRef.current = null;
+ onSubscriptionUpdate();
+ }, [onSubscriptionUpdate]);
+
+ const upgrade = useCallback(() => {
+ if (newTabRef.current) {
+ newTabRef.current.focus();
+ } else {
+ trigger(
+ { recurring },
+ {
+ onSuccess: data => {
+ // FIXME: safari prevents from opening new tab by window api
+ // TODO(@xp): what if electron?
+ const newTab = window.open(
+ data.checkout,
+ '_blank',
+ 'noopener noreferrer'
+ );
+
+ if (newTab) {
+ newTabRef.current = newTab;
+
+ newTab.addEventListener('close', onClose);
+ }
+ },
+ }
+ );
+ }
+ }, [trigger, recurring, onClose]);
+
+ useEffect(() => {
+ return () => {
+ if (newTabRef.current) {
+ newTabRef.current.removeEventListener('close', onClose);
+ newTabRef.current = null;
+ }
+ };
+ }, [onClose]);
+
+ return (
+
+ );
+};
+
+const ChangeRecurring = ({
+ from: _from /* TODO: from can be useful when showing confirmation modal */,
+ to,
+ onSubscriptionUpdate,
+}: {
+ from: SubscriptionRecurring;
+ to: SubscriptionRecurring;
+ onSubscriptionUpdate: SubscriptionMutator;
+}) => {
+ const { isMutating, trigger } = useMutation({
+ mutation: updateSubscriptionMutation,
+ });
+
+ const change = useCallback(() => {
+ trigger(
+ { recurring: to },
+ {
+ onSuccess: data => {
+ onSubscriptionUpdate(data.updateSubscriptionRecurring);
+ },
+ }
+ );
+ }, [trigger, onSubscriptionUpdate, to]);
+
+ return (
+
+ );
+};
+
+const SignupAction = ({ children }: PropsWithChildren) => {
+ // TODO: add login action
+ return (
+
+ );
+};
diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/skeleton.css.ts b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/skeleton.css.ts
new file mode 100644
index 0000000000..abec1785a6
--- /dev/null
+++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/skeleton.css.ts
@@ -0,0 +1,29 @@
+import { style } from '@vanilla-extract/css';
+
+export const plansWrapper = style({
+ display: 'flex',
+ gap: '16px',
+});
+
+export const planItemCard = style({
+ width: '258px',
+ height: '426px',
+ flexShrink: '0',
+ borderRadius: '16px',
+ backgroundColor: 'var(--affine-background-primary-color)',
+ border: '1px solid var(--affine-border-color)',
+ padding: '20px',
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '20px',
+});
+
+export const planItemHeader = style({
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '10px',
+});
+export const planItemContent = style({
+ flexGrow: '1',
+ height: 0,
+});
diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/skeleton.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/skeleton.tsx
new file mode 100644
index 0000000000..1ae9a390b7
--- /dev/null
+++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/skeleton.tsx
@@ -0,0 +1,60 @@
+import { Skeleton } from '@mui/material';
+
+import { PlanLayout } from './layout';
+import * as styles from './skeleton.css';
+
+/**
+ * Customize Skeleton component with rounded border radius
+ * @param param0
+ * @returns
+ */
+const RoundedSkeleton = ({
+ radius = 8,
+ ...props
+}: {
+ radius?: number;
+} & React.ComponentProps) => (
+
+);
+
+const SubtitleSkeleton = () => (
+
+);
+
+const TabsSkeleton = () => (
+ // TODO: height should be `32px` by design
+ // but the RadioGroup component is not matching with the design currently
+ // set to `24px` for now to avoid blinking
+
+);
+
+const PlanItemSkeleton = () => (
+
+
+
+
+
+
+
+);
+
+const ScrollSkeleton = () => (
+
+);
+
+export const PlansSkeleton = () => {
+ return (
+ }
+ tabs={}
+ scroll={}
+ />
+ );
+};
diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/style.css.ts b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/style.css.ts
index c3587350d2..4189024ff9 100644
--- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/style.css.ts
+++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/plans/style.css.ts
@@ -14,16 +14,10 @@ export const radioButtonDiscount = style({
});
export const planCardsWrapper = style({
- marginLeft: 'calc(-1 * var(--setting-modal-gap-x))',
- paddingLeft: 'var(--setting-modal-gap-x)',
- paddingRight: 'calc(var(--setting-modal-gap-x) + 300px)',
- width: 'var(--setting-modal-width)',
- marginTop: '24px',
+ paddingRight: 'calc(var(--setting-modal-gap-x))',
display: 'flex',
- overflowX: 'auto',
- scrollSnapType: 'x mandatory',
- // TODO: should display the horizontal scrollbar, ensure the box-shadow is not clipped
- paddingBottom: '21px',
+ gap: '16px',
+ width: 'fit-content',
});
export const planCard = style({
@@ -35,9 +29,6 @@ export const planCard = style({
position: 'relative',
selectors: {
- '&:not(:last-child)': {
- marginRight: '16px',
- },
'&::before': {
content: '',
position: 'absolute',
@@ -101,27 +92,27 @@ export const planAction = style({
export const planBenefits = style({
marginTop: '20px',
fontSize: 'var(--affine-font-xs)',
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '8px',
});
export const planBenefit = style({
display: 'flex',
- selectors: {
- '&:not(:last-child)': {
- marginBottom: '8px',
- },
- },
+ gap: '8px',
+ lineHeight: '20px',
+ alignItems: 'normal',
+ fontSize: '12px',
});
export const planBenefitIcon = style({
- display: 'inline-block',
- marginRight: '8px',
+ display: 'flex',
+ alignItems: 'center',
+ height: '20px',
});
-export const allPlansLink = style({
- display: 'block',
- marginTop: '36px',
- color: 'var(--affine-primary-color)',
- background: 'transparent',
- borderColor: 'transparent',
- fontSize: 'var(--affine-font-xs)',
+export const planBenefitText = style({
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
});
diff --git a/packages/frontend/core/src/components/affine/setting-modal/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/index.tsx
index 349463a2f9..dbac104bd7 100644
--- a/packages/frontend/core/src/components/affine/setting-modal/index.tsx
+++ b/packages/frontend/core/src/components/affine/setting-modal/index.tsx
@@ -54,7 +54,7 @@ export const SettingModal = ({
const paddingX = parseInt(computedStyle.paddingLeft, 10);
modalContentRef.current?.style.setProperty(
'--setting-modal-width',
- `${contentWidth + marginX * 2 + paddingX * 2}px`
+ `${contentWidth + marginX * 2}px`
);
modalContentRef.current?.style.setProperty(
'--setting-modal-gap-x',