mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-15 05:37:32 +00:00
refactor(component): refactor the implementation of Button and IconButton (#7716)
## Button
- Remove props withoutHoverStyle
refactor hover impl with independent layer, so that hover-color won't affect the background even if is overridden outside
- Update `type` (renamed to `variant`):
- remove `processing` and `warning`
- rename `default` with `secondary`
- Remove `shape` props
- Remove `icon` and `iconPosition`, replaced with `prefix: ReactNode` and `suffix: ReactNode`
- Integrate tooltip for more convenient usage
- New Storybook document
- Focus style
## IconButton
- A Wrapper base on `<Button />`
- Override Button size and variant
- size: `'12' | '14' | '16' | '20' | '24' | number`
These presets size are referenced from the design system.
- variant: `'plain' | 'solid' | 'danger' | 'custom'`
- Inset icon via Button 's prefix
## Fix
- fix some button related issues
- close AF-1159, AF-1160, AF-1161, AF-1162, AF-1163, AF-1158, AF-1157
## Storybook

This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { Button, IconButton } from '@affine/component/ui/button';
|
import { Button, IconButton } from '@affine/component/ui/button';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { CloseIcon } from '@blocksuite/icons/rc';
|
import { CloseIcon } from '@blocksuite/icons/rc';
|
||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import * as styles from './index.css';
|
import * as styles from './index.css';
|
||||||
@@ -37,11 +38,12 @@ export const LocalDemoTips = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.tipsRightItem}>
|
<div className={styles.tipsRightItem}>
|
||||||
<div>
|
<Button style={{ background: cssVar('white') }} onClick={handleClick}>
|
||||||
<Button onClick={handleClick}>{buttonLabel}</Button>
|
{buttonLabel}
|
||||||
</div>
|
</Button>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
|
size="20"
|
||||||
data-testid="local-demo-tips-close-button"
|
data-testid="local-demo-tips-close-button"
|
||||||
>
|
>
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const MobileNavbar = () => {
|
|||||||
onOpenChange: setOpenMenu,
|
onOpenChange: setOpenMenu,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton type="plain" className={styles.iconButton}>
|
<IconButton variant="plain" size="24" className={styles.iconButton}>
|
||||||
{openMenu ? <CloseIcon /> : <PropertyIcon />}
|
{openMenu ? <CloseIcon /> : <PropertyIcon />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { ArrowLeftSmallIcon } from '@blocksuite/icons/rc';
|
import { ArrowLeftSmallIcon } from '@blocksuite/icons/rc';
|
||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import type { ButtonProps } from '../../ui/button';
|
import type { ButtonProps } from '../../ui/button';
|
||||||
@@ -9,15 +10,15 @@ export const BackButton: FC<ButtonProps> = props => {
|
|||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
type="plain"
|
variant="plain"
|
||||||
style={{
|
style={{
|
||||||
marginTop: 12,
|
marginTop: 12,
|
||||||
marginLeft: -5,
|
marginLeft: -5,
|
||||||
paddingLeft: 0,
|
paddingLeft: 0,
|
||||||
paddingRight: 5,
|
paddingRight: 5,
|
||||||
color: 'var(--affine-text-secondary-color)',
|
color: cssVar('textSecondaryColor'),
|
||||||
}}
|
}}
|
||||||
icon={<ArrowLeftSmallIcon />}
|
prefix={<ArrowLeftSmallIcon />}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{t['com.affine.backButton']()}
|
{t['com.affine.backButton']()}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const ChangeEmailPage = ({
|
|||||||
disabled={hasSetUp}
|
disabled={hasSetUp}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
variant="primary"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={onContinue}
|
onClick={onContinue}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export const ChangePasswordPage: FC<{
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{hasSetUp ? (
|
{hasSetUp ? (
|
||||||
<Button type="primary" size="large" onClick={onOpenAffine}>
|
<Button variant="primary" size="large" onClick={onOpenAffine}>
|
||||||
{t['com.affine.auth.open.affine']()}
|
{t['com.affine.auth.open.affine']()}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const ConfirmChangeEmail: FC<{
|
|||||||
title={t['com.affine.auth.change.email.page.success.title']()}
|
title={t['com.affine.auth.change.email.page.success.title']()}
|
||||||
subtitle={t['com.affine.auth.change.email.page.success.subtitle']()}
|
subtitle={t['com.affine.auth.change.email.page.success.subtitle']()}
|
||||||
>
|
>
|
||||||
<Button type="primary" size="large" onClick={onOpenAffine}>
|
<Button variant="primary" size="large" onClick={onOpenAffine}>
|
||||||
{t['com.affine.auth.open.affine']()}
|
{t['com.affine.auth.open.affine']()}
|
||||||
</Button>
|
</Button>
|
||||||
</AuthPageContainer>
|
</AuthPageContainer>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const ConfirmChangeEmail: FC<{
|
|||||||
title={t['com.affine.auth.change.email.page.success.title']()}
|
title={t['com.affine.auth.change.email.page.success.title']()}
|
||||||
subtitle={t['com.affine.auth.change.email.page.success.subtitle']()}
|
subtitle={t['com.affine.auth.change.email.page.success.subtitle']()}
|
||||||
>
|
>
|
||||||
<Button type="primary" size="large" onClick={onOpenAffine}>
|
<Button variant="primary" size="large" onClick={onOpenAffine}>
|
||||||
{t['com.affine.auth.open.affine']()}
|
{t['com.affine.auth.open.affine']()}
|
||||||
</Button>
|
</Button>
|
||||||
</AuthPageContainer>
|
</AuthPageContainer>
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ export const OnboardingPage = ({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
type="primary"
|
variant="primary"
|
||||||
size="extraLarge"
|
size="extraLarge"
|
||||||
itemType="submit"
|
itemType="submit"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -248,8 +248,7 @@ export const OnboardingPage = ({
|
|||||||
setQuestionIdx(questionIdx + 1);
|
setQuestionIdx(questionIdx + 1);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
iconPosition="end"
|
suffix={<ArrowRightSmallIcon />}
|
||||||
icon={<ArrowRightSmallIcon />}
|
|
||||||
>
|
>
|
||||||
{questionIdx === 0 ? 'start' : 'Next'}
|
{questionIdx === 0 ? 'start' : 'Next'}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -271,7 +270,7 @@ export const OnboardingPage = ({
|
|||||||
</p>
|
</p>
|
||||||
<Button
|
<Button
|
||||||
className={clsx(styles.button, styles.openAFFiNEButton)}
|
className={clsx(styles.button, styles.openAFFiNEButton)}
|
||||||
type="primary"
|
variant="primary"
|
||||||
size="extraLarge"
|
size="extraLarge"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (callbackUrl) {
|
if (callbackUrl) {
|
||||||
@@ -280,8 +279,7 @@ export const OnboardingPage = ({
|
|||||||
onOpenAffine();
|
onOpenAffine();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
iconPosition="end"
|
suffix={<ArrowRightSmallIcon />}
|
||||||
icon={<ArrowRightSmallIcon />}
|
|
||||||
>
|
>
|
||||||
Get Started
|
Get Started
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export const SetPasswordPage: FC<{
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{hasSetUp ? (
|
{hasSetUp ? (
|
||||||
<Button type="primary" size="large" onClick={onOpenAffine}>
|
<Button variant="primary" size="large" onClick={onOpenAffine}>
|
||||||
{t['com.affine.auth.open.affine']()}
|
{t['com.affine.auth.open.affine']()}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const SetPassword: FC<{
|
|||||||
/>
|
/>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
variant="primary"
|
||||||
size="large"
|
size="large"
|
||||||
disabled={!passwordPass}
|
disabled={!passwordPass}
|
||||||
style={{ marginRight: 20 }}
|
style={{ marginRight: 20 }}
|
||||||
@@ -44,7 +44,7 @@ export const SetPassword: FC<{
|
|||||||
{t['com.affine.auth.set.password.save']()}
|
{t['com.affine.auth.set.password.save']()}
|
||||||
</Button>
|
</Button>
|
||||||
{showLater ? (
|
{showLater ? (
|
||||||
<Button type="plain" size="large" onClick={onLater}>
|
<Button variant="plain" size="large" onClick={onLater}>
|
||||||
{t['com.affine.auth.later']()}
|
{t['com.affine.auth.later']()}
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const SignInSuccessPage: FC<{
|
|||||||
title={t['com.affine.auth.signed.success.title']()}
|
title={t['com.affine.auth.signed.success.title']()}
|
||||||
subtitle={t['com.affine.auth.signed.success.subtitle']()}
|
subtitle={t['com.affine.auth.signed.success.subtitle']()}
|
||||||
>
|
>
|
||||||
<Button type="primary" size="large" onClick={onOpenAffine}>
|
<Button variant="primary" size="large" onClick={onOpenAffine}>
|
||||||
{t['com.affine.auth.open.affine']()}
|
{t['com.affine.auth.open.affine']()}
|
||||||
</Button>
|
</Button>
|
||||||
</AuthPageContainer>
|
</AuthPageContainer>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export const SignUpPage: FC<{
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{hasSetUp ? (
|
{hasSetUp ? (
|
||||||
<Button type="primary" size="large" onClick={onOpenAffine}>
|
<Button variant="primary" size="large" onClick={onOpenAffine}>
|
||||||
{openButtonText ?? t['com.affine.auth.open.affine']()}
|
{openButtonText ?? t['com.affine.auth.open.affine']()}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
|
|||||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
import { CollaborationIcon, SettingsIcon } from '@blocksuite/icons/rc';
|
import { CollaborationIcon, SettingsIcon } from '@blocksuite/icons/rc';
|
||||||
import type { WorkspaceMetadata } from '@toeverything/infra';
|
import type { WorkspaceMetadata } from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
|
||||||
import { type MouseEvent, useCallback } from 'react';
|
import { type MouseEvent, useCallback } from 'react';
|
||||||
|
|
||||||
import { Button } from '../../../ui/button';
|
import { Button } from '../../../ui/button';
|
||||||
@@ -89,8 +88,7 @@ export const WorkspaceCard = ({
|
|||||||
<Button
|
<Button
|
||||||
loading={!!openingId && openingId === meta.id}
|
loading={!!openingId && openingId === meta.id}
|
||||||
disabled={!!openingId}
|
disabled={!!openingId}
|
||||||
type="default"
|
className={styles.showOnCardHover}
|
||||||
className={clsx(styles.enableCloudButton, styles.showOnCardHover)}
|
|
||||||
onClick={onEnableCloud}
|
onClick={onEnableCloud}
|
||||||
>
|
>
|
||||||
{enableCloudText}
|
{enableCloudText}
|
||||||
|
|||||||
@@ -79,21 +79,16 @@ export const settingButton = style({
|
|||||||
boxShadow: cssVar('shadow1'),
|
boxShadow: cssVar('shadow1'),
|
||||||
background: cssVar('white80'),
|
background: cssVar('white80'),
|
||||||
},
|
},
|
||||||
// [`.${card}:hover &:hover`]: {
|
|
||||||
// background: cssVar('hoverColor'),
|
|
||||||
// },
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const enableCloudButton = style({
|
|
||||||
background: 'transparent',
|
|
||||||
});
|
|
||||||
|
|
||||||
export const showOnCardHover = style({
|
export const showOnCardHover = style({
|
||||||
display: 'none',
|
visibility: 'hidden',
|
||||||
|
opacity: 0,
|
||||||
selectors: {
|
selectors: {
|
||||||
[`.${card}:hover &`]: {
|
[`.${card}:hover &`]: {
|
||||||
display: 'block',
|
visibility: 'visible',
|
||||||
|
opacity: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const PublicLinkDisableModal = (props: ConfirmModalProps) => {
|
|||||||
cancelText={t['com.affine.publicLinkDisableModal.button.cancel']()}
|
cancelText={t['com.affine.publicLinkDisableModal.button.cancel']()}
|
||||||
confirmText={t['com.affine.publicLinkDisableModal.button.disable']()}
|
confirmText={t['com.affine.publicLinkDisableModal.button.disable']()}
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
type: 'error',
|
variant: 'error',
|
||||||
['data-testid' as string]: 'confirm-enable-affine-cloud-button',
|
['data-testid' as string]: 'confirm-enable-affine-cloud-button',
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ import {
|
|||||||
importPageContainerStyle,
|
importPageContainerStyle,
|
||||||
} from './index.css';
|
} from './index.css';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Not used
|
||||||
|
*/
|
||||||
export const ImportPage = ({
|
export const ImportPage = ({
|
||||||
importMarkdown,
|
importMarkdown,
|
||||||
importHtml,
|
importHtml,
|
||||||
@@ -37,8 +40,9 @@ export const ImportPage = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
icon={<CloseIcon />}
|
>
|
||||||
/>
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
<div className={importPageBodyStyle}>
|
<div className={importPageBodyStyle}>
|
||||||
<div className="title">Import</div>
|
<div className="title">Import</div>
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const AcceptInvitePage = ({
|
|||||||
</FlexWrapper>
|
</FlexWrapper>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button type="primary" size="large" onClick={onOpenWorkspace}>
|
<Button variant="primary" size="large" onClick={onOpenWorkspace}>
|
||||||
{t['Visit Workspace']()}
|
{t['Visit Workspace']()}
|
||||||
</Button>
|
</Button>
|
||||||
</AuthPageContainer>
|
</AuthPageContainer>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export const InviteModal = ({
|
|||||||
confirmText={t['Invite']()}
|
confirmText={t['Invite']()}
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
loading: isMutating,
|
loading: isMutating,
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
['data-testid' as string]: 'confirm-enable-affine-cloud-button',
|
['data-testid' as string]: 'confirm-enable-affine-cloud-button',
|
||||||
}}
|
}}
|
||||||
onConfirm={handleConfirm}
|
onConfirm={handleConfirm}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export const MemberLimitModal = ({
|
|||||||
: 'com.affine.payment.member-limit.pro.confirm'
|
: 'com.affine.payment.member-limit.pro.confirm'
|
||||||
]()}
|
]()}
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
}}
|
}}
|
||||||
onConfirm={handleConfirm}
|
onConfirm={handleConfirm}
|
||||||
></ConfirmModal>
|
></ConfirmModal>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { SignOutIcon } from '@blocksuite/icons/rc';
|
|||||||
|
|
||||||
import { Avatar } from '../../ui/avatar';
|
import { Avatar } from '../../ui/avatar';
|
||||||
import { Button, IconButton } from '../../ui/button';
|
import { Button, IconButton } from '../../ui/button';
|
||||||
import { Tooltip } from '../../ui/tooltip';
|
|
||||||
import { AffineOtherPageLayout } from '../affine-other-page-layout';
|
import { AffineOtherPageLayout } from '../affine-other-page-layout';
|
||||||
import type { User } from '../auth-components';
|
import type { User } from '../auth-components';
|
||||||
import { NotFoundPattern } from './not-found-pattern';
|
import { NotFoundPattern } from './not-found-pattern';
|
||||||
@@ -38,7 +37,7 @@ export const NoPermissionOrNotFound = ({
|
|||||||
<p className={wrapper}>{t['404.hint']()}</p>
|
<p className={wrapper}>{t['404.hint']()}</p>
|
||||||
<div className={wrapper}>
|
<div className={wrapper}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
variant="primary"
|
||||||
size="extraLarge"
|
size="extraLarge"
|
||||||
onClick={onBack}
|
onClick={onBack}
|
||||||
className={largeButtonEffect}
|
className={largeButtonEffect}
|
||||||
@@ -49,11 +48,13 @@ export const NoPermissionOrNotFound = ({
|
|||||||
<div className={wrapper}>
|
<div className={wrapper}>
|
||||||
<Avatar url={user.avatar ?? user.image} name={user.label} />
|
<Avatar url={user.avatar ?? user.image} name={user.label} />
|
||||||
<span style={{ margin: '0 12px' }}>{user.email}</span>
|
<span style={{ margin: '0 12px' }}>{user.email}</span>
|
||||||
<Tooltip content={t['404.signOut']()}>
|
<IconButton
|
||||||
<IconButton onClick={onSignOut}>
|
onClick={onSignOut}
|
||||||
<SignOutIcon />
|
size="20"
|
||||||
</IconButton>
|
tooltip={t['404.signOut']()}
|
||||||
</Tooltip>
|
>
|
||||||
|
<SignOutIcon />
|
||||||
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -80,7 +81,7 @@ export const NotFoundPage = ({
|
|||||||
<p className={wrapper}>{t['404.hint']()}</p>
|
<p className={wrapper}>{t['404.hint']()}</p>
|
||||||
<div className={wrapper}>
|
<div className={wrapper}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
variant="primary"
|
||||||
size="extraLarge"
|
size="extraLarge"
|
||||||
onClick={onBack}
|
onClick={onBack}
|
||||||
className={largeButtonEffect}
|
className={largeButtonEffect}
|
||||||
@@ -93,11 +94,13 @@ export const NotFoundPage = ({
|
|||||||
<div className={wrapper}>
|
<div className={wrapper}>
|
||||||
<Avatar url={user.avatar ?? user.image} name={user.label} />
|
<Avatar url={user.avatar ?? user.image} name={user.label} />
|
||||||
<span style={{ margin: '0 12px' }}>{user.email}</span>
|
<span style={{ margin: '0 12px' }}>{user.email}</span>
|
||||||
<Tooltip content={t['404.signOut']()}>
|
<IconButton
|
||||||
<IconButton onClick={onSignOut}>
|
onClick={onSignOut}
|
||||||
<SignOutIcon />
|
size="20"
|
||||||
</IconButton>
|
tooltip={t['404.signOut']()}
|
||||||
</Tooltip>
|
>
|
||||||
|
<SignOutIcon />
|
||||||
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -199,21 +199,20 @@ export const Avatar = forwardRef<HTMLSpanElement, AvatarProps>(
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{onRemove ? (
|
{onRemove ? (
|
||||||
<Tooltip
|
<IconButton
|
||||||
portalOptions={{ container: removeButtonDom }}
|
tooltipOptions={{
|
||||||
{...removeTooltipOptions}
|
portalOptions: { container: removeButtonDom },
|
||||||
|
...removeTooltipOptions,
|
||||||
|
}}
|
||||||
|
variant="solid"
|
||||||
|
size="12"
|
||||||
|
className={clsx(style.removeButton, removeButtonClassName)}
|
||||||
|
onClick={onRemove}
|
||||||
|
ref={setRemoveButtonDom}
|
||||||
|
{...removeButtonProps}
|
||||||
>
|
>
|
||||||
<IconButton
|
<CloseIcon />
|
||||||
size="extraSmall"
|
</IconButton>
|
||||||
type="default"
|
|
||||||
className={clsx(style.removeButton, removeButtonClassName)}
|
|
||||||
onClick={onRemove}
|
|
||||||
ref={setRemoveButtonDom}
|
|
||||||
{...removeButtonProps}
|
|
||||||
>
|
|
||||||
<CloseIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
) : null}
|
||||||
</AvatarRoot>
|
</AvatarRoot>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
import { createVar, globalStyle, keyframes, style } from '@vanilla-extract/css';
|
import { createVar, keyframes, style } from '@vanilla-extract/css';
|
||||||
export const sizeVar = createVar('sizeVar');
|
export const sizeVar = createVar('sizeVar');
|
||||||
export const blurVar = createVar('blurVar');
|
export const blurVar = createVar('blurVar');
|
||||||
const bottomAnimation = keyframes({
|
const bottomAnimation = keyframes({
|
||||||
@@ -172,7 +172,7 @@ export const hoverWrapper = style({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: 'rgba(60, 61, 63, 0.5)',
|
backgroundColor: 'rgba(60, 61, 63, 0.5)',
|
||||||
zIndex: '1',
|
zIndex: '1',
|
||||||
color: cssVar('white'),
|
color: cssVar('pureWhite'),
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
transition: 'opacity .15s',
|
transition: 'opacity .15s',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
@@ -189,14 +189,8 @@ export const removeButton = style({
|
|||||||
visibility: 'hidden',
|
visibility: 'hidden',
|
||||||
zIndex: '1',
|
zIndex: '1',
|
||||||
selectors: {
|
selectors: {
|
||||||
'&:hover': {
|
[`${avatarRoot}:hover &`]: {
|
||||||
background: '#f6f6f6',
|
visibility: 'visible',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
globalStyle(`${avatarRoot}:hover ${removeButton}`, {
|
|
||||||
visibility: 'visible',
|
|
||||||
});
|
|
||||||
globalStyle(`${avatarRoot} ${removeButton}:hover`, {
|
|
||||||
background: '#f6f6f6',
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,371 +1,259 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
import { globalStyle, style } from '@vanilla-extract/css';
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { createVar, globalStyle, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
// Using variables can override externally, without considering the priority of selectors.
|
||||||
|
// size vars
|
||||||
|
export const hVar = createVar('height');
|
||||||
|
export const wVar = createVar('width');
|
||||||
|
export const iconSizeVar = createVar('iconSize');
|
||||||
|
const gapVar = createVar('gap');
|
||||||
|
const paddingVar = createVar('padding');
|
||||||
|
const fontSizeVar = createVar('fontSize');
|
||||||
|
const fontWeightVar = createVar('fontWeight');
|
||||||
|
const lineHeightVar = createVar('lineHeight');
|
||||||
|
const shadowVar = createVar('shadow');
|
||||||
|
|
||||||
|
// style vars
|
||||||
|
const bgVar = createVar('bg');
|
||||||
|
const textVar = createVar('fg');
|
||||||
|
const iconColorVar = createVar('icon');
|
||||||
|
const borderColorVar = createVar('border');
|
||||||
|
const borderWidthVar = createVar('borderWidth');
|
||||||
|
|
||||||
export const button = style({
|
export const button = style({
|
||||||
display: 'inline-flex',
|
vars: {
|
||||||
justifyContent: 'center',
|
// default vars
|
||||||
alignItems: 'center',
|
[gapVar]: '4px',
|
||||||
userSelect: 'none',
|
[wVar]: 'unset',
|
||||||
touchAction: 'manipulation',
|
[hVar]: 'unset',
|
||||||
|
[borderWidthVar]: '1px',
|
||||||
|
},
|
||||||
|
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
outline: '0',
|
position: 'relative',
|
||||||
border: '1px solid',
|
display: 'inline-flex',
|
||||||
padding: '0 8px',
|
alignItems: 'center',
|
||||||
borderRadius: '8px',
|
justifyContent: 'center',
|
||||||
fontSize: cssVar('fontXs'),
|
userSelect: 'none',
|
||||||
fontWeight: 500,
|
outline: 0,
|
||||||
|
borderRadius: 8,
|
||||||
transition: 'all .3s',
|
transition: 'all .3s',
|
||||||
['WebkitAppRegion' as string]: 'no-drag',
|
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
// changeable
|
['WebkitAppRegion' as string]: 'no-drag',
|
||||||
height: '28px',
|
|
||||||
background: cssVar('white'),
|
// hover layer
|
||||||
borderColor: cssVar('borderColor'),
|
':before': {
|
||||||
color: cssVar('textPrimaryColor'),
|
content: '""',
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
transition: 'inherit',
|
||||||
|
borderRadius: 'inherit',
|
||||||
|
opacity: 0,
|
||||||
|
left: '50%',
|
||||||
|
top: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
backgroundColor: cssVarV2('layer/background/hoverOverlay'),
|
||||||
|
borderColor: 'transparent',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
borderWidth: 'inherit',
|
||||||
|
borderStyle: 'inherit',
|
||||||
|
},
|
||||||
|
|
||||||
|
// style
|
||||||
|
backgroundColor: bgVar,
|
||||||
|
color: textVar,
|
||||||
|
boxShadow: shadowVar,
|
||||||
|
borderWidth: borderWidthVar,
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderColor: borderColorVar,
|
||||||
|
|
||||||
|
// size
|
||||||
|
width: wVar,
|
||||||
|
height: hVar,
|
||||||
|
gap: gapVar,
|
||||||
|
padding: paddingVar,
|
||||||
|
fontSize: fontSizeVar,
|
||||||
|
fontWeight: fontWeightVar,
|
||||||
|
lineHeight: lineHeightVar,
|
||||||
|
|
||||||
selectors: {
|
selectors: {
|
||||||
'&.text-bold': {
|
'&:hover:before': { opacity: 1 },
|
||||||
fontWeight: 600,
|
'&[data-block]': { display: 'flex' },
|
||||||
},
|
|
||||||
'&:not(.without-hover):hover': {
|
// size
|
||||||
background: cssVar('hoverColor'),
|
'&[data-size="default"]': {
|
||||||
},
|
vars: {
|
||||||
'&.disabled': {
|
[hVar]: '28px', // line-height + paddingY * 2 (to ignore border width)
|
||||||
opacity: '.4',
|
[paddingVar]: '0px 8px',
|
||||||
cursor: 'default',
|
[iconSizeVar]: '16px',
|
||||||
color: cssVar('textDisableColor'),
|
[paddingVar]: '4px 12px',
|
||||||
pointerEvents: 'none',
|
[fontSizeVar]: cssVar('fontXs'),
|
||||||
},
|
[fontWeightVar]: '500',
|
||||||
'&.loading': {
|
[lineHeightVar]: '20px',
|
||||||
cursor: 'default',
|
|
||||||
color: cssVar('textDisableColor'),
|
|
||||||
pointerEvents: 'none',
|
|
||||||
},
|
|
||||||
'&.disabled:not(.without-hover):hover, &.loading:not(.without-hover):hover':
|
|
||||||
{
|
|
||||||
background: 'inherit',
|
|
||||||
},
|
},
|
||||||
'&.block': {
|
},
|
||||||
display: 'flex',
|
'&[data-size="large"]': {
|
||||||
|
vars: {
|
||||||
|
[hVar]: '32px',
|
||||||
|
[paddingVar]: '0px 8px',
|
||||||
|
[iconSizeVar]: '20px',
|
||||||
|
[paddingVar]: '4px 12px',
|
||||||
|
[fontSizeVar]: '15px',
|
||||||
|
[fontWeightVar]: '500',
|
||||||
|
[lineHeightVar]: '24px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'&[data-size="extraLarge"]': {
|
||||||
|
vars: {
|
||||||
|
[hVar]: '40px',
|
||||||
|
[paddingVar]: '0px 8px',
|
||||||
|
[iconSizeVar]: '24px',
|
||||||
|
[paddingVar]: '8px 18px',
|
||||||
|
[fontSizeVar]: '15',
|
||||||
|
[fontWeightVar]: '600',
|
||||||
|
[lineHeightVar]: '24px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// type
|
||||||
|
'&[data-variant="primary"]': {
|
||||||
|
vars: {
|
||||||
|
[bgVar]: cssVarV2('button/primary'),
|
||||||
|
[textVar]: cssVarV2('button/pureWhiteText'),
|
||||||
|
[iconColorVar]: cssVarV2('button/pureWhiteText'),
|
||||||
|
[borderColorVar]: cssVarV2('button/innerBlackBorder'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'&[data-variant="secondary"]': {
|
||||||
|
vars: {
|
||||||
|
[bgVar]: cssVarV2('button/secondary'),
|
||||||
|
[textVar]: cssVarV2('text/primary'),
|
||||||
|
[iconColorVar]: cssVarV2('icon/primary'),
|
||||||
|
[borderColorVar]: cssVarV2('layer/border'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'&[data-variant="plain"]': {
|
||||||
|
vars: {
|
||||||
|
[bgVar]: 'transparent',
|
||||||
|
[textVar]: cssVarV2('text/primary'),
|
||||||
|
[iconColorVar]: cssVarV2('icon/primary'),
|
||||||
|
[borderColorVar]: 'transparent',
|
||||||
|
[borderWidthVar]: '0px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'&[data-variant="error"]': {
|
||||||
|
vars: {
|
||||||
|
[bgVar]: cssVarV2('button/error'),
|
||||||
|
[textVar]: cssVarV2('button/pureWhiteText'),
|
||||||
|
[iconColorVar]: cssVarV2('button/pureWhiteText'),
|
||||||
|
[borderColorVar]: cssVarV2('button/innerBlackBorder'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'&[data-variant="success"]': {
|
||||||
|
vars: {
|
||||||
|
[bgVar]: cssVarV2('button/success'),
|
||||||
|
[textVar]: cssVarV2('button/pureWhiteText'),
|
||||||
|
[iconColorVar]: cssVarV2('button/pureWhiteText'),
|
||||||
|
[borderColorVar]: cssVarV2('button/innerBlackBorder'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// disabled
|
||||||
|
'&[data-disabled]': {
|
||||||
|
cursor: 'not-allowed',
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
|
||||||
|
// default keyboard focus style
|
||||||
|
'&:focus-visible::after': {
|
||||||
|
content: '""',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
height: '100%',
|
||||||
'&.circle': {
|
position: 'absolute',
|
||||||
borderRadius: '50%',
|
top: 0,
|
||||||
},
|
left: 0,
|
||||||
'&.round': {
|
borderRadius: 'inherit',
|
||||||
borderRadius: '14px',
|
boxShadow: `0 0 0 1px ${cssVarV2('layer/insideBorder/primary')}`,
|
||||||
},
|
|
||||||
// size
|
|
||||||
'&.large': {
|
|
||||||
height: '32px',
|
|
||||||
fontSize: cssVar('fontBase'),
|
|
||||||
fontWeight: 600,
|
|
||||||
},
|
|
||||||
'&.round.large': {
|
|
||||||
borderRadius: '16px',
|
|
||||||
},
|
|
||||||
'&.extraLarge': {
|
|
||||||
height: '40px',
|
|
||||||
fontSize: cssVar('fontBase'),
|
|
||||||
fontWeight: 700,
|
|
||||||
},
|
|
||||||
'&.extraLarge.primary': {
|
|
||||||
boxShadow: `${cssVar('largeButtonEffect')} !important`,
|
|
||||||
},
|
|
||||||
'&.round.extraLarge': {
|
|
||||||
borderRadius: '20px',
|
|
||||||
},
|
|
||||||
// type
|
|
||||||
'&.plain': {
|
|
||||||
color: cssVar('textPrimaryColor'),
|
|
||||||
borderColor: 'transparent',
|
|
||||||
background: 'transparent',
|
|
||||||
},
|
|
||||||
'&.primary': {
|
|
||||||
color: cssVar('pureWhite'),
|
|
||||||
background: cssVar('primaryColor'),
|
|
||||||
borderColor: cssVar('black10'),
|
|
||||||
},
|
|
||||||
'&.primary:not(.without-hover):hover': {
|
|
||||||
background: `linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), ${cssVar(
|
|
||||||
'primaryColor'
|
|
||||||
)}`,
|
|
||||||
},
|
|
||||||
'&.primary.disabled': {
|
|
||||||
opacity: '.4',
|
|
||||||
cursor: 'default',
|
|
||||||
},
|
|
||||||
'&.primary.disabled:not(.without-hover):hover': {
|
|
||||||
background: cssVar('primaryColor'),
|
|
||||||
},
|
|
||||||
'&.error': {
|
|
||||||
color: cssVar('pureWhite'),
|
|
||||||
background: cssVar('errorColor'),
|
|
||||||
borderColor: cssVar('black10'),
|
|
||||||
},
|
|
||||||
'&.error:not(.without-hover):hover': {
|
|
||||||
background: `linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), ${cssVar(
|
|
||||||
'errorColor'
|
|
||||||
)}`,
|
|
||||||
},
|
|
||||||
'&.error.disabled': {
|
|
||||||
opacity: '.4',
|
|
||||||
cursor: 'default',
|
|
||||||
},
|
|
||||||
'&.error.disabled:not(.without-hover):hover': {
|
|
||||||
background: cssVar('errorColor'),
|
|
||||||
},
|
|
||||||
'&.warning': {
|
|
||||||
color: cssVar('pureWhite'),
|
|
||||||
background: cssVar('warningColor'),
|
|
||||||
borderColor: cssVar('black10'),
|
|
||||||
},
|
|
||||||
'&.warning:not(.without-hover):hover': {
|
|
||||||
background: `linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), ${cssVar(
|
|
||||||
'warningColor'
|
|
||||||
)}`,
|
|
||||||
},
|
|
||||||
'&.warning.disabled': {
|
|
||||||
opacity: '.4',
|
|
||||||
cursor: 'default',
|
|
||||||
},
|
|
||||||
'&.warning.disabled:not(.without-hover):hover': {
|
|
||||||
background: cssVar('warningColor'),
|
|
||||||
},
|
|
||||||
'&.success': {
|
|
||||||
color: cssVar('pureWhite'),
|
|
||||||
background: cssVar('successColor'),
|
|
||||||
borderColor: cssVar('black10'),
|
|
||||||
},
|
|
||||||
'&.success:not(.without-hover):hover': {
|
|
||||||
background: `linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), ${cssVar(
|
|
||||||
'successColor'
|
|
||||||
)}`,
|
|
||||||
},
|
|
||||||
'&.success.disabled': {
|
|
||||||
opacity: '.4',
|
|
||||||
cursor: 'default',
|
|
||||||
},
|
|
||||||
'&.success.disabled:not(.without-hover):hover': {
|
|
||||||
background: cssVar('successColor'),
|
|
||||||
},
|
|
||||||
'&.processing': {
|
|
||||||
color: cssVar('pureWhite'),
|
|
||||||
background: cssVar('processingColor'),
|
|
||||||
borderColor: cssVar('black10'),
|
|
||||||
},
|
|
||||||
'&.processing:not(.without-hover):hover': {
|
|
||||||
background: `linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), ${cssVar(
|
|
||||||
'processingColor'
|
|
||||||
)}`,
|
|
||||||
},
|
|
||||||
'&.processing.disabled': {
|
|
||||||
opacity: '.4',
|
|
||||||
cursor: 'default',
|
|
||||||
},
|
|
||||||
'&.processing.disabled:not(.without-hover):hover': {
|
|
||||||
background: cssVar('processingColor'),
|
|
||||||
},
|
|
||||||
'&.danger:hover': {
|
|
||||||
color: cssVar('errorColor'),
|
|
||||||
background: cssVar('backgroundErrorColor'),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
globalStyle(`${button} > span`, {
|
export const content = style({
|
||||||
// flex: 1,
|
// in case that width is specified by parent and text is too long
|
||||||
lineHeight: 1,
|
textOverflow: 'ellipsis',
|
||||||
padding: '0 4px',
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
});
|
});
|
||||||
export const buttonIcon = style({
|
|
||||||
|
export const icon = style({
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
display: 'inline-flex',
|
// There are two kinds of icon size:
|
||||||
justifyContent: 'center',
|
// 1. control by props: width and height
|
||||||
alignItems: 'center',
|
width: iconSizeVar,
|
||||||
color: cssVar('iconColor'),
|
height: iconSizeVar,
|
||||||
fontSize: '16px',
|
// 2. width/height is set to `1em`
|
||||||
width: '16px',
|
fontSize: iconSizeVar,
|
||||||
height: '16px',
|
color: iconColorVar,
|
||||||
selectors: {
|
|
||||||
'&.start': {
|
|
||||||
marginRight: '4px',
|
|
||||||
},
|
|
||||||
'&.end': {
|
|
||||||
marginLeft: '4px',
|
|
||||||
},
|
|
||||||
'&.large': {
|
|
||||||
fontSize: '20px',
|
|
||||||
width: '20px',
|
|
||||||
height: '20px',
|
|
||||||
},
|
|
||||||
'&.extraLarge': {
|
|
||||||
fontSize: '20px',
|
|
||||||
width: '20px',
|
|
||||||
height: '20px',
|
|
||||||
},
|
|
||||||
'&.color-white': {
|
|
||||||
color: cssVar('pureWhite'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
globalStyle(`${icon} > svg`, {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'block',
|
||||||
|
});
|
||||||
|
|
||||||
export const iconButton = style({
|
export const iconButton = style({
|
||||||
display: 'inline-flex',
|
vars: {
|
||||||
justifyContent: 'center',
|
[paddingVar]: '2px',
|
||||||
alignItems: 'center',
|
// TODO(@CatsJuice): Replace with theme variables when ready
|
||||||
userSelect: 'none',
|
'--shadow':
|
||||||
touchAction: 'manipulation',
|
'0px 0px 1px 0px rgba(0, 0, 0, 0.12), 0px 1px 5px 0px rgba(0, 0, 0, 0.12)',
|
||||||
outline: '0',
|
},
|
||||||
border: '1px solid',
|
borderRadius: 4,
|
||||||
borderRadius: '4px',
|
|
||||||
transition: 'all .3s',
|
|
||||||
['WebkitAppRegion' as string]: 'no-drag',
|
|
||||||
cursor: 'pointer',
|
|
||||||
background: cssVar('white'),
|
|
||||||
// changeable
|
|
||||||
width: '24px',
|
|
||||||
height: '24px',
|
|
||||||
fontSize: '20px',
|
|
||||||
color: cssVar('textPrimaryColor'),
|
|
||||||
borderColor: cssVar('borderColor'),
|
|
||||||
selectors: {
|
selectors: {
|
||||||
'&.without-padding': {
|
'[data-theme="dark"] &': {
|
||||||
margin: '-2px',
|
vars: {
|
||||||
},
|
'--shadow':
|
||||||
'&.active': {
|
'0px 0px 1px 0px rgba(0, 0, 0, 0.66), 0px 1px 5px 0px rgba(0, 0, 0, 0.72)',
|
||||||
color: cssVar('primaryColor'),
|
|
||||||
},
|
|
||||||
'&:not(.without-hover):hover': {
|
|
||||||
background: cssVar('hoverColor'),
|
|
||||||
},
|
|
||||||
'&.disabled': {
|
|
||||||
opacity: '.4',
|
|
||||||
cursor: 'default',
|
|
||||||
color: cssVar('textDisableColor'),
|
|
||||||
pointerEvents: 'none',
|
|
||||||
},
|
|
||||||
'&.loading': {
|
|
||||||
cursor: 'default',
|
|
||||||
color: cssVar('textDisableColor'),
|
|
||||||
pointerEvents: 'none',
|
|
||||||
},
|
|
||||||
'&.disabled:not(.without-hover):hover, &.loading:not(.without-hover):hover':
|
|
||||||
{
|
|
||||||
background: 'inherit',
|
|
||||||
},
|
},
|
||||||
// size
|
|
||||||
'&.large': {
|
|
||||||
width: '32px',
|
|
||||||
height: '32px',
|
|
||||||
fontSize: '24px',
|
|
||||||
},
|
},
|
||||||
'&.large.without-padding': {
|
'&[data-icon-variant="plain"]': {
|
||||||
margin: '-4px',
|
vars: {
|
||||||
|
[bgVar]: 'transparent',
|
||||||
|
[iconColorVar]: cssVarV2('icon/primary'),
|
||||||
|
[borderColorVar]: 'transparent',
|
||||||
|
[borderWidthVar]: '0px',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'&.small': {
|
'&[data-icon-variant="danger"]': {
|
||||||
width: '20px',
|
vars: {
|
||||||
height: '20px',
|
[bgVar]: 'transparent',
|
||||||
fontSize: '16px',
|
[iconColorVar]: cssVarV2('icon/primary'),
|
||||||
|
[borderColorVar]: 'transparent',
|
||||||
|
[borderWidthVar]: '0px',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'&.extra-small': {
|
'&[data-icon-variant="danger"]:hover': {
|
||||||
width: '16px',
|
vars: {
|
||||||
height: '16px',
|
[bgVar]: cssVar('backgroundErrorColor'),
|
||||||
fontSize: '12px',
|
[iconColorVar]: cssVar('errorColor'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
// type
|
// disable hover layer for danger type
|
||||||
'&.plain': {
|
'&[data-icon-variant="danger"]:hover:before': {
|
||||||
color: cssVar('iconColor'),
|
opacity: 0,
|
||||||
borderColor: 'transparent',
|
|
||||||
background: 'transparent',
|
|
||||||
},
|
},
|
||||||
'&.plain.active': {
|
'&[data-icon-variant="solid"]': {
|
||||||
color: cssVar('primaryColor'),
|
vars: {
|
||||||
|
[bgVar]: cssVarV2('button/iconButtonSolid'),
|
||||||
|
[iconColorVar]: cssVarV2('icon/primary'),
|
||||||
|
[borderColorVar]: 'transparent',
|
||||||
|
[shadowVar]: 'var(--shadow)',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'&.primary': {
|
|
||||||
color: cssVar('white'),
|
'&[data-icon-size="24"]': {
|
||||||
background: cssVar('primaryColor'),
|
vars: { [paddingVar]: '4px' },
|
||||||
borderColor: cssVar('black10'),
|
|
||||||
},
|
|
||||||
'&.primary:not(.without-hover):hover': {
|
|
||||||
background: `linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), ${cssVar(
|
|
||||||
'primaryColor'
|
|
||||||
)}`,
|
|
||||||
},
|
|
||||||
'&.primary.disabled': {
|
|
||||||
opacity: '.4',
|
|
||||||
cursor: 'default',
|
|
||||||
},
|
|
||||||
'&.primary.disabled:not(.without-hover):hover': {
|
|
||||||
background: cssVar('primaryColor'),
|
|
||||||
},
|
|
||||||
'&.error': {
|
|
||||||
color: cssVar('white'),
|
|
||||||
background: cssVar('errorColor'),
|
|
||||||
borderColor: cssVar('black10'),
|
|
||||||
},
|
|
||||||
'&.error:not(.without-hover):hover': {
|
|
||||||
background: `linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), ${cssVar(
|
|
||||||
'errorColor'
|
|
||||||
)}`,
|
|
||||||
},
|
|
||||||
'&.error.disabled': {
|
|
||||||
opacity: '.4',
|
|
||||||
cursor: 'default',
|
|
||||||
},
|
|
||||||
'&.error.disabled:not(.without-hover):hover': {
|
|
||||||
background: cssVar('errorColor'),
|
|
||||||
},
|
|
||||||
'&.warning': {
|
|
||||||
color: cssVar('white'),
|
|
||||||
background: cssVar('warningColor'),
|
|
||||||
borderColor: cssVar('black10'),
|
|
||||||
},
|
|
||||||
'&.warning:not(.without-hover):hover': {
|
|
||||||
background: `linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), ${cssVar(
|
|
||||||
'warningColor'
|
|
||||||
)}`,
|
|
||||||
},
|
|
||||||
'&.warning.disabled': {
|
|
||||||
opacity: '.4',
|
|
||||||
cursor: 'default',
|
|
||||||
},
|
|
||||||
'&.warning.disabled:not(.without-hover):hover': {
|
|
||||||
background: cssVar('warningColor'),
|
|
||||||
},
|
|
||||||
'&.success': {
|
|
||||||
color: cssVar('white'),
|
|
||||||
background: cssVar('successColor'),
|
|
||||||
borderColor: cssVar('black10'),
|
|
||||||
},
|
|
||||||
'&.success:not(.without-hover):hover': {
|
|
||||||
background: `linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), ${cssVar(
|
|
||||||
'successColor'
|
|
||||||
)}`,
|
|
||||||
},
|
|
||||||
'&.success.disabled': {
|
|
||||||
opacity: '.4',
|
|
||||||
cursor: 'default',
|
|
||||||
},
|
|
||||||
'&.success.disabled:not(.without-hover):hover': {
|
|
||||||
background: cssVar('successColor'),
|
|
||||||
},
|
|
||||||
'&.processing': {
|
|
||||||
color: cssVar('white'),
|
|
||||||
background: cssVar('processingColor'),
|
|
||||||
borderColor: cssVar('black10'),
|
|
||||||
},
|
|
||||||
'&.processing:not(.without-hover):hover': {
|
|
||||||
background: `linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), ${cssVar(
|
|
||||||
'processingColor'
|
|
||||||
)}`,
|
|
||||||
},
|
|
||||||
'&.processing.disabled': {
|
|
||||||
opacity: '.4',
|
|
||||||
cursor: 'default',
|
|
||||||
},
|
|
||||||
'&.processing.disabled:not(.without-hover):hover': {
|
|
||||||
background: cssVar('processingColor'),
|
|
||||||
},
|
|
||||||
'&.danger:hover': {
|
|
||||||
color: cssVar('errorColor'),
|
|
||||||
background: cssVar('backgroundErrorColor'),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { globalStyle, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
// table
|
||||||
|
export const table = style({
|
||||||
|
vars: { '--border-color': '#974FFF' },
|
||||||
|
});
|
||||||
|
globalStyle(`${table} thead td, ${table} tbody tr td:nth-child(1)`, {
|
||||||
|
backgroundColor: '#974FFF10',
|
||||||
|
padding: '16px',
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 12,
|
||||||
|
color: 'var(--border-color)',
|
||||||
|
});
|
||||||
|
globalStyle(`${table} td`, {
|
||||||
|
textAlign: 'center',
|
||||||
|
border: '0.5px dashed var(--border-color)',
|
||||||
|
borderTopColor: 'transparent',
|
||||||
|
borderBottomColor: 'transparent',
|
||||||
|
padding: '16px 8px',
|
||||||
|
});
|
||||||
|
globalStyle(`${table} thead td`, {
|
||||||
|
borderTopColor: 'var(--border-color)',
|
||||||
|
});
|
||||||
|
globalStyle(`${table} tbody tr:last-child td`, {
|
||||||
|
borderBottomColor: 'var(--border-color)',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const settings = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: '8px 100px',
|
||||||
|
marginBottom: 40,
|
||||||
|
});
|
||||||
|
globalStyle(`${settings} > section`, {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
});
|
||||||
|
globalStyle(`${settings} > section > span`, {
|
||||||
|
display: 'inline-block',
|
||||||
|
width: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const overrideBackground = style({
|
||||||
|
background: 'cyan',
|
||||||
|
});
|
||||||
|
export const overrideTextColor = style({
|
||||||
|
color: 'red',
|
||||||
|
});
|
||||||
|
export const overrideBorder = style({
|
||||||
|
borderColor: 'green',
|
||||||
|
});
|
||||||
|
export const overrideFontSize = style({
|
||||||
|
fontSize: 24,
|
||||||
|
});
|
||||||
|
export const overrideIconSize = style({
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
});
|
||||||
|
export const overrideIconColor = style({
|
||||||
|
color: 'forestgreen',
|
||||||
|
});
|
||||||
@@ -1,47 +1,183 @@
|
|||||||
import { InformationIcon } from '@blocksuite/icons/rc';
|
import {
|
||||||
import type { Meta, StoryFn } from '@storybook/react';
|
AfFiNeIcon,
|
||||||
|
ArrowRightBigIcon,
|
||||||
|
FolderIcon,
|
||||||
|
} from '@blocksuite/icons/rc';
|
||||||
|
import type { Meta } from '@storybook/react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { Switch } from '../switch';
|
||||||
import type { ButtonProps } from './button';
|
import type { ButtonProps } from './button';
|
||||||
import { Button } from './button';
|
import { Button } from './button';
|
||||||
|
import * as styles from './button.stories.css';
|
||||||
export default {
|
export default {
|
||||||
title: 'UI/Button',
|
title: 'UI/Button',
|
||||||
component: Button,
|
component: Button,
|
||||||
argTypes: {
|
|
||||||
onClick: () => console.log('Click button'),
|
|
||||||
},
|
|
||||||
} satisfies Meta<ButtonProps>;
|
} satisfies Meta<ButtonProps>;
|
||||||
|
|
||||||
const Template: StoryFn<ButtonProps> = args => <Button {...args} />;
|
// const Template: StoryFn<ButtonProps> = args => <Button {...args} />;
|
||||||
|
|
||||||
export const Default: StoryFn<ButtonProps> = Template.bind(undefined);
|
const types: ButtonProps['variant'][] = [
|
||||||
Default.args = {
|
'primary',
|
||||||
type: 'default',
|
'secondary',
|
||||||
children: 'This is a default button',
|
'plain',
|
||||||
icon: <InformationIcon />,
|
'error',
|
||||||
|
'success',
|
||||||
|
];
|
||||||
|
const sizes: ButtonProps['size'][] = ['default', 'large', 'extraLarge'];
|
||||||
|
|
||||||
|
const Groups = ({
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: Omit<ButtonProps, 'variant' | 'size'>) => {
|
||||||
|
return (
|
||||||
|
<table className={styles.table}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Type/Size</td>
|
||||||
|
{sizes.map(size => (
|
||||||
|
<td key={size}>{size}</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{types.map(type => (
|
||||||
|
<tr key={type}>
|
||||||
|
<td>{type}</td>
|
||||||
|
{sizes.map(size => (
|
||||||
|
<td key={size}>
|
||||||
|
<Button variant={type} size={size} {...props}>
|
||||||
|
{children ?? `${size} - ${type}`}
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Primary: StoryFn<ButtonProps> = Template.bind(undefined);
|
export const Default = () => <Groups />;
|
||||||
Primary.args = {
|
|
||||||
type: 'primary',
|
export const WithIcon = () => {
|
||||||
children: 'Content',
|
return <Groups prefix={<FolderIcon />} suffix={<span>🚀</span>} />;
|
||||||
icon: <InformationIcon />,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Disabled: StoryFn<ButtonProps> = Template.bind(undefined);
|
export const Loading = () => {
|
||||||
Disabled.args = {
|
const [loading, setLoading] = useState(false);
|
||||||
disabled: true,
|
|
||||||
children: 'This is a disabled button',
|
const toggleLoading = useCallback(() => setLoading(v => !v), []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInterval(toggleLoading, 1000);
|
||||||
|
}, [toggleLoading]);
|
||||||
|
|
||||||
|
return <Groups loading={loading} prefix={<FolderIcon />} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LargeSizeButton: StoryFn<ButtonProps> = Template.bind(undefined);
|
export const OverrideViaClassName = () => {
|
||||||
LargeSizeButton.args = {
|
const [overrideBg, setOverrideBg] = useState(false);
|
||||||
size: 'large',
|
const [overrideTextColor, setOverrideTextColor] = useState(false);
|
||||||
children: 'This is a large button',
|
const [overrideBorder, setOverrideBorder] = useState(false);
|
||||||
|
const [overrideFontSize, setOverrideFontSize] = useState(false);
|
||||||
|
const [overridePrefixSize, setOverridePrefixSize] = useState(false);
|
||||||
|
const [overrideSuffixSize, setOverrideSuffixSize] = useState(false);
|
||||||
|
const [overridePrefixColor, setOverridePrefixColor] = useState(false);
|
||||||
|
const [overrideSuffixColor, setOverrideSuffixColor] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={styles.settings}>
|
||||||
|
<section>
|
||||||
|
<span>Override background color</span>
|
||||||
|
<Switch checked={overrideBg} onChange={setOverrideBg} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<span>Override text color</span>
|
||||||
|
<Switch checked={overrideTextColor} onChange={setOverrideTextColor} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<span>Override border color</span>
|
||||||
|
<Switch checked={overrideBorder} onChange={setOverrideBorder} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<span>Override font size</span>
|
||||||
|
<Switch checked={overrideFontSize} onChange={setOverrideFontSize} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<span>Override prefix size</span>
|
||||||
|
<Switch
|
||||||
|
checked={overridePrefixSize}
|
||||||
|
onChange={setOverridePrefixSize}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<span>Override suffix size</span>
|
||||||
|
<Switch
|
||||||
|
checked={overrideSuffixSize}
|
||||||
|
onChange={setOverrideSuffixSize}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<span>Override prefix color</span>
|
||||||
|
<Switch
|
||||||
|
checked={overridePrefixColor}
|
||||||
|
onChange={setOverridePrefixColor}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<span>Override suffix color</span>
|
||||||
|
<Switch
|
||||||
|
checked={overrideSuffixColor}
|
||||||
|
onChange={setOverrideSuffixColor}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Groups
|
||||||
|
prefix={<FolderIcon />}
|
||||||
|
suffix={<ArrowRightBigIcon />}
|
||||||
|
className={clsx({
|
||||||
|
[styles.overrideBackground]: overrideBg,
|
||||||
|
[styles.overrideTextColor]: overrideTextColor,
|
||||||
|
[styles.overrideBorder]: overrideBorder,
|
||||||
|
[styles.overrideFontSize]: overrideFontSize,
|
||||||
|
})}
|
||||||
|
prefixClassName={clsx({
|
||||||
|
[styles.overrideIconSize]: overridePrefixSize,
|
||||||
|
[styles.overrideIconColor]: overridePrefixColor,
|
||||||
|
})}
|
||||||
|
suffixClassName={clsx({
|
||||||
|
[styles.overrideIconSize]: overrideSuffixSize,
|
||||||
|
[styles.overrideIconColor]: overrideSuffixColor,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExtraLargeSizeButton: StoryFn<ButtonProps> =
|
export const FixedWidth = () => {
|
||||||
Template.bind(undefined);
|
const widths = [60, 100, 120, 160, 180];
|
||||||
ExtraLargeSizeButton.args = {
|
return (
|
||||||
size: 'extraLarge',
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||||
children: 'This is a extra large button',
|
{widths.map(width => (
|
||||||
|
<Button prefix={<AfFiNeIcon />} key={width} style={{ width }}>
|
||||||
|
This is a width fixed button
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Disabled = () => {
|
||||||
|
return <Groups disabled />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,172 +1,168 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type {
|
import type {
|
||||||
FC,
|
CSSProperties,
|
||||||
HTMLAttributes,
|
HTMLAttributes,
|
||||||
PropsWithChildren,
|
MouseEvent,
|
||||||
ReactElement,
|
ReactElement,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { forwardRef, useMemo } from 'react';
|
import { cloneElement, forwardRef, useCallback } from 'react';
|
||||||
|
|
||||||
import { Loading } from '../loading';
|
import { Loading } from '../loading';
|
||||||
import { button, buttonIcon } from './button.css';
|
import { Tooltip, type TooltipProps } from '../tooltip';
|
||||||
|
import * as styles from './button.css';
|
||||||
|
|
||||||
export type ButtonType =
|
export type ButtonType =
|
||||||
| 'default'
|
|
||||||
| 'primary'
|
| 'primary'
|
||||||
|
| 'secondary'
|
||||||
| 'plain'
|
| 'plain'
|
||||||
| 'error'
|
| 'error'
|
||||||
| 'warning'
|
|
||||||
| 'success'
|
| 'success'
|
||||||
| 'processing';
|
| 'custom';
|
||||||
export type ButtonSize = 'default' | 'large' | 'extraLarge';
|
export type ButtonSize = 'default' | 'large' | 'extraLarge' | 'custom';
|
||||||
type BaseButtonProps = {
|
|
||||||
type?: ButtonType;
|
export interface ButtonProps
|
||||||
|
extends Omit<HTMLAttributes<HTMLButtonElement>, 'type' | 'prefix'> {
|
||||||
|
/**
|
||||||
|
* Preset color scheme
|
||||||
|
* @default 'secondary'
|
||||||
|
*/
|
||||||
|
variant?: ButtonType;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
icon?: ReactElement;
|
/**
|
||||||
iconPosition?: 'start' | 'end';
|
* By default, the button is `inline-flex`, set to `true` to make it `flex`
|
||||||
shape?: 'default' | 'round' | 'circle';
|
* @default false
|
||||||
|
*/
|
||||||
block?: boolean;
|
block?: boolean;
|
||||||
|
/**
|
||||||
|
* Preset size, will be overridden by `style` or `className`
|
||||||
|
* @default 'default'
|
||||||
|
*/
|
||||||
size?: ButtonSize;
|
size?: ButtonSize;
|
||||||
|
/**
|
||||||
|
* Will show a loading spinner at `prefix` position
|
||||||
|
*/
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
withoutHoverStyle?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ButtonProps = PropsWithChildren<BaseButtonProps> &
|
/**
|
||||||
Omit<HTMLAttributes<HTMLButtonElement>, 'type'> & {
|
* By default, it is considered as an icon with preset size and color,
|
||||||
componentProps?: {
|
* can be overridden by `prefixClassName` and `prefixStyle`.
|
||||||
startIcon?: Omit<IconButtonProps, 'icon' | 'iconPosition'>;
|
*
|
||||||
endIcon?: Omit<IconButtonProps, 'icon' | 'iconPosition'>;
|
* If `loading` is true, will be replaced by a spinner.(`prefixClassName` and `prefixStyle` still work)
|
||||||
};
|
* */
|
||||||
};
|
prefix?: ReactElement;
|
||||||
|
prefixClassName?: string;
|
||||||
|
prefixStyle?: CSSProperties;
|
||||||
|
contentClassName?: string;
|
||||||
|
contentStyle?: CSSProperties;
|
||||||
|
|
||||||
type IconButtonProps = PropsWithChildren<BaseButtonProps> &
|
/**
|
||||||
Omit<HTMLAttributes<HTMLDivElement>, 'type'>;
|
* By default, it is considered as an icon with preset size and color,
|
||||||
|
* can be overridden by `suffixClassName` and `suffixStyle`.
|
||||||
|
* */
|
||||||
|
suffix?: ReactElement;
|
||||||
|
suffixClassName?: string;
|
||||||
|
suffixStyle?: CSSProperties;
|
||||||
|
|
||||||
const defaultProps = {
|
tooltip?: TooltipProps['content'];
|
||||||
type: 'default',
|
tooltipOptions?: Partial<Omit<TooltipProps, 'content'>>;
|
||||||
disabled: false,
|
}
|
||||||
shape: 'default',
|
|
||||||
size: 'default',
|
|
||||||
iconPosition: 'start',
|
|
||||||
loading: false,
|
|
||||||
withoutHoverStyle: false,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const ButtonIcon: FC<IconButtonProps> = props => {
|
const IconSlot = ({
|
||||||
const {
|
icon,
|
||||||
size,
|
loading,
|
||||||
icon,
|
className,
|
||||||
iconPosition = 'start',
|
...attrs
|
||||||
children,
|
}: {
|
||||||
type,
|
icon?: ReactElement;
|
||||||
loading,
|
loading?: boolean;
|
||||||
withoutHoverStyle,
|
} & HTMLAttributes<HTMLElement>) => {
|
||||||
...otherProps
|
const showLoadingHere = loading !== undefined;
|
||||||
} = {
|
const visible = icon || loading;
|
||||||
...defaultProps,
|
return visible ? (
|
||||||
...props,
|
<div className={clsx(styles.icon, className)} {...attrs}>
|
||||||
};
|
{showLoadingHere && loading ? <Loading size="100%" /> : null}
|
||||||
const onlyIcon = icon && !children;
|
{icon && !loading
|
||||||
return (
|
? cloneElement(icon, {
|
||||||
<div
|
width: '100%',
|
||||||
{...otherProps}
|
height: '100%',
|
||||||
className={clsx(buttonIcon, {
|
...icon.props,
|
||||||
'color-white': type && type !== 'default' && type !== 'plain',
|
})
|
||||||
large: size === 'large',
|
: null}
|
||||||
extraLarge: size === 'extraLarge',
|
|
||||||
end: iconPosition === 'end' && !onlyIcon,
|
|
||||||
start: iconPosition === 'start' && !onlyIcon,
|
|
||||||
loading,
|
|
||||||
})}
|
|
||||||
data-without-hover={withoutHoverStyle}
|
|
||||||
>
|
|
||||||
{icon}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
(props, ref) => {
|
(
|
||||||
const {
|
{
|
||||||
|
variant = 'secondary',
|
||||||
|
size = 'default',
|
||||||
children,
|
children,
|
||||||
type,
|
|
||||||
disabled,
|
disabled,
|
||||||
shape,
|
|
||||||
size,
|
|
||||||
icon: propsIcon,
|
|
||||||
iconPosition,
|
|
||||||
block,
|
block,
|
||||||
loading,
|
loading,
|
||||||
withoutHoverStyle,
|
|
||||||
className,
|
className,
|
||||||
|
|
||||||
|
prefix,
|
||||||
|
prefixClassName,
|
||||||
|
prefixStyle,
|
||||||
|
suffix,
|
||||||
|
suffixClassName,
|
||||||
|
suffixStyle,
|
||||||
|
contentClassName,
|
||||||
|
contentStyle,
|
||||||
|
|
||||||
|
tooltip,
|
||||||
|
tooltipOptions,
|
||||||
|
onClick,
|
||||||
|
|
||||||
...otherProps
|
...otherProps
|
||||||
} = {
|
},
|
||||||
...defaultProps,
|
ref
|
||||||
...props,
|
) => {
|
||||||
} satisfies ButtonProps;
|
const handleClick = useCallback(
|
||||||
|
(e: MouseEvent<HTMLButtonElement>) => {
|
||||||
const icon = useMemo(() => {
|
if (loading || disabled) return;
|
||||||
if (loading) {
|
onClick?.(e);
|
||||||
return <Loading />;
|
},
|
||||||
}
|
[disabled, loading, onClick]
|
||||||
return propsIcon;
|
);
|
||||||
}, [propsIcon, loading]);
|
|
||||||
|
|
||||||
const baseIconButtonProps = useMemo(() => {
|
|
||||||
return {
|
|
||||||
size,
|
|
||||||
iconPosition,
|
|
||||||
icon,
|
|
||||||
type,
|
|
||||||
disabled,
|
|
||||||
loading,
|
|
||||||
} as const;
|
|
||||||
}, [disabled, icon, iconPosition, loading, size, type]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<Tooltip content={tooltip} {...tooltipOptions}>
|
||||||
{...otherProps}
|
<button
|
||||||
ref={ref}
|
{...otherProps}
|
||||||
className={clsx(
|
ref={ref}
|
||||||
button,
|
className={clsx(styles.button, className)}
|
||||||
{
|
data-loading={loading || undefined}
|
||||||
primary: type === 'primary',
|
data-block={block || undefined}
|
||||||
plain: type === 'plain',
|
disabled={disabled}
|
||||||
error: type === 'error',
|
data-disabled={disabled || undefined}
|
||||||
warning: type === 'warning',
|
data-size={size}
|
||||||
success: type === 'success',
|
data-variant={variant}
|
||||||
processing: type === 'processing',
|
onClick={handleClick}
|
||||||
large: size === 'large',
|
>
|
||||||
extraLarge: size === 'extraLarge',
|
<IconSlot
|
||||||
disabled,
|
icon={prefix}
|
||||||
circle: shape === 'circle',
|
loading={loading}
|
||||||
round: shape === 'round',
|
className={prefixClassName}
|
||||||
block,
|
style={prefixStyle}
|
||||||
loading,
|
|
||||||
'without-hover': withoutHoverStyle,
|
|
||||||
},
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
disabled={disabled}
|
|
||||||
data-disabled={disabled}
|
|
||||||
>
|
|
||||||
{icon && iconPosition === 'start' ? (
|
|
||||||
<ButtonIcon
|
|
||||||
{...baseIconButtonProps}
|
|
||||||
{...props.componentProps?.startIcon}
|
|
||||||
icon={icon}
|
|
||||||
iconPosition="start"
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
{children ? (
|
||||||
<span>{children}</span>
|
<span
|
||||||
{icon && iconPosition === 'end' ? (
|
className={clsx(styles.content, contentClassName)}
|
||||||
<ButtonIcon
|
style={contentStyle}
|
||||||
{...baseIconButtonProps}
|
>
|
||||||
{...props.componentProps?.endIcon}
|
{children}
|
||||||
icon={icon}
|
</span>
|
||||||
iconPosition="end"
|
) : null}
|
||||||
|
<IconSlot
|
||||||
|
icon={suffix}
|
||||||
|
className={suffixClassName}
|
||||||
|
style={suffixStyle}
|
||||||
/>
|
/>
|
||||||
) : null}
|
</button>
|
||||||
</button>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,49 +1,153 @@
|
|||||||
import { InformationIcon } from '@blocksuite/icons/rc';
|
import { AfFiNeIcon } from '@blocksuite/icons/rc';
|
||||||
import type { Meta, StoryFn } from '@storybook/react';
|
import type { Meta } from '@storybook/react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { type ReactElement, useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { Switch } from '../switch';
|
||||||
|
import * as styles from './button.stories.css';
|
||||||
import type { IconButtonProps } from './icon-button';
|
import type { IconButtonProps } from './icon-button';
|
||||||
import { IconButton } from './icon-button';
|
import { IconButton } from './icon-button';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'UI/IconButton',
|
title: 'UI/IconButton',
|
||||||
component: IconButton,
|
component: IconButton,
|
||||||
argTypes: {
|
|
||||||
onClick: () => console.log('Click button'),
|
|
||||||
},
|
|
||||||
} satisfies Meta<IconButtonProps>;
|
} satisfies Meta<IconButtonProps>;
|
||||||
|
|
||||||
const Template: StoryFn<IconButtonProps> = args => <IconButton {...args} />;
|
const types: IconButtonProps['variant'][] = ['plain', 'solid', 'danger'];
|
||||||
|
const sizes: IconButtonProps['size'][] = ['12', '14', '16', '20', '24'];
|
||||||
|
|
||||||
export const Plain: StoryFn<IconButtonProps> = Template.bind(undefined);
|
const Groups = ({
|
||||||
Plain.args = {
|
children,
|
||||||
children: <InformationIcon />,
|
...props
|
||||||
|
}: Omit<IconButtonProps, 'type' | 'size' | 'children'> & {
|
||||||
|
children?: ReactElement;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<table className={styles.table}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Type/Size</td>
|
||||||
|
{sizes.map(size => (
|
||||||
|
<td key={size}>{size}</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{types.map(type => (
|
||||||
|
<tr key={type}>
|
||||||
|
<td>{type}</td>
|
||||||
|
{sizes.map(size => (
|
||||||
|
<td key={size}>
|
||||||
|
<IconButton variant={type} size={size} {...props}>
|
||||||
|
{children ?? <AfFiNeIcon />}
|
||||||
|
</IconButton>
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Primary: StoryFn<IconButtonProps> = Template.bind(undefined);
|
export const Default = () => <Groups />;
|
||||||
Primary.args = {
|
|
||||||
type: 'primary',
|
export const Loading = () => {
|
||||||
icon: <InformationIcon />,
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const toggleLoading = useCallback(() => setLoading(v => !v), []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInterval(toggleLoading, 1000);
|
||||||
|
}, [toggleLoading]);
|
||||||
|
|
||||||
|
return <Groups loading={loading} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Disabled: StoryFn<IconButtonProps> = Template.bind(undefined);
|
export const OverrideViaClassName = () => {
|
||||||
Disabled.args = {
|
const [overrideBg, setOverrideBg] = useState(false);
|
||||||
disabled: true,
|
const [overrideBorder, setOverrideBorder] = useState(false);
|
||||||
icon: <InformationIcon />,
|
const [overridePrefixColor, setOverridePrefixColor] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={styles.settings}>
|
||||||
|
<section>
|
||||||
|
<span>Override background color</span>
|
||||||
|
<Switch checked={overrideBg} onChange={setOverrideBg} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<span>Override border color</span>
|
||||||
|
<Switch checked={overrideBorder} onChange={setOverrideBorder} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<span>Override icon color</span>
|
||||||
|
<Switch
|
||||||
|
checked={overridePrefixColor}
|
||||||
|
onChange={setOverridePrefixColor}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Groups
|
||||||
|
className={clsx({
|
||||||
|
[styles.overrideBackground]: overrideBg,
|
||||||
|
[styles.overrideBorder]: overrideBorder,
|
||||||
|
})}
|
||||||
|
iconClassName={clsx({
|
||||||
|
[styles.overrideIconColor]: overridePrefixColor,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
export const ExtraSmallSizeButton: StoryFn<IconButtonProps> =
|
|
||||||
Template.bind(undefined);
|
export const CustomSize = () => {
|
||||||
ExtraSmallSizeButton.args = {
|
const sizes = [
|
||||||
size: 'extraSmall',
|
[13, 2],
|
||||||
icon: <InformationIcon />,
|
[15, 2],
|
||||||
};
|
[17, 2],
|
||||||
export const SmallSizeButton: StoryFn<IconButtonProps> =
|
[19, 2],
|
||||||
Template.bind(undefined);
|
[21, 3],
|
||||||
SmallSizeButton.args = {
|
[23, 3],
|
||||||
size: 'small',
|
[25, 3],
|
||||||
icon: <InformationIcon />,
|
[27, 3],
|
||||||
};
|
[29, 4],
|
||||||
export const LargeSizeButton: StoryFn<IconButtonProps> =
|
[31, 4],
|
||||||
Template.bind(undefined);
|
[33, 4],
|
||||||
LargeSizeButton.args = {
|
[35, 4],
|
||||||
size: 'large',
|
];
|
||||||
icon: <InformationIcon />,
|
return types.map(type => {
|
||||||
|
return (
|
||||||
|
<div key={type}>
|
||||||
|
<div style={{ display: 'flex', gap: 4, alignItems: 'center' }}>
|
||||||
|
{sizes.map(size => (
|
||||||
|
<div
|
||||||
|
key={size[0]}
|
||||||
|
style={{
|
||||||
|
fontSize: 10,
|
||||||
|
textAlign: 'center',
|
||||||
|
color: 'rgba(100, 100, 100, 0.5)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
size={size[0]}
|
||||||
|
style={{ padding: size[1] }}
|
||||||
|
variant={type}
|
||||||
|
>
|
||||||
|
<AfFiNeIcon />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<div style={{ marginTop: 8 }}>Size: {size[0]}px</div>
|
||||||
|
<div style={{ marginTop: 2 }}>Padding: {size[1]}px</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Disabled = () => <Groups disabled />;
|
||||||
|
|||||||
@@ -1,85 +1,78 @@
|
|||||||
|
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type { HTMLAttributes, PropsWithChildren, ReactElement } from 'react';
|
import { type CSSProperties, forwardRef, type ReactElement } from 'react';
|
||||||
import { forwardRef } from 'react';
|
|
||||||
|
|
||||||
import { Loading } from '../loading';
|
import { Button, type ButtonProps } from './button';
|
||||||
import type { ButtonType } from './button';
|
import { iconButton, iconSizeVar } from './button.css';
|
||||||
import { iconButton } from './button.css';
|
|
||||||
|
|
||||||
export type IconButtonSize = 'default' | 'large' | 'small' | 'extraSmall';
|
export interface IconButtonProps
|
||||||
export type IconButtonProps = Omit<HTMLAttributes<HTMLButtonElement>, 'type'> &
|
extends Omit<
|
||||||
PropsWithChildren<{
|
ButtonProps,
|
||||||
type?: ButtonType;
|
| 'variant'
|
||||||
disabled?: boolean;
|
| 'size'
|
||||||
size?: IconButtonSize;
|
| 'prefix'
|
||||||
loading?: boolean;
|
| 'suffix'
|
||||||
withoutPadding?: boolean;
|
| 'children'
|
||||||
active?: boolean;
|
| 'prefixClassName'
|
||||||
withoutHoverStyle?: boolean;
|
| 'prefixStyle'
|
||||||
icon?: ReactElement;
|
| 'suffix'
|
||||||
}>;
|
| 'suffixClassName'
|
||||||
|
| 'suffixStyle'
|
||||||
const defaultProps = {
|
> {
|
||||||
type: 'plain',
|
/** Icon element */
|
||||||
disabled: false,
|
children?: ReactElement;
|
||||||
size: 'default',
|
/** Same as `children`, compatibility of the old API */
|
||||||
loading: false,
|
icon?: ReactElement;
|
||||||
withoutPadding: false,
|
variant?: 'plain' | 'solid' | 'danger' | 'custom';
|
||||||
active: false,
|
/**
|
||||||
withoutHoverStyle: false,
|
* Use preset size,
|
||||||
} as const;
|
* or use custom size(px) (default padding is `2px`, have to override yourself)
|
||||||
|
*
|
||||||
|
* > These presets size are referenced from the design system.
|
||||||
|
* > The number is the size of the icon, the button size is calculated based on the icon size + padding.
|
||||||
|
* > OR, you can define `width` and `height` in `style` or `className` directly.
|
||||||
|
*/
|
||||||
|
size?: '12' | '14' | '16' | '20' | '24' | number;
|
||||||
|
iconClassName?: string;
|
||||||
|
iconStyle?: CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
||||||
(props, ref) => {
|
(
|
||||||
const {
|
{
|
||||||
type,
|
variant = 'plain',
|
||||||
size,
|
size = '20',
|
||||||
withoutPadding,
|
style,
|
||||||
children,
|
|
||||||
disabled,
|
|
||||||
loading,
|
|
||||||
active,
|
|
||||||
withoutHoverStyle,
|
|
||||||
icon: propsIcon,
|
|
||||||
className,
|
className,
|
||||||
|
children,
|
||||||
|
icon,
|
||||||
|
iconClassName,
|
||||||
|
iconStyle,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = {
|
},
|
||||||
...defaultProps,
|
ref
|
||||||
...props,
|
) => {
|
||||||
};
|
const validatedSize = isNaN(parseInt(size as string, 10)) ? 16 : size;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<Button
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={clsx(
|
style={{
|
||||||
iconButton,
|
...style,
|
||||||
{
|
...assignInlineVars({
|
||||||
'without-padding': withoutPadding,
|
[iconSizeVar]: `${validatedSize}px`,
|
||||||
|
}),
|
||||||
primary: type === 'primary',
|
}}
|
||||||
plain: type === 'plain',
|
data-icon-variant={variant}
|
||||||
error: type === 'error',
|
data-icon-size={validatedSize}
|
||||||
warning: type === 'warning',
|
className={clsx(iconButton, className)}
|
||||||
success: type === 'success',
|
size={'custom'}
|
||||||
processing: type === 'processing',
|
variant={'custom'}
|
||||||
|
prefix={children ?? icon}
|
||||||
large: size === 'large',
|
prefixClassName={iconClassName}
|
||||||
small: size === 'small',
|
prefixStyle={iconStyle}
|
||||||
'extra-small': size === 'extraSmall',
|
|
||||||
|
|
||||||
disabled,
|
|
||||||
loading,
|
|
||||||
active,
|
|
||||||
'without-hover': withoutHoverStyle,
|
|
||||||
},
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
disabled={disabled}
|
|
||||||
data-disabled={disabled}
|
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
/>
|
||||||
{loading ? <Loading /> : children || propsIcon}
|
|
||||||
</button>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import type {
|
|
||||||
CSSProperties,
|
|
||||||
HTMLAttributes,
|
|
||||||
PropsWithChildren,
|
|
||||||
ReactElement,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
export const SIZE_SMALL = 'small' as const;
|
|
||||||
export const SIZE_MIDDLE = 'middle' as const;
|
|
||||||
export const SIZE_DEFAULT = 'default' as const;
|
|
||||||
|
|
||||||
export type ButtonProps = PropsWithChildren &
|
|
||||||
Omit<HTMLAttributes<HTMLButtonElement>, 'type'> & {
|
|
||||||
size?: typeof SIZE_SMALL | typeof SIZE_MIDDLE | typeof SIZE_DEFAULT;
|
|
||||||
disabled?: boolean;
|
|
||||||
hoverBackground?: CSSProperties['background'];
|
|
||||||
hoverColor?: CSSProperties['color'];
|
|
||||||
hoverStyle?: CSSProperties;
|
|
||||||
icon?: ReactElement;
|
|
||||||
iconPosition?: 'start' | 'end';
|
|
||||||
shape?: 'default' | 'round' | 'circle';
|
|
||||||
type?: 'primary' | 'light' | 'warning' | 'danger' | 'default';
|
|
||||||
bold?: boolean;
|
|
||||||
loading?: boolean;
|
|
||||||
noBorder?: boolean;
|
|
||||||
};
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
import type { ButtonProps } from './interface';
|
|
||||||
|
|
||||||
export const getButtonColors = (
|
|
||||||
type: ButtonProps['type'],
|
|
||||||
disabled: boolean,
|
|
||||||
extend?: {
|
|
||||||
hoverBackground: ButtonProps['hoverBackground'];
|
|
||||||
hoverColor: ButtonProps['hoverColor'];
|
|
||||||
hoverStyle: ButtonProps['hoverStyle'];
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
switch (type) {
|
|
||||||
case 'primary':
|
|
||||||
return {
|
|
||||||
background: 'var(--affine-primary-color)',
|
|
||||||
color: 'var(--affine-white)',
|
|
||||||
borderColor: 'var(--affine-primary-color)',
|
|
||||||
backgroundBlendMode: 'overlay',
|
|
||||||
opacity: disabled ? '.4' : '1',
|
|
||||||
'.affine-button-icon': {
|
|
||||||
color: 'var(--affine-white)',
|
|
||||||
},
|
|
||||||
':hover': {
|
|
||||||
background:
|
|
||||||
'linear-gradient(var(--affine-primary-color),var(--affine-primary-color)),var(--affine-hover-color)',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
case 'light':
|
|
||||||
return {
|
|
||||||
background: 'var(--affine-tertiary-color)',
|
|
||||||
color: disabled
|
|
||||||
? 'var(--affine-text-disable-color)'
|
|
||||||
: 'var(--affine-text-emphasis-color)',
|
|
||||||
borderColor: 'var(--affine-tertiary-color)',
|
|
||||||
'.affine-button-icon': {
|
|
||||||
borderColor: 'var(--affine-text-emphasis-color)',
|
|
||||||
},
|
|
||||||
':hover': {
|
|
||||||
borderColor: disabled
|
|
||||||
? 'var(--affine-disable-color)'
|
|
||||||
: 'var(--affine-text-emphasis-color)',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
case 'warning':
|
|
||||||
return {
|
|
||||||
background: 'var(--affine-background-warning-color)',
|
|
||||||
color: 'var(--affine-warning-color)',
|
|
||||||
borderColor: 'var(--affine-background-warning-color)',
|
|
||||||
'.affine-button-icon': {
|
|
||||||
color: 'var(--affine-warning-color)',
|
|
||||||
},
|
|
||||||
':hover': {
|
|
||||||
borderColor: 'var(--affine-warning-color)',
|
|
||||||
color: extend?.hoverColor,
|
|
||||||
background: extend?.hoverBackground,
|
|
||||||
...extend?.hoverStyle,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
case 'danger':
|
|
||||||
return {
|
|
||||||
background: 'var(--affine-background-error-color)',
|
|
||||||
color: 'var(--affine-error-color)',
|
|
||||||
borderColor: 'var(--affine-background-error-color)',
|
|
||||||
'.affine-button-icon': {
|
|
||||||
color: 'var(--affine-error-color)',
|
|
||||||
},
|
|
||||||
':hover': {
|
|
||||||
borderColor: 'var(--affine-error-color)',
|
|
||||||
color: extend?.hoverColor,
|
|
||||||
background: extend?.hoverBackground,
|
|
||||||
...extend?.hoverStyle,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
color: 'var(--affine-text-primary-color)',
|
|
||||||
borderColor: 'var(--affine-border-color)',
|
|
||||||
':hover': {
|
|
||||||
borderColor: 'var(--affine-primary-color)',
|
|
||||||
color: extend?.hoverColor ?? 'var(--affine-primary-color)',
|
|
||||||
'.affine-button-icon': {
|
|
||||||
color: extend?.hoverColor ?? 'var(--affine-primary-color)',
|
|
||||||
background: extend?.hoverBackground,
|
|
||||||
...extend?.hoverStyle,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -134,8 +134,7 @@ export const NavButtons = memo(function NavButtons({
|
|||||||
<div className={styles.headerNavButtons} key="nav-btn-group">
|
<div className={styles.headerNavButtons} key="nav-btn-group">
|
||||||
<IconButton
|
<IconButton
|
||||||
key="nav-btn-prev"
|
key="nav-btn-prev"
|
||||||
size="small"
|
size="16"
|
||||||
className={styles.focusInteractive}
|
|
||||||
disabled={prevDisabled}
|
disabled={prevDisabled}
|
||||||
data-testid="date-picker-nav-prev"
|
data-testid="date-picker-nav-prev"
|
||||||
onClick={onPrev}
|
onClick={onPrev}
|
||||||
@@ -147,8 +146,7 @@ export const NavButtons = memo(function NavButtons({
|
|||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
key="nav-btn-next"
|
key="nav-btn-next"
|
||||||
size="small"
|
size="16"
|
||||||
className={styles.focusInteractive}
|
|
||||||
disabled={nextDisabled}
|
disabled={nextDisabled}
|
||||||
data-testid="date-picker-nav-next"
|
data-testid="date-picker-nav-next"
|
||||||
onClick={onNext}
|
onClick={onNext}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||||
|
|
||||||
|
import { withUnit } from '../../utils/with-unit';
|
||||||
import { loading, speedVar } from './styles.css';
|
import { loading, speedVar } from './styles.css';
|
||||||
|
|
||||||
export interface LoadingProps {
|
export interface LoadingProps {
|
||||||
size?: number;
|
size?: number | string;
|
||||||
speed?: number;
|
speed?: number;
|
||||||
progress?: number;
|
progress?: number;
|
||||||
}
|
}
|
||||||
@@ -13,11 +14,13 @@ export const Loading = ({
|
|||||||
speed = 1.2,
|
speed = 1.2,
|
||||||
progress = 0.2,
|
progress = 0.2,
|
||||||
}: LoadingProps) => {
|
}: LoadingProps) => {
|
||||||
|
// allow `string` such as `16px` | `100%` | `1em`
|
||||||
|
const sizeWithUnit = size ? withUnit(size, 'px') : '16px';
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
className={loading}
|
className={loading}
|
||||||
width={size ? `${size}px` : '16px'}
|
width={sizeWithUnit}
|
||||||
height={size ? `${size}px` : '16px'}
|
height={sizeWithUnit}
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const ConfirmModalTemplate: StoryFn<ConfirmModalProps> = () => {
|
|||||||
confirmText="Confirm"
|
confirmText="Confirm"
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
loading: loading,
|
loading: loading,
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Input placeholder="input someting" status={inputStatus} />
|
<Input placeholder="input someting" status={inputStatus} />
|
||||||
@@ -82,7 +82,7 @@ const OverlayModalTemplate: StoryFn<OverlayModalProps> = () => {
|
|||||||
title="Modal Title"
|
title="Modal Title"
|
||||||
description="Modal description"
|
description="Modal description"
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
}}
|
}}
|
||||||
topImage={
|
topImage={
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -130,10 +130,12 @@ export const ModalInner = forwardRef<HTMLDivElement, ModalProps>(
|
|||||||
style: overlayStyle,
|
style: overlayStyle,
|
||||||
...otherOverlayOptions
|
...otherOverlayOptions
|
||||||
} = {},
|
} = {},
|
||||||
closeButtonOptions = {},
|
closeButtonOptions,
|
||||||
children,
|
children,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = props;
|
} = props;
|
||||||
|
const { className: closeButtonClassName, ...otherCloseButtonProps } =
|
||||||
|
closeButtonOptions || {};
|
||||||
|
|
||||||
const [container, setContainer] = useState<ModalTransitionContainer | null>(
|
const [container, setContainer] = useState<ModalTransitionContainer | null>(
|
||||||
null
|
null
|
||||||
@@ -204,11 +206,11 @@ export const ModalInner = forwardRef<HTMLDivElement, ModalProps>(
|
|||||||
{withoutCloseButton ? null : (
|
{withoutCloseButton ? null : (
|
||||||
<Dialog.Close asChild>
|
<Dialog.Close asChild>
|
||||||
<IconButton
|
<IconButton
|
||||||
className={styles.closeButton}
|
size="20"
|
||||||
|
className={clsx(styles.closeButton, closeButtonClassName)}
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
type="plain"
|
|
||||||
data-testid="modal-close-button"
|
data-testid="modal-close-button"
|
||||||
{...closeButtonOptions}
|
{...otherCloseButtonProps}
|
||||||
>
|
>
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const AdminPanelHeader = ({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
variant="primary"
|
||||||
disabled={modifiedValues.length === 0}
|
disabled={modifiedValues.length === 0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openConfirmModal({
|
openConfirmModal({
|
||||||
@@ -41,7 +41,7 @@ export const AdminPanelHeader = ({
|
|||||||
'Are you sure you want to save the following changes?',
|
'Are you sure you want to save the following changes?',
|
||||||
confirmText: 'Save',
|
confirmText: 'Save',
|
||||||
confirmButtonOptions: {
|
confirmButtonOptions: {
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
},
|
},
|
||||||
onConfirm: onConfirm,
|
onConfirm: onConfirm,
|
||||||
children:
|
children:
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export const ErrorDetail: FC<ErrorDetailProps> = props => {
|
|||||||
))}
|
))}
|
||||||
<div className={styles.errorFooter}>
|
<div className={styles.errorFooter}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
variant="primary"
|
||||||
onClick={onBtnClick}
|
onClick={onBtnClick}
|
||||||
loading={isBtnLoading}
|
loading={isBtnLoading}
|
||||||
size="extraLarge"
|
size="extraLarge"
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ export const thumbContent = style({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const actionButton = style({
|
export const actionButton = style({
|
||||||
fontWeight: 500,
|
|
||||||
fontSize: cssVar('fontSm'),
|
fontSize: cssVar('fontSm'),
|
||||||
lineHeight: '22px',
|
lineHeight: '22px',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ export const AIOnboardingEdgeless = () => {
|
|||||||
notify.dismiss(id);
|
notify.dismiss(id);
|
||||||
toggleEdgelessAIOnboarding(false);
|
toggleEdgelessAIOnboarding(false);
|
||||||
}}
|
}}
|
||||||
type="plain"
|
variant="plain"
|
||||||
className={styles.actionButton}
|
className={styles.actionButton}
|
||||||
>
|
>
|
||||||
<span className={styles.getStartedButtonText}>
|
<span className={styles.getStartedButtonText}>
|
||||||
@@ -113,7 +113,7 @@ export const AIOnboardingEdgeless = () => {
|
|||||||
{aiSubscription ? null : (
|
{aiSubscription ? null : (
|
||||||
<Button
|
<Button
|
||||||
className={styles.actionButton}
|
className={styles.actionButton}
|
||||||
type="plain"
|
variant="plain"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
goToPricingPlans();
|
goToPricingPlans();
|
||||||
notify.dismiss(id);
|
notify.dismiss(id);
|
||||||
|
|||||||
@@ -114,17 +114,3 @@ export const subscribeActions = style({
|
|||||||
gap: 12,
|
gap: 12,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
});
|
});
|
||||||
export const baseActionButton = style({
|
|
||||||
fontSize: cssVar('fontBase'),
|
|
||||||
selectors: {
|
|
||||||
'&.large': {
|
|
||||||
fontWeight: 500,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
export const transparentActionButton = style([
|
|
||||||
baseActionButton,
|
|
||||||
{
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|||||||
@@ -243,36 +243,26 @@ export const AIOnboardingGeneral = () => {
|
|||||||
>
|
>
|
||||||
{isLast ? (
|
{isLast ? (
|
||||||
<>
|
<>
|
||||||
<IconButton
|
<IconButton size="20" onClick={onPrev}>
|
||||||
size="default"
|
<ArrowLeftSmallIcon />
|
||||||
icon={<ArrowLeftSmallIcon width={20} height={20} />}
|
</IconButton>
|
||||||
onClick={onPrev}
|
|
||||||
type="plain"
|
|
||||||
className={styles.baseActionButton}
|
|
||||||
/>
|
|
||||||
{aiSubscription ? (
|
{aiSubscription ? (
|
||||||
<Button
|
<Button
|
||||||
className={styles.baseActionButton}
|
|
||||||
size="large"
|
size="large"
|
||||||
onClick={closeAndDismiss}
|
onClick={closeAndDismiss}
|
||||||
type="primary"
|
variant="primary"
|
||||||
>
|
>
|
||||||
{t['com.affine.ai-onboarding.general.get-started']()}
|
{t['com.affine.ai-onboarding.general.get-started']()}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<div className={styles.subscribeActions}>
|
<div className={styles.subscribeActions}>
|
||||||
<Button
|
<Button size="large" onClick={goToPricingPlans}>
|
||||||
className={styles.baseActionButton}
|
|
||||||
size="large"
|
|
||||||
onClick={goToPricingPlans}
|
|
||||||
>
|
|
||||||
{t['com.affine.ai-onboarding.general.purchase']()}
|
{t['com.affine.ai-onboarding.general.purchase']()}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className={styles.baseActionButton}
|
|
||||||
size="large"
|
size="large"
|
||||||
onClick={closeAndDismiss}
|
onClick={closeAndDismiss}
|
||||||
type="primary"
|
variant="primary"
|
||||||
>
|
>
|
||||||
{t['com.affine.ai-onboarding.general.try-for-free']()}
|
{t['com.affine.ai-onboarding.general.try-for-free']()}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -282,21 +272,15 @@ export const AIOnboardingGeneral = () => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{isFirst ? (
|
{isFirst ? (
|
||||||
<Button
|
<Button onClick={remindLater} size="large">
|
||||||
className={styles.transparentActionButton}
|
|
||||||
onClick={remindLater}
|
|
||||||
size="large"
|
|
||||||
type="default"
|
|
||||||
>
|
|
||||||
{t['com.affine.ai-onboarding.general.skip']()}
|
{t['com.affine.ai-onboarding.general.skip']()}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
icon={<ArrowLeftSmallIcon />}
|
prefix={<ArrowLeftSmallIcon />}
|
||||||
className={styles.baseActionButton}
|
|
||||||
onClick={onPrev}
|
onClick={onPrev}
|
||||||
type="plain"
|
|
||||||
size="large"
|
size="large"
|
||||||
|
variant="plain"
|
||||||
>
|
>
|
||||||
{t['com.affine.ai-onboarding.general.prev']()}
|
{t['com.affine.ai-onboarding.general.prev']()}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -305,12 +289,7 @@ export const AIOnboardingGeneral = () => {
|
|||||||
<div>
|
<div>
|
||||||
{index + 1} / {list.length}
|
{index + 1} / {list.length}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button size="large" variant="primary" onClick={onNext}>
|
||||||
className={styles.baseActionButton}
|
|
||||||
size="large"
|
|
||||||
type="primary"
|
|
||||||
onClick={onNext}
|
|
||||||
>
|
|
||||||
{t['com.affine.ai-onboarding.general.next']()}
|
{t['com.affine.ai-onboarding.general.next']()}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const FooterActions = ({ onDismiss }: { onDismiss: () => void }) => {
|
|||||||
<a href="https://ai.affine.pro" target="_blank" rel="noreferrer">
|
<a href="https://ai.affine.pro" target="_blank" rel="noreferrer">
|
||||||
<Button
|
<Button
|
||||||
className={styles.actionButton}
|
className={styles.actionButton}
|
||||||
type="plain"
|
variant="plain"
|
||||||
onClick={onDismiss}
|
onClick={onDismiss}
|
||||||
>
|
>
|
||||||
{t['com.affine.ai-onboarding.local.action-learn-more']()}
|
{t['com.affine.ai-onboarding.local.action-learn-more']()}
|
||||||
@@ -50,7 +50,7 @@ const FooterActions = ({ onDismiss }: { onDismiss: () => void }) => {
|
|||||||
{loggedIn ? null : (
|
{loggedIn ? null : (
|
||||||
<Button
|
<Button
|
||||||
className={styles.actionButton}
|
className={styles.actionButton}
|
||||||
type="plain"
|
variant="plain"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onDismiss();
|
onDismiss();
|
||||||
jumpToSignIn('', RouteLogic.REPLACE, {}, { initCloud: 'true' });
|
jumpToSignIn('', RouteLogic.REPLACE, {}, { initCloud: 'true' });
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export const AfterSignInSendEmail = ({
|
|||||||
<Button
|
<Button
|
||||||
style={!verifyToken ? { cursor: 'not-allowed' } : {}}
|
style={!verifyToken ? { cursor: 'not-allowed' } : {}}
|
||||||
disabled={!verifyToken || isSending}
|
disabled={!verifyToken || isSending}
|
||||||
type="plain"
|
variant="plain"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={onResendClick}
|
onClick={onResendClick}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ export const AfterSignUpSendEmail: FC<AuthPanelProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
style={!verifyToken ? { cursor: 'not-allowed' } : {}}
|
style={!verifyToken ? { cursor: 'not-allowed' } : {}}
|
||||||
disabled={!verifyToken || isSending}
|
disabled={!verifyToken || isSending}
|
||||||
type="plain"
|
variant="plain"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={onResendClick}
|
onClick={onResendClick}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const AiLoginRequiredModal = () => {
|
|||||||
},
|
},
|
||||||
confirmText: t['com.affine.ai.login-required.dialog-confirm'](),
|
confirmText: t['com.affine.ai.login-required.dialog-confirm'](),
|
||||||
confirmButtonOptions: {
|
confirmButtonOptions: {
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
},
|
},
|
||||||
cancelText: t['com.affine.ai.login-required.dialog-cancel'](),
|
cancelText: t['com.affine.ai.login-required.dialog-cancel'](),
|
||||||
onOpenChange: setOpen,
|
onOpenChange: setOpen,
|
||||||
|
|||||||
@@ -82,11 +82,11 @@ function OAuthProvider({
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
key={provider}
|
key={provider}
|
||||||
type="primary"
|
variant="primary"
|
||||||
block
|
block
|
||||||
size="extraLarge"
|
size="extraLarge"
|
||||||
style={{ marginTop: 30 }}
|
style={{ marginTop: 30, width: '100%' }}
|
||||||
icon={icon}
|
prefix={icon}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
Continue with {provider}
|
Continue with {provider}
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ export const SendEmail = ({
|
|||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
variant="primary"
|
||||||
size="extraLarge"
|
size="extraLarge"
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
disabled={hasSentEmail}
|
disabled={hasSentEmail}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ export const SignInWithPassword: FC<AuthPanelProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
data-testid="sign-in-button"
|
data-testid="sign-in-button"
|
||||||
type="primary"
|
variant="primary"
|
||||||
size="extraLarge"
|
size="extraLarge"
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import { authAtom } from '@affine/core/atoms';
|
|||||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||||
import { mixpanel } from '@affine/core/mixpanel';
|
import { mixpanel } from '@affine/core/mixpanel';
|
||||||
import { Trans, useI18n } from '@affine/i18n';
|
import { Trans, useI18n } from '@affine/i18n';
|
||||||
import { ArrowDownBigIcon } from '@blocksuite/icons/rc';
|
import { ArrowRightBigIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
@@ -149,21 +150,13 @@ export const SignIn: FC<AuthPanelProps> = ({
|
|||||||
|
|
||||||
{verifyToken ? (
|
{verifyToken ? (
|
||||||
<Button
|
<Button
|
||||||
|
style={{ width: '100%' }}
|
||||||
size="extraLarge"
|
size="extraLarge"
|
||||||
data-testid="continue-login-button"
|
data-testid="continue-login-button"
|
||||||
block
|
block
|
||||||
loading={isMutating}
|
loading={isMutating}
|
||||||
icon={
|
suffix={<ArrowRightBigIcon />}
|
||||||
<ArrowDownBigIcon
|
suffixStyle={{ width: 20, height: 20, color: cssVar('blue') }}
|
||||||
width={20}
|
|
||||||
height={20}
|
|
||||||
style={{
|
|
||||||
transform: 'rotate(-90deg)',
|
|
||||||
color: 'var(--affine-blue)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
iconPosition="end"
|
|
||||||
onClick={onContinue}
|
onClick={onContinue}
|
||||||
>
|
>
|
||||||
{t['com.affine.auth.sign.email.continue']()}
|
{t['com.affine.auth.sign.email.continue']()}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ const NameWorkspaceContent = ({
|
|||||||
cancelText={t['com.affine.nameWorkspace.button.cancel']()}
|
cancelText={t['com.affine.nameWorkspace.button.cancel']()}
|
||||||
confirmText={t['com.affine.nameWorkspace.button.create']()}
|
confirmText={t['com.affine.nameWorkspace.button.create']()}
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
disabled: !workspaceName || loading,
|
disabled: !workspaceName || loading,
|
||||||
['data-testid' as string]: 'create-workspace-create-button',
|
['data-testid' as string]: 'create-workspace-create-button',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const HistoryTipsModal = () => {
|
|||||||
description={t['com.affine.history-vision.tips-modal.description']()}
|
description={t['com.affine.history-vision.tips-modal.description']()}
|
||||||
cancelText={t['com.affine.history-vision.tips-modal.cancel']()}
|
cancelText={t['com.affine.history-vision.tips-modal.cancel']()}
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
}}
|
}}
|
||||||
onConfirm={handleConfirm}
|
onConfirm={handleConfirm}
|
||||||
confirmText={t['com.affine.history-vision.tips-modal.confirm']()}
|
confirmText={t['com.affine.history-vision.tips-modal.confirm']()}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const IssueFeedbackModal = () => {
|
|||||||
to={`${runtimeConfig.githubUrl}/issues/new/choose`}
|
to={`${runtimeConfig.githubUrl}/issues/new/choose`}
|
||||||
confirmText={t['com.affine.issue-feedback.confirm']()}
|
confirmText={t['com.affine.issue-feedback.confirm']()}
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
}}
|
}}
|
||||||
external
|
external
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const AnimateInTooltip = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.next}>
|
<div className={styles.next}>
|
||||||
{visible ? (
|
{visible ? (
|
||||||
<Button type="primary" size="extraLarge" onClick={onNext}>
|
<Button variant="primary" size="extraLarge" onClick={onNext}>
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ export const EdgelessSwitch = ({
|
|||||||
onSwitchToPageMode={onSwitchToPageMode}
|
onSwitchToPageMode={onSwitchToPageMode}
|
||||||
onSwitchToEdgelessMode={onSwitchToEdgelessMode}
|
onSwitchToEdgelessMode={onSwitchToEdgelessMode}
|
||||||
/>
|
/>
|
||||||
<Button size="extraLarge" type="primary" onClick={onNextClick}>
|
<Button size="extraLarge" variant="primary" onClick={onNextClick}>
|
||||||
Next
|
Next
|
||||||
</Button>
|
</Button>
|
||||||
</header>
|
</header>
|
||||||
@@ -263,7 +263,7 @@ export const EdgelessSwitch = ({
|
|||||||
<Button
|
<Button
|
||||||
className={styles.wellDoneEnterAnim}
|
className={styles.wellDoneEnterAnim}
|
||||||
onClick={onNextClick}
|
onClick={onNextClick}
|
||||||
type="primary"
|
variant="primary"
|
||||||
size="extraLarge"
|
size="extraLarge"
|
||||||
style={{ marginTop: 40 }}
|
style={{ marginTop: 40 }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -251,11 +251,9 @@ const PlanPrompt = () => {
|
|||||||
: '' /* TODO(@catsjuice): loading UI */
|
: '' /* TODO(@catsjuice): loading UI */
|
||||||
}
|
}
|
||||||
|
|
||||||
<IconButton
|
<IconButton onClick={closeFreePlanPrompt}>
|
||||||
size="small"
|
<CloseIcon />
|
||||||
icon={<CloseIcon />}
|
</IconButton>
|
||||||
onClick={closeFreePlanPrompt}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}, [closeFreePlanPrompt, isProWorkspace, t]);
|
}, [closeFreePlanPrompt, isProWorkspace, t]);
|
||||||
@@ -393,7 +391,7 @@ const PageHistoryList = ({
|
|||||||
})}
|
})}
|
||||||
{onLoadMore ? (
|
{onLoadMore ? (
|
||||||
<Button
|
<Button
|
||||||
type="plain"
|
variant="plain"
|
||||||
loading={loadingMore}
|
loading={loadingMore}
|
||||||
disabled={loadingMore}
|
disabled={loadingMore}
|
||||||
className={styles.historyItemLoadMore}
|
className={styles.historyItemLoadMore}
|
||||||
@@ -480,7 +478,7 @@ const PageHistoryManager = ({
|
|||||||
},
|
},
|
||||||
confirmText: t['com.affine.history.confirm-restore-modal.restore'](),
|
confirmText: t['com.affine.history.confirm-restore-modal.restore'](),
|
||||||
confirmButtonOptions: {
|
confirmButtonOptions: {
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
['data-testid' as string]: 'confirm-restore-history-button',
|
['data-testid' as string]: 'confirm-restore-history-button',
|
||||||
},
|
},
|
||||||
onConfirm: handleRestore,
|
onConfirm: handleRestore,
|
||||||
@@ -520,12 +518,12 @@ const PageHistoryManager = ({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div className={styles.historyFooter}>
|
<div className={styles.historyFooter}>
|
||||||
<Button type="plain" onClick={onClose}>
|
<Button onClick={onClose}>
|
||||||
{t['com.affine.history.back-to-page']()}
|
{t['com.affine.history.back-to-page']()}
|
||||||
</Button>
|
</Button>
|
||||||
<div className={styles.spacer} />
|
<div className={styles.spacer} />
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
variant="primary"
|
||||||
onClick={onConfirmRestore}
|
onClick={onConfirmRestore}
|
||||||
disabled={isMutating || !activeVersion}
|
disabled={isMutating || !activeVersion}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const ConfirmDeletePropertyModal = ({
|
|||||||
onClick: onCancel,
|
onClick: onCancel,
|
||||||
}}
|
}}
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
type: 'error',
|
variant: 'error',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -117,22 +117,14 @@ export const tableBodySortable = style({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const addPropertyButton = style({
|
export const addPropertyButton = style({
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
alignSelf: 'flex-start',
|
alignSelf: 'flex-start',
|
||||||
fontSize: cssVar('fontSm'),
|
fontSize: cssVar('fontSm'),
|
||||||
color: `${cssVar('textSecondaryColor')} !important`,
|
color: `${cssVar('textSecondaryColor')}`,
|
||||||
padding: '0 4px',
|
padding: '0 4px',
|
||||||
height: 36,
|
height: 36,
|
||||||
cursor: 'pointer',
|
|
||||||
':hover': {
|
|
||||||
color: cssVar('textPrimaryColor'),
|
|
||||||
backgroundColor: cssVar('hoverColor'),
|
|
||||||
},
|
|
||||||
gap: 2,
|
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
|
gap: 6,
|
||||||
});
|
});
|
||||||
|
|
||||||
globalStyle(`${addPropertyButton} svg`, {
|
globalStyle(`${addPropertyButton} svg`, {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: cssVar('iconSecondary'),
|
color: cssVar('iconSecondary'),
|
||||||
|
|||||||
@@ -703,11 +703,9 @@ export const PagePropertiesTableHeader = ({
|
|||||||
</div>
|
</div>
|
||||||
{properties.length === 0 || manager.readonly ? null : (
|
{properties.length === 0 || manager.readonly ? null : (
|
||||||
<PagePropertiesSettingsPopup>
|
<PagePropertiesSettingsPopup>
|
||||||
<IconButton
|
<IconButton data-testid="page-info-show-more" size="20">
|
||||||
data-testid="page-info-show-more"
|
<MoreHorizontalIcon />
|
||||||
type="plain"
|
</IconButton>
|
||||||
icon={<MoreHorizontalIcon />}
|
|
||||||
/>
|
|
||||||
</PagePropertiesSettingsPopup>
|
</PagePropertiesSettingsPopup>
|
||||||
)}
|
)}
|
||||||
<Collapsible.Trigger asChild role="button" onClick={handleCollapse}>
|
<Collapsible.Trigger asChild role="button" onClick={handleCollapse}>
|
||||||
@@ -715,15 +713,12 @@ export const PagePropertiesTableHeader = ({
|
|||||||
className={styles.tableHeaderCollapseButtonWrapper}
|
className={styles.tableHeaderCollapseButtonWrapper}
|
||||||
data-testid="page-info-collapse"
|
data-testid="page-info-collapse"
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton size="20">
|
||||||
type="plain"
|
<ToggleExpandIcon
|
||||||
icon={
|
className={styles.collapsedIcon}
|
||||||
<ToggleExpandIcon
|
data-collapsed={!open}
|
||||||
className={styles.collapsedIcon}
|
/>
|
||||||
data-collapsed={!open}
|
</IconButton>
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Collapsible.Trigger>
|
</Collapsible.Trigger>
|
||||||
</div>
|
</div>
|
||||||
@@ -1056,8 +1051,8 @@ export const PagePropertiesAddProperty = () => {
|
|||||||
return (
|
return (
|
||||||
<Menu {...menuOptions}>
|
<Menu {...menuOptions}>
|
||||||
<Button
|
<Button
|
||||||
type="plain"
|
variant="plain"
|
||||||
icon={<PlusIcon />}
|
prefix={<PlusIcon />}
|
||||||
className={styles.addPropertyButton}
|
className={styles.addPropertyButton}
|
||||||
>
|
>
|
||||||
{t['com.affine.page-properties.add-property']()}
|
{t['com.affine.page-properties.add-property']()}
|
||||||
|
|||||||
@@ -405,11 +405,9 @@ export const TagsEditor = ({ pageId, readonly }: TagsEditorProps) => {
|
|||||||
<TagItem maxWidth="100%" tag={tag} mode="inline" />
|
<TagItem maxWidth="100%" tag={tag} mode="inline" />
|
||||||
<div className={styles.spacer} />
|
<div className={styles.spacer} />
|
||||||
<EditTagMenu tagId={tag.id} onTagDelete={onTagDelete}>
|
<EditTagMenu tagId={tag.id} onTagDelete={onTagDelete}>
|
||||||
<IconButton
|
<IconButton className={styles.tagEditIcon}>
|
||||||
className={styles.tagEditIcon}
|
<MoreHorizontalIcon />
|
||||||
type="plain"
|
</IconButton>
|
||||||
icon={<MoreHorizontalIcon />}
|
|
||||||
/>
|
|
||||||
</EditTagMenu>
|
</EditTagMenu>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export const CloudQuotaModal = () => {
|
|||||||
isFreePlanOwner ? t['com.affine.payment.upgrade']() : t['Got it']()
|
isFreePlanOwner ? t['com.affine.payment.upgrade']() : t['Got it']()
|
||||||
}
|
}
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const LocalQuotaModal = () => {
|
|||||||
onConfirm={onConfirm}
|
onConfirm={onConfirm}
|
||||||
confirmText={t['Got it']()}
|
confirmText={t['Got it']()}
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export const AIUsagePanel = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{hasPaymentFeature && (
|
{hasPaymentFeature && (
|
||||||
<AISubscribe type="primary" className={styles.storageButton}>
|
<AISubscribe variant="primary">
|
||||||
{t['com.affine.payment.ai.usage.purchase-button-label']()}
|
{t['com.affine.payment.ai.usage.purchase-button-label']()}
|
||||||
</AISubscribe>
|
</AISubscribe>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -142,7 +142,6 @@ export const AvatarAndName = () => {
|
|||||||
<Button
|
<Button
|
||||||
data-testid="save-user-name"
|
data-testid="save-user-name"
|
||||||
onClick={handleUpdateUserName}
|
onClick={handleUpdateUserName}
|
||||||
className={styles.button}
|
|
||||||
style={{
|
style={{
|
||||||
marginLeft: '12px',
|
marginLeft: '12px',
|
||||||
}}
|
}}
|
||||||
@@ -234,7 +233,7 @@ export const AccountSetting: FC = () => {
|
|||||||
/>
|
/>
|
||||||
<AvatarAndName />
|
<AvatarAndName />
|
||||||
<SettingRow name={t['com.affine.settings.email']()} desc={account.email}>
|
<SettingRow name={t['com.affine.settings.email']()} desc={account.email}>
|
||||||
<Button onClick={onChangeEmail} className={styles.button}>
|
<Button onClick={onChangeEmail}>
|
||||||
{account.info?.emailVerified
|
{account.info?.emailVerified
|
||||||
? t['com.affine.settings.email.action.change']()
|
? t['com.affine.settings.email.action.change']()
|
||||||
: t['com.affine.settings.email.action.verify']()}
|
: t['com.affine.settings.email.action.verify']()}
|
||||||
@@ -244,7 +243,7 @@ export const AccountSetting: FC = () => {
|
|||||||
name={t['com.affine.settings.password']()}
|
name={t['com.affine.settings.password']()}
|
||||||
desc={t['com.affine.settings.password.message']()}
|
desc={t['com.affine.settings.password.message']()}
|
||||||
>
|
>
|
||||||
<Button onClick={onPasswordButtonClick} className={styles.button}>
|
<Button onClick={onPasswordButtonClick}>
|
||||||
{account.info?.hasPassword
|
{account.info?.hasPassword
|
||||||
? t['com.affine.settings.password.action.change']()
|
? t['com.affine.settings.password.action.change']()
|
||||||
: t['com.affine.settings.password.action.set']()}
|
: t['com.affine.settings.password.action.set']()}
|
||||||
|
|||||||
@@ -28,6 +28,3 @@ globalStyle(`${storageProgressWrapper} .storage-progress-bar-wrapper`, {
|
|||||||
export const storageProgressBar = style({
|
export const storageProgressBar = style({
|
||||||
height: '100%',
|
height: '100%',
|
||||||
});
|
});
|
||||||
export const storageButton = style({
|
|
||||||
padding: '4px 12px',
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export interface StorageProgressProgress {
|
|||||||
|
|
||||||
enum ButtonType {
|
enum ButtonType {
|
||||||
Primary = 'primary',
|
Primary = 'primary',
|
||||||
Default = 'default',
|
Default = 'secondary',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StorageProgress = ({ onUpgrade }: StorageProgressProgress) => {
|
export const StorageProgress = ({ onUpgrade }: StorageProgressProgress) => {
|
||||||
@@ -101,11 +101,7 @@ export const StorageProgress = ({ onUpgrade }: StorageProgressProgress) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span tabIndex={0}>
|
<span tabIndex={0}>
|
||||||
<Button
|
<Button variant={buttonType} onClick={onUpgrade}>
|
||||||
type={buttonType}
|
|
||||||
onClick={onUpgrade}
|
|
||||||
className={styles.storageButton}
|
|
||||||
>
|
|
||||||
{isFreeUser
|
{isFreeUser
|
||||||
? t['com.affine.storage.upgrade']()
|
? t['com.affine.storage.upgrade']()
|
||||||
: t['com.affine.storage.change-plan']()}
|
: t['com.affine.storage.change-plan']()}
|
||||||
|
|||||||
@@ -39,6 +39,3 @@ globalStyle(`${avatarWrapper} .camera-icon-wrapper`, {
|
|||||||
color: cssVar('white'),
|
color: cssVar('white'),
|
||||||
fontSize: cssVar('fontH4'),
|
fontSize: cssVar('fontH4'),
|
||||||
});
|
});
|
||||||
export const button = style({
|
|
||||||
padding: '4px 12px',
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -299,9 +299,7 @@ const TypeFormLink = () => {
|
|||||||
desc={t['com.affine.payment.billing-type-form.description']()}
|
desc={t['com.affine.payment.billing-type-form.description']()}
|
||||||
>
|
>
|
||||||
<a target="_blank" href={link} rel="noreferrer">
|
<a target="_blank" href={link} rel="noreferrer">
|
||||||
<Button style={{ padding: '4px 12px' }}>
|
<Button>{t['com.affine.payment.billing-type-form.go']()}</Button>
|
||||||
{t['com.affine.payment.billing-type-form.go']()}
|
|
||||||
</Button>
|
|
||||||
</a>
|
</a>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
);
|
);
|
||||||
@@ -435,7 +433,7 @@ const PlanAction = ({
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className={styles.planAction}
|
className={styles.planAction}
|
||||||
type="primary"
|
variant="primary"
|
||||||
onClick={gotoPlansSetting}
|
onClick={gotoPlansSetting}
|
||||||
>
|
>
|
||||||
{plan === SubscriptionPlan.Pro
|
{plan === SubscriptionPlan.Pro
|
||||||
@@ -460,12 +458,7 @@ const PaymentMethodUpdater = () => {
|
|||||||
}, [trigger]);
|
}, [trigger]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button onClick={update} loading={isMutating} disabled={isMutating}>
|
||||||
className={styles.button}
|
|
||||||
onClick={update}
|
|
||||||
loading={isMutating}
|
|
||||||
disabled={isMutating}
|
|
||||||
>
|
|
||||||
{t['com.affine.payment.billing-setting.update']()}
|
{t['com.affine.payment.billing-setting.update']()}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
@@ -492,7 +485,7 @@ const ResumeSubscription = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ResumeAction open={open} onOpenChange={setOpen}>
|
<ResumeAction open={open} onOpenChange={setOpen}>
|
||||||
<Button className={styles.button} onClick={handleClick}>
|
<Button onClick={handleClick}>
|
||||||
{t['com.affine.payment.billing-setting.resume-subscription']()}
|
{t['com.affine.payment.billing-setting.resume-subscription']()}
|
||||||
</Button>
|
</Button>
|
||||||
</ResumeAction>
|
</ResumeAction>
|
||||||
@@ -503,10 +496,11 @@ const CancelSubscription = ({ loading }: { loading?: boolean }) => {
|
|||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
style={{ pointerEvents: 'none' }}
|
style={{ pointerEvents: 'none' }}
|
||||||
icon={<ArrowRightSmallIcon />}
|
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
/>
|
>
|
||||||
|
<ArrowRightSmallIcon />
|
||||||
|
</IconButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -583,7 +577,7 @@ const InvoiceLine = ({
|
|||||||
: ''
|
: ''
|
||||||
} $${invoice.amount / 100} - ${planText}`}
|
} $${invoice.amount / 100} - ${planText}`}
|
||||||
>
|
>
|
||||||
<Button className={styles.button} onClick={open}>
|
<Button onClick={open}>
|
||||||
{t['com.affine.payment.billing-setting.view-invoice']()}
|
{t['com.affine.payment.billing-setting.view-invoice']()}
|
||||||
</Button>
|
</Button>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|||||||
@@ -46,9 +46,6 @@ export const currentPlanName = style({
|
|||||||
color: cssVar('textEmphasisColor'),
|
color: cssVar('textEmphasisColor'),
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
});
|
});
|
||||||
export const button = style({
|
|
||||||
padding: '4px 12px',
|
|
||||||
});
|
|
||||||
export const subscriptionSettingSkeleton = style({
|
export const subscriptionSettingSkeleton = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const ExperimentalFeaturesPrompt = ({
|
|||||||
<Button
|
<Button
|
||||||
disabled={!checked}
|
disabled={!checked}
|
||||||
onClick={onConfirm}
|
onClick={onConfirm}
|
||||||
type="primary"
|
variant="primary"
|
||||||
data-testid="experimental-confirm-button"
|
data-testid="experimental-confirm-button"
|
||||||
>
|
>
|
||||||
{t[
|
{t[
|
||||||
|
|||||||
@@ -43,12 +43,12 @@ export const AICancel = ({ module, ...btnProps }: AICancelProps) => {
|
|||||||
confirmText:
|
confirmText:
|
||||||
t['com.affine.payment.ai.action.cancel.confirm.confirm-text'](),
|
t['com.affine.payment.ai.action.cancel.confirm.confirm-text'](),
|
||||||
confirmButtonOptions: {
|
confirmButtonOptions: {
|
||||||
type: 'default',
|
variant: 'secondary',
|
||||||
},
|
},
|
||||||
cancelText:
|
cancelText:
|
||||||
t['com.affine.payment.ai.action.cancel.confirm.cancel-text'](),
|
t['com.affine.payment.ai.action.cancel.confirm.cancel-text'](),
|
||||||
cancelButtonOptions: {
|
cancelButtonOptions: {
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
},
|
},
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
try {
|
try {
|
||||||
@@ -92,7 +92,12 @@ export const AICancel = ({ module, ...btnProps }: AICancelProps) => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={cancel} loading={isMutating} type="primary" {...btnProps}>
|
<Button
|
||||||
|
onClick={cancel}
|
||||||
|
loading={isMutating}
|
||||||
|
variant="primary"
|
||||||
|
{...btnProps}
|
||||||
|
>
|
||||||
{t['com.affine.payment.ai.action.cancel.button-label']()}
|
{t['com.affine.payment.ai.action.cancel.button-label']()}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export const AILogin = (btnProps: ButtonProps) => {
|
|||||||
}, [setOpen]);
|
}, [setOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={onClickSignIn} type="primary" {...btnProps}>
|
<Button onClick={onClickSignIn} variant="primary" {...btnProps}>
|
||||||
{t['com.affine.payment.ai.action.login.button-label']()}
|
{t['com.affine.payment.ai.action.login.button-label']()}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const AIResume = ({ module, ...btnProps }: AIResumeProps) => {
|
|||||||
confirmText:
|
confirmText:
|
||||||
t['com.affine.payment.ai.action.resume.confirm.confirm-text'](),
|
t['com.affine.payment.ai.action.resume.confirm.confirm-text'](),
|
||||||
confirmButtonOptions: {
|
confirmButtonOptions: {
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
},
|
},
|
||||||
cancelText:
|
cancelText:
|
||||||
t['com.affine.payment.ai.action.resume.confirm.cancel-text'](),
|
t['com.affine.payment.ai.action.resume.confirm.cancel-text'](),
|
||||||
@@ -79,7 +79,12 @@ export const AIResume = ({ module, ...btnProps }: AIResumeProps) => {
|
|||||||
}, [subscription, openConfirmModal, t, module, idempotencyKey]);
|
}, [subscription, openConfirmModal, t, module, idempotencyKey]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button loading={isMutating} onClick={resume} type="primary" {...btnProps}>
|
<Button
|
||||||
|
loading={isMutating}
|
||||||
|
onClick={resume}
|
||||||
|
variant="primary"
|
||||||
|
{...btnProps}
|
||||||
|
>
|
||||||
{t['com.affine.payment.ai.action.resume.button-label']()}
|
{t['com.affine.payment.ai.action.resume.button-label']()}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export const AISubscribe = ({
|
|||||||
<Button
|
<Button
|
||||||
loading={isMutating}
|
loading={isMutating}
|
||||||
onClick={subscribe}
|
onClick={subscribe}
|
||||||
type="primary"
|
variant="primary"
|
||||||
{...btnProps}
|
{...btnProps}
|
||||||
>
|
>
|
||||||
{btnProps.children ?? `${priceReadable} / ${priceFrequency}`}
|
{btnProps.children ?? `${priceReadable} / ${priceFrequency}`}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export const allPlansLink = style({
|
|||||||
|
|
||||||
export const collapsibleHeader = style({
|
export const collapsibleHeader = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
alignItems: 'start',
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
});
|
});
|
||||||
export const collapsibleHeaderContent = style({
|
export const collapsibleHeaderContent = style({
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const PricingCollapsible = ({
|
|||||||
<div className={styles.collapsibleHeaderTitle}>{title}</div>
|
<div className={styles.collapsibleHeaderTitle}>{title}</div>
|
||||||
<div className={styles.collapsibleHeaderCaption}>{caption}</div>
|
<div className={styles.collapsibleHeaderCaption}>{caption}</div>
|
||||||
</div>
|
</div>
|
||||||
<IconButton onClick={toggle}>
|
<IconButton onClick={toggle} size="20">
|
||||||
<ArrowUpSmallIcon
|
<ArrowUpSmallIcon
|
||||||
style={{
|
style={{
|
||||||
transform: open ? 'rotate(0deg)' : 'rotate(180deg)',
|
transform: open ? 'rotate(0deg)' : 'rotate(180deg)',
|
||||||
@@ -163,7 +163,7 @@ export const PlanLayout = ({ cloud, ai, cloudTip }: PlanLayoutProps) => {
|
|||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => scrollToAnchor('cloudPricingPlan')}
|
onClick={() => scrollToAnchor('cloudPricingPlan')}
|
||||||
type="primary"
|
variant="primary"
|
||||||
>
|
>
|
||||||
{t['com.affine.ai-scroll-tip.view']()}
|
{t['com.affine.ai-scroll-tip.view']()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const ConfirmLoadingModal = ({
|
|||||||
cancelText={cancelText}
|
cancelText={cancelText}
|
||||||
confirmText={confirmText}
|
confirmText={confirmText}
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
loading,
|
loading,
|
||||||
}}
|
}}
|
||||||
open={open}
|
open={open}
|
||||||
@@ -120,7 +120,7 @@ export const DowngradeModal = ({
|
|||||||
<Button
|
<Button
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
onClick={() => onOpenChange?.(false)}
|
onClick={() => onOpenChange?.(false)}
|
||||||
type="primary"
|
variant="primary"
|
||||||
>
|
>
|
||||||
{t['com.affine.payment.modal.downgrade.confirm']()}
|
{t['com.affine.payment.modal.downgrade.confirm']()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Button } from '@affine/component/ui/button';
|
import { Button, type ButtonProps } from '@affine/component/ui/button';
|
||||||
import { Tooltip } from '@affine/component/ui/tooltip';
|
import { Tooltip } from '@affine/component/ui/tooltip';
|
||||||
import { generateSubscriptionCallbackLink } from '@affine/core/hooks/affine/use-subscription-notify';
|
import { generateSubscriptionCallbackLink } from '@affine/core/hooks/affine/use-subscription-notify';
|
||||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||||
@@ -10,10 +10,11 @@ import { SubscriptionPlan, SubscriptionStatus } from '@affine/graphql';
|
|||||||
import { Trans, useI18n } from '@affine/i18n';
|
import { Trans, useI18n } from '@affine/i18n';
|
||||||
import { DoneIcon } from '@blocksuite/icons/rc';
|
import { DoneIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
|
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import type { HTMLAttributes, PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { authAtom } from '../../../../../atoms/index';
|
import { authAtom } from '../../../../../atoms/index';
|
||||||
@@ -194,7 +195,7 @@ const Downgrade = ({ disabled }: { disabled?: boolean }) => {
|
|||||||
<div className={styles.planAction}>
|
<div className={styles.planAction}>
|
||||||
<Button
|
<Button
|
||||||
className={styles.planAction}
|
className={styles.planAction}
|
||||||
type="primary"
|
variant="primary"
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
@@ -232,7 +233,7 @@ const BookDemo = ({ plan }: { plan: SubscriptionPlan }) => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button className={styles.planAction} type="primary">
|
<Button className={styles.planAction} variant="primary">
|
||||||
{t['com.affine.payment.tell-us-use-case']()}
|
{t['com.affine.payment.tell-us-use-case']()}
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
@@ -243,8 +244,8 @@ export const Upgrade = ({
|
|||||||
className,
|
className,
|
||||||
recurring,
|
recurring,
|
||||||
children,
|
children,
|
||||||
...attrs
|
...btnProps
|
||||||
}: HTMLAttributes<HTMLButtonElement> & {
|
}: ButtonProps & {
|
||||||
recurring: SubscriptionRecurring;
|
recurring: SubscriptionRecurring;
|
||||||
}) => {
|
}) => {
|
||||||
const [isMutating, setMutating] = useState(false);
|
const [isMutating, setMutating] = useState(false);
|
||||||
@@ -307,11 +308,11 @@ export const Upgrade = ({
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
className={clsx(styles.planAction, className)}
|
className={clsx(styles.planAction, className)}
|
||||||
type="primary"
|
variant="primary"
|
||||||
onClick={upgrade}
|
onClick={upgrade}
|
||||||
disabled={isMutating}
|
disabled={isMutating}
|
||||||
loading={isMutating}
|
loading={isMutating}
|
||||||
{...attrs}
|
{...btnProps}
|
||||||
>
|
>
|
||||||
{children ?? t['com.affine.payment.upgrade']()}
|
{children ?? t['com.affine.payment.upgrade']()}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -371,7 +372,7 @@ const ChangeRecurring = ({
|
|||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
className={styles.planAction}
|
className={styles.planAction}
|
||||||
type="primary"
|
variant="primary"
|
||||||
onClick={onStartChange}
|
onClick={onStartChange}
|
||||||
disabled={disabled || isMutating}
|
disabled={disabled || isMutating}
|
||||||
loading={isMutating}
|
loading={isMutating}
|
||||||
@@ -405,7 +406,7 @@ const SignUpAction = ({ children }: PropsWithChildren) => {
|
|||||||
<Button
|
<Button
|
||||||
onClick={onClickSignIn}
|
onClick={onClickSignIn}
|
||||||
className={styles.planAction}
|
className={styles.planAction}
|
||||||
type="primary"
|
variant="primary"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -415,7 +416,6 @@ const SignUpAction = ({ children }: PropsWithChildren) => {
|
|||||||
const ResumeButton = () => {
|
const ResumeButton = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [hovered, setHovered] = useState(false);
|
|
||||||
const subscription = useService(SubscriptionService).subscription;
|
const subscription = useService(SubscriptionService).subscription;
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
@@ -435,14 +435,14 @@ const ResumeButton = () => {
|
|||||||
return (
|
return (
|
||||||
<ResumeAction open={open} onOpenChange={setOpen}>
|
<ResumeAction open={open} onOpenChange={setOpen}>
|
||||||
<Button
|
<Button
|
||||||
className={styles.planAction}
|
className={styles.resumeAction}
|
||||||
onMouseEnter={() => setHovered(true)}
|
|
||||||
onMouseLeave={() => setHovered(false)}
|
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
style={assignInlineVars({
|
||||||
|
'--default-content': t['com.affine.payment.current-plan'](),
|
||||||
|
'--hover-content': t['com.affine.payment.resume-renewal'](),
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{hovered
|
<span className={styles.resumeActionContent} />
|
||||||
? t['com.affine.payment.resume-renewal']()
|
|
||||||
: t['com.affine.payment.current-plan']()}
|
|
||||||
</Button>
|
</Button>
|
||||||
</ResumeAction>
|
</ResumeAction>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -177,7 +177,17 @@ export const planPriceDesc = style({
|
|||||||
});
|
});
|
||||||
export const planAction = style({
|
export const planAction = style({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
fontWeight: 500,
|
});
|
||||||
|
export const resumeAction = style([planAction, {}]);
|
||||||
|
export const resumeActionContent = style({
|
||||||
|
':after': {
|
||||||
|
content: 'var(--default-content)',
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
[`${resumeAction}:hover &:after`]: {
|
||||||
|
content: 'var(--hover-content)',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
export const planBenefits = style({
|
export const planBenefits = style({
|
||||||
fontSize: cssVar('fontXs'),
|
fontSize: cssVar('fontXs'),
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export const WorkspaceDeleteModal = ({
|
|||||||
cancelText={t['com.affine.workspaceDelete.button.cancel']()}
|
cancelText={t['com.affine.workspaceDelete.button.cancel']()}
|
||||||
confirmText={t['com.affine.workspaceDelete.button.delete']()}
|
confirmText={t['com.affine.workspaceDelete.button.delete']()}
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
type: 'error',
|
variant: 'error',
|
||||||
disabled: !allowDelete,
|
disabled: !allowDelete,
|
||||||
['data-testid' as string]: 'delete-workspace-confirm-button',
|
['data-testid' as string]: 'delete-workspace-confirm-button',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export const DeleteLeaveWorkspace = () => {
|
|||||||
description={t['com.affine.deleteLeaveWorkspace.leaveDescription']()}
|
description={t['com.affine.deleteLeaveWorkspace.leaveDescription']()}
|
||||||
confirmText={t['Leave']()}
|
confirmText={t['Leave']()}
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
type: 'warning',
|
variant: 'error',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export const EnableCloudPanel = () => {
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
data-testid="publish-enable-affine-cloud-button"
|
data-testid="publish-enable-affine-cloud-button"
|
||||||
type="primary"
|
variant="primary"
|
||||||
onClick={confirmEnableCloudAndClose}
|
onClick={confirmEnableCloudAndClose}
|
||||||
style={{ marginTop: '12px' }}
|
style={{ marginTop: '12px' }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const MembersPanelLocal = () => {
|
|||||||
<Tooltip content={t['com.affine.settings.member-tooltip']()}>
|
<Tooltip content={t['com.affine.settings.member-tooltip']()}>
|
||||||
<div className={style.fakeWrapper}>
|
<div className={style.fakeWrapper}>
|
||||||
<SettingRow name={`${t['Members']()} (0)`} desc={t['Members hint']()}>
|
<SettingRow name={`${t['Members']()} (0)`} desc={t['Members hint']()}>
|
||||||
<Button size="large">{t['Invite Members']()}</Button>
|
<Button>{t['Invite Members']()}</Button>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -393,7 +393,6 @@ const MemberItem = ({
|
|||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={!operationButtonInfo.show}
|
disabled={!operationButtonInfo.show}
|
||||||
type="plain"
|
|
||||||
style={{
|
style={{
|
||||||
visibility: operationButtonInfo.show ? 'visible' : 'hidden',
|
visibility: operationButtonInfo.show ? 'visible' : 'hidden',
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
|
|||||||
@@ -173,11 +173,9 @@ const EditPropertyButton = ({
|
|||||||
}}
|
}}
|
||||||
items={editing ? editMenuItems : defaultMenuItems}
|
items={editing ? editMenuItems : defaultMenuItems}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton onClick={() => setOpen(true)} size="20">
|
||||||
onClick={() => setOpen(true)}
|
<MoreHorizontalIcon />
|
||||||
type="plain"
|
</IconButton>
|
||||||
icon={<MoreHorizontalIcon />}
|
|
||||||
/>
|
|
||||||
</Menu>
|
</Menu>
|
||||||
<ConfirmDeletePropertyModal
|
<ConfirmDeletePropertyModal
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
@@ -350,7 +348,7 @@ const WorkspaceSettingPropertiesMain = () => {
|
|||||||
<div className={styles.listHeader}>
|
<div className={styles.listHeader}>
|
||||||
{properties.length > 0 ? (
|
{properties.length > 0 ? (
|
||||||
<Menu items={filterMenuItems}>
|
<Menu items={filterMenuItems}>
|
||||||
<Button type="default" icon={<FilterIcon />}>
|
<Button prefix={<FilterIcon />}>
|
||||||
{filterMode === 'all'
|
{filterMode === 'all'
|
||||||
? t['com.affine.filter']()
|
? t['com.affine.filter']()
|
||||||
: t[`com.affine.settings.workspace.properties.${filterMode}`]()}
|
: t[`com.affine.settings.workspace.properties.${filterMode}`]()}
|
||||||
@@ -365,7 +363,7 @@ const WorkspaceSettingPropertiesMain = () => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button type="primary">
|
<Button variant="primary">
|
||||||
{t['com.affine.settings.workspace.properties.add_property']()}
|
{t['com.affine.settings.workspace.properties.add_property']()}
|
||||||
</Button>
|
</Button>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const DefaultShareButton = forwardRef(function DefaultShareButton(
|
|||||||
}, [shareService]);
|
}, [shareService]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button ref={ref} className={styles.shareButton} type="primary">
|
<Button ref={ref} className={styles.shareButton} variant="primary">
|
||||||
{shared
|
{shared
|
||||||
? t['com.affine.share-menu.sharedButton']()
|
? t['com.affine.share-menu.sharedButton']()
|
||||||
: t['com.affine.share-menu.shareButton']()}
|
: t['com.affine.share-menu.shareButton']()}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const LocalSharePage = (props: ShareMenuProps) => {
|
|||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
onClick={props.onEnableAffineCloud}
|
onClick={props.onEnableAffineCloud}
|
||||||
type="primary"
|
variant="primary"
|
||||||
data-testid="share-menu-enable-affine-cloud-button"
|
data-testid="share-menu-enable-affine-cloud-button"
|
||||||
>
|
>
|
||||||
{t['Enable AFFiNE Cloud']()}
|
{t['Enable AFFiNE Cloud']()}
|
||||||
@@ -256,7 +256,7 @@ export const AffineSharePage = (props: ShareMenuProps) => {
|
|||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
onClick={onClickCreateLink}
|
onClick={onClickCreateLink}
|
||||||
type="primary"
|
variant="primary"
|
||||||
data-testid="share-menu-create-link-button"
|
data-testid="share-menu-create-link-button"
|
||||||
style={{ padding: '4px 12px', whiteSpace: 'nowrap' }}
|
style={{ padding: '4px 12px', whiteSpace: 'nowrap' }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const SignOutModal = ({ ...props }: ConfirmModalProps) => {
|
|||||||
cancelText={cancelText ?? defaultTexts.cancelText}
|
cancelText={cancelText ?? defaultTexts.cancelText}
|
||||||
confirmText={confirmText ?? defaultTexts.children}
|
confirmText={confirmText ?? defaultTexts.children}
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
type: 'error',
|
variant: 'error',
|
||||||
['data-testid' as string]: 'confirm-sign-out-button',
|
['data-testid' as string]: 'confirm-sign-out-button',
|
||||||
}}
|
}}
|
||||||
contentOptions={{
|
contentOptions={{
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const StarAFFiNEModal = () => {
|
|||||||
cancelText={t['com.affine.star-affine.cancel']()}
|
cancelText={t['com.affine.star-affine.cancel']()}
|
||||||
to={runtimeConfig.githubUrl}
|
to={runtimeConfig.githubUrl}
|
||||||
confirmButtonOptions={{
|
confirmButtonOptions={{
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
}}
|
}}
|
||||||
confirmText={t['com.affine.star-affine.confirm']()}
|
confirmText={t['com.affine.star-affine.confirm']()}
|
||||||
external
|
external
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const UpgradeSuccessLayout = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthPageContainer title={title} subtitle={subtitle}>
|
<AuthPageContainer title={title} subtitle={subtitle}>
|
||||||
<Button type="primary" size="extraLarge" onClick={openAffine}>
|
<Button variant="primary" size="extraLarge" onClick={openAffine}>
|
||||||
{t['com.affine.other-page.nav.open-affine']()}
|
{t['com.affine.other-page.nav.open-affine']()}
|
||||||
</Button>
|
</Button>
|
||||||
</AuthPageContainer>
|
</AuthPageContainer>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const SubscriptionChangedNotifyFooter = ({
|
|||||||
className={clsx(actionButton, cancelButton)}
|
className={clsx(actionButton, cancelButton)}
|
||||||
size={'default'}
|
size={'default'}
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
type="plain"
|
variant="plain"
|
||||||
>
|
>
|
||||||
{cancelText}
|
{cancelText}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -40,7 +40,7 @@ const SubscriptionChangedNotifyFooter = ({
|
|||||||
<Button
|
<Button
|
||||||
onClick={onConfirm}
|
onClick={onConfirm}
|
||||||
className={clsx(actionButton, confirmButton)}
|
className={clsx(actionButton, confirmButton)}
|
||||||
type="plain"
|
variant="plain"
|
||||||
>
|
>
|
||||||
{okText}
|
{okText}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -6,11 +6,7 @@ export const root = style({
|
|||||||
height: 32,
|
height: 32,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
boxShadow: '0px 1px 2px 0px rgba(0, 0, 0, 0.15)',
|
boxShadow: '0px 1px 2px 0px rgba(0, 0, 0, 0.15)',
|
||||||
border: `1px solid ${cssVarV2('layer/border')}`,
|
borderWidth: 1,
|
||||||
|
borderColor: cssVarV2('layer/border'),
|
||||||
background: cssVarV2('button/siderbarPrimary/background'),
|
background: cssVarV2('button/siderbarPrimary/background'),
|
||||||
});
|
});
|
||||||
export const icon = style({
|
|
||||||
color: cssVarV2('icon/primary'),
|
|
||||||
fontSize: 20,
|
|
||||||
display: 'block',
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Button, Tooltip } from '@affine/component';
|
import { IconButton } from '@affine/component';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
@@ -12,6 +12,7 @@ interface AddPageButtonProps {
|
|||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sideBottom = { side: 'bottom' as const };
|
||||||
export function AddPageButton({
|
export function AddPageButton({
|
||||||
onClick,
|
onClick,
|
||||||
className,
|
className,
|
||||||
@@ -20,15 +21,15 @@ export function AddPageButton({
|
|||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content={t['New Page']()} side="bottom">
|
<IconButton
|
||||||
<Button
|
tooltip={t['New Page']()}
|
||||||
data-testid="sidebar-new-page-button"
|
tooltipOptions={sideBottom}
|
||||||
style={style}
|
data-testid="sidebar-new-page-button"
|
||||||
className={clsx([styles.root, className])}
|
style={style}
|
||||||
onClick={onClick}
|
className={clsx([styles.root, className])}
|
||||||
>
|
onClick={onClick}
|
||||||
<PlusIcon className={styles.icon} />
|
>
|
||||||
</Button>
|
<PlusIcon />
|
||||||
</Tooltip>
|
</IconButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,25 +34,20 @@ export const label = style({
|
|||||||
lineHeight: '20px',
|
lineHeight: '20px',
|
||||||
flexGrow: '0',
|
flexGrow: '0',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
gap: 2,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'start',
|
justifyContent: 'start',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
});
|
});
|
||||||
|
|
||||||
export const collapseButton = style({
|
|
||||||
selectors: {
|
|
||||||
[`${label} > &`]: {
|
|
||||||
color: cssVarV2('icon/tertiary'),
|
|
||||||
transform: 'translateY(1px)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
export const collapseIcon = style({
|
export const collapseIcon = style({
|
||||||
transform: 'rotate(90deg)',
|
vars: { '--y': '1px', '--r': '90deg' },
|
||||||
|
color: cssVarV2('icon/tertiary'),
|
||||||
|
transform: 'translateY(var(--y)) rotate(var(--r))',
|
||||||
transition: 'transform 0.2s',
|
transition: 'transform 0.2s',
|
||||||
selectors: {
|
selectors: {
|
||||||
[`${root}[data-collapsed="true"] &`]: {
|
[`${root}[data-collapsed="true"] &`]: {
|
||||||
transform: 'rotate(0deg)',
|
vars: { '--r': '0deg' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { IconButton } from '@affine/component';
|
|
||||||
import { ToggleCollapseIcon } from '@blocksuite/icons/rc';
|
import { ToggleCollapseIcon } from '@blocksuite/icons/rc';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { type ForwardedRef, forwardRef, type PropsWithChildren } from 'react';
|
import { type ForwardedRef, forwardRef, type PropsWithChildren } from 'react';
|
||||||
@@ -42,14 +41,12 @@ export const CategoryDivider = forwardRef(
|
|||||||
<div className={styles.label}>
|
<div className={styles.label}>
|
||||||
{label}
|
{label}
|
||||||
{collapsible ? (
|
{collapsible ? (
|
||||||
<IconButton
|
<ToggleCollapseIcon
|
||||||
withoutHoverStyle
|
width={16}
|
||||||
className={styles.collapseButton}
|
height={16}
|
||||||
size="small"
|
|
||||||
data-testid="category-divider-collapse-button"
|
data-testid="category-divider-collapse-button"
|
||||||
>
|
className={styles.collapseIcon}
|
||||||
<ToggleCollapseIcon className={styles.collapseIcon} />
|
/>
|
||||||
</IconButton>
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.actions} onClick={e => e.stopPropagation()}>
|
<div className={styles.actions} onClick={e => e.stopPropagation()}>
|
||||||
|
|||||||
@@ -1,23 +1,18 @@
|
|||||||
import { style } from '@vanilla-extract/css';
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
export const sidebarSwitch = style({
|
export const sidebarSwitchClip = style({
|
||||||
opacity: 0,
|
flexShrink: 0,
|
||||||
display: 'inline-flex',
|
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
pointerEvents: 'none',
|
transition:
|
||||||
transition: 'max-width 0.2s ease-in-out, margin 0.3s ease-in-out',
|
'max-width 0.2s ease-in-out, margin 0.3s ease-in-out, opacity 0.3s ease',
|
||||||
selectors: {
|
selectors: {
|
||||||
'&[data-show=true]': {
|
'&[data-show=true]': {
|
||||||
maxWidth: '32px',
|
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
width: '32px',
|
maxWidth: '60px',
|
||||||
flexShrink: 0,
|
|
||||||
fontSize: '24px',
|
|
||||||
pointerEvents: 'auto',
|
|
||||||
},
|
},
|
||||||
'&[data-show=false]': {
|
'&[data-show=false]': {
|
||||||
|
opacity: 0,
|
||||||
maxWidth: 0,
|
maxWidth: 0,
|
||||||
margin: '0 !important',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { IconButton, Tooltip } from '@affine/component';
|
import { IconButton } from '@affine/component';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { SidebarIcon } from '@blocksuite/icons/rc';
|
import { SidebarIcon } from '@blocksuite/icons/rc';
|
||||||
import clsx from 'clsx';
|
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
|
|
||||||
import { appSidebarOpenAtom } from '../index.jotai';
|
import { appSidebarOpenAtom } from '../index.jotai';
|
||||||
@@ -19,19 +18,21 @@ export const SidebarSwitch = ({
|
|||||||
const tooltipContent = open
|
const tooltipContent = open
|
||||||
? t['com.affine.sidebarSwitch.collapse']()
|
? t['com.affine.sidebarSwitch.collapse']()
|
||||||
: t['com.affine.sidebarSwitch.expand']();
|
: t['com.affine.sidebarSwitch.expand']();
|
||||||
|
// TODO(@CatsJuice): Tooltip shortcut style
|
||||||
const collapseKeyboardShortcuts =
|
const collapseKeyboardShortcuts =
|
||||||
environment.isBrowser && environment.isMacOs ? ' ⌘+/' : ' Ctrl+/';
|
environment.isBrowser && environment.isMacOs ? ' ⌘+/' : ' Ctrl+/';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<div
|
||||||
content={tooltipContent + ' ' + collapseKeyboardShortcuts}
|
data-show={show}
|
||||||
side={open ? 'bottom' : 'right'}
|
className={styles.sidebarSwitchClip}
|
||||||
|
data-testid={`app-sidebar-arrow-button-${open ? 'collapse' : 'expand'}`}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
className={clsx(styles.sidebarSwitch, className)}
|
tooltip={tooltipContent + ' ' + collapseKeyboardShortcuts}
|
||||||
data-show={show}
|
tooltipOptions={{ side: open ? 'bottom' : 'right' }}
|
||||||
size="large"
|
className={className}
|
||||||
data-testid={`app-sidebar-arrow-button-${open ? 'collapse' : 'expand'}`}
|
size="24"
|
||||||
style={{
|
style={{
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
}}
|
}}
|
||||||
@@ -39,6 +40,6 @@ export const SidebarSwitch = ({
|
|||||||
>
|
>
|
||||||
<SidebarIcon />
|
<SidebarIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export function patchNotificationService(
|
|||||||
description: toReactNode(message),
|
description: toReactNode(message),
|
||||||
confirmText,
|
confirmText,
|
||||||
confirmButtonOptions: {
|
confirmButtonOptions: {
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
},
|
},
|
||||||
cancelText,
|
cancelText,
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
@@ -177,7 +177,7 @@ export function patchNotificationService(
|
|||||||
description: description,
|
description: description,
|
||||||
confirmText: confirmText ?? 'Confirm',
|
confirmText: confirmText ?? 'Confirm',
|
||||||
confirmButtonOptions: {
|
confirmButtonOptions: {
|
||||||
type: 'primary',
|
variant: 'primary',
|
||||||
},
|
},
|
||||||
cancelText: cancelText ?? 'Cancel',
|
cancelText: cancelText ?? 'Cancel',
|
||||||
onConfirm: () => {
|
onConfirm: () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IconButton, Tooltip } from '@affine/component';
|
import { IconButton } from '@affine/component';
|
||||||
import { openInfoModalAtom } from '@affine/core/atoms';
|
import { openInfoModalAtom } from '@affine/core/atoms';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { InformationIcon } from '@blocksuite/icons/rc';
|
import { InformationIcon } from '@blocksuite/icons/rc';
|
||||||
@@ -11,12 +11,13 @@ export const InfoButton = () => {
|
|||||||
setOpenInfoModal(true);
|
setOpenInfoModal(true);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Tooltip content={t['com.affine.page-properties.page-info.view']()}>
|
<IconButton
|
||||||
<IconButton
|
size="20"
|
||||||
data-testid="header-info-button"
|
tooltip={t['com.affine.page-properties.page-info.view']()}
|
||||||
onClick={onOpenInfoModal}
|
data-testid="header-info-button"
|
||||||
icon={<InformationIcon />}
|
onClick={onOpenInfoModal}
|
||||||
/>
|
>
|
||||||
</Tooltip>
|
<InformationIcon />
|
||||||
|
</IconButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ export const DetailPageHeaderPresentButton = () => {
|
|||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
style={{ flexShrink: 0 }}
|
style={{ flexShrink: 0 }}
|
||||||
size={'large'}
|
size="24"
|
||||||
icon={<PresentationIcon />}
|
|
||||||
onClick={() => handlePresent(!isPresent)}
|
onClick={() => handlePresent(!isPresent)}
|
||||||
></IconButton>
|
>
|
||||||
|
<PresentationIcon />
|
||||||
|
</IconButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,11 +11,10 @@ export const PresentButton = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
icon={<PresentationIcon />}
|
prefix={<PresentationIcon />}
|
||||||
className={styles.presentButton}
|
className={styles.presentButton}
|
||||||
onClick={() => handlePresent()}
|
onClick={() => handlePresent()}
|
||||||
disabled={isPresent}
|
disabled={isPresent}
|
||||||
withoutHoverStyle
|
|
||||||
>
|
>
|
||||||
{t['com.affine.share-page.header.present']()}
|
{t['com.affine.share-page.header.present']()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ export const headerDivider = style({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const presentButton = style({
|
export const presentButton = style({
|
||||||
gap: '4px',
|
|
||||||
background: cssVar('black'),
|
background: cssVar('black'),
|
||||||
color: cssVar('white'),
|
color: cssVar('white'),
|
||||||
borderColor: cssVar('pureBlack10'),
|
borderColor: cssVar('pureBlack10'),
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { IconButtonProps } from '@affine/component';
|
import type { IconButtonProps } from '@affine/component';
|
||||||
import { IconButton, Tooltip } from '@affine/component';
|
import { IconButton } from '@affine/component';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { FavoritedIcon, FavoriteIcon } from '@blocksuite/icons/rc';
|
import { FavoritedIcon, FavoriteIcon } from '@blocksuite/icons/rc';
|
||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
import Lottie from 'lottie-react';
|
import Lottie from 'lottie-react';
|
||||||
import { forwardRef, useCallback, useState } from 'react';
|
import { forwardRef, useCallback, useState } from 'react';
|
||||||
|
|
||||||
@@ -25,24 +26,32 @@ export const FavoriteTag = forwardRef<
|
|||||||
[active, onClick]
|
[active, onClick]
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Tooltip content={active ? t['Favorited']() : t['Favorite']()} side="top">
|
<IconButton
|
||||||
<IconButton ref={ref} active={active} onClick={handleClick} {...props}>
|
tooltip={active ? t['Favorited']() : t['Favorite']()}
|
||||||
{active ? (
|
tooltipOptions={{ side: 'top' }}
|
||||||
playAnimation ? (
|
ref={ref}
|
||||||
<Lottie
|
onClick={handleClick}
|
||||||
loop={false}
|
size="20"
|
||||||
animationData={favoritedAnimation}
|
{...props}
|
||||||
onComplete={() => setPlayAnimation(false)}
|
>
|
||||||
style={{ width: '20px', height: '20px' }}
|
{active ? (
|
||||||
/>
|
playAnimation ? (
|
||||||
) : (
|
<Lottie
|
||||||
<FavoritedIcon data-testid="favorited-icon" />
|
loop={false}
|
||||||
)
|
animationData={favoritedAnimation}
|
||||||
|
onComplete={() => setPlayAnimation(false)}
|
||||||
|
style={{ width: '20px', height: '20px' }}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<FavoriteIcon />
|
<FavoritedIcon
|
||||||
)}
|
color={cssVar('primaryColor')}
|
||||||
</IconButton>
|
data-testid="favorited-icon"
|
||||||
</Tooltip>
|
/>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<FavoriteIcon />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
FavoriteTag.displayName = 'FavoriteTag';
|
FavoriteTag.displayName = 'FavoriteTag';
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user