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