feat: refactor button with new design (#3343)

This commit is contained in:
Qi 2023-07-21 19:07:28 +08:00 committed by GitHub
parent a4f60f22cf
commit 439ef1ba90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 665 additions and 473 deletions

View File

@ -79,7 +79,7 @@ const NameWorkspaceContent = ({
<div className={style.buttonGroup}> <div className={style.buttonGroup}>
<Button <Button
data-testid="create-workspace-close-button" data-testid="create-workspace-close-button"
type="light" type="primary"
onClick={onClose} onClick={onClose}
> >
{t.Cancel()} {t.Cancel()}
@ -155,7 +155,7 @@ const SetDBLocationContent = ({
<Button <Button
disabled={opening} disabled={opening}
data-testid="create-workspace-customize-button" data-testid="create-workspace-customize-button"
type="light" type="primary"
onClick={handleSelectDBFileLocation} onClick={handleSelectDBFileLocation}
> >
{t['Customize']()} {t['Customize']()}

View File

@ -47,7 +47,7 @@ const LanguageMenuContent: FC<{
</> </>
); );
}; };
export const LanguageMenu: FC<{ triggerProps: ButtonProps }> = ({ export const LanguageMenu: FC<{ triggerProps?: ButtonProps }> = ({
triggerProps, triggerProps,
}) => { }) => {
const i18n = useI18N(); const i18n = useI18N();

View File

@ -99,7 +99,7 @@ export const WorkspaceDeleteModal = ({
data-testid="delete-workspace-confirm-button" data-testid="delete-workspace-confirm-button"
disabled={!allowDelete} disabled={!allowDelete}
onClick={handleDelete} onClick={handleDelete}
type="danger" type="error"
shape="circle" shape="circle"
style={{ marginLeft: '24px' }} style={{ marginLeft: '24px' }}
> >

View File

@ -37,7 +37,7 @@ export const WorkspaceLeave = ({ open, onClose }: WorkspaceDeleteProps) => {
</Button> </Button>
<Button <Button
onClick={handleLeave} onClick={handleLeave}
type="danger" type="error"
shape="circle" shape="circle"
style={{ marginLeft: '24px' }} style={{ marginLeft: '24px' }}
> >

View File

@ -14,7 +14,6 @@ export const ExportPanel: FC<{
<> <>
<SettingRow name={t['Export']()} desc={t['Export Description']()}> <SettingRow name={t['Export']()} desc={t['Export Description']()}>
<Button <Button
size="small"
data-testid="export-affine-backup" data-testid="export-affine-backup"
onClick={async () => { onClick={async () => {
const result = await window.apis?.dialog.saveDBFileAs(workspaceId); const result = await window.apis?.dialog.saveDBFileAs(workspaceId);

View File

@ -84,13 +84,12 @@ export const ProfilePanel: FC<{
/> />
{input === workspace.blockSuiteWorkspace.meta.name ? null : ( {input === workspace.blockSuiteWorkspace.meta.name ? null : (
<IconButton <IconButton
size="middle"
data-testid="save-workspace-name" data-testid="save-workspace-name"
onClick={() => { onClick={() => {
handleUpdateWorkspaceName(input); handleUpdateWorkspaceName(input);
}} }}
active={true}
style={{ style={{
color: 'var(--affine-primary-color)',
marginLeft: '12px', marginLeft: '12px',
}} }}
> >

View File

@ -74,7 +74,7 @@ const PublishPanelAffine: FC<PublishPanelAffineProps> = props => {
<FlexWrapper justifyContent="space-between"> <FlexWrapper justifyContent="space-between">
<Button <Button
className={style.urlButton} className={style.urlButton}
size="middle" size="large"
onClick={useCallback(() => { onClick={useCallback(() => {
window.open(shareUrl, '_blank'); window.open(shareUrl, '_blank');
}, [shareUrl])} }, [shareUrl])}
@ -82,7 +82,7 @@ const PublishPanelAffine: FC<PublishPanelAffineProps> = props => {
> >
{shareUrl} {shareUrl}
</Button> </Button>
<Button size="middle" onClick={copyUrl}> <Button size="large" onClick={copyUrl}>
{t['Copy']()} {t['Copy']()}
</Button> </Button>
</FlexWrapper> </FlexWrapper>

View File

@ -79,14 +79,13 @@ export const StoragePanel: FC<{
<Button <Button
data-testid="move-folder" data-testid="move-folder"
className={style.urlButton} className={style.urlButton}
size="middle" size="large"
onClick={handleMoveTo} onClick={handleMoveTo}
> >
{secondaryPath} {secondaryPath}
</Button> </Button>
</Tooltip> </Tooltip>
<Button <Button
size="small"
data-testid="reveal-folder" data-testid="reveal-folder"
data-disabled={moveToInProgress} data-disabled={moveToInProgress}
onClick={onRevealDBFile} onClick={onRevealDBFile}
@ -96,7 +95,6 @@ export const StoragePanel: FC<{
</FlexWrapper> </FlexWrapper>
) : ( ) : (
<Button <Button
size="small"
data-testid="move-folder" data-testid="move-folder"
data-disabled={moveToInProgress} data-disabled={moveToInProgress}
onClick={handleMoveTo} onClick={handleMoveTo}

View File

@ -32,11 +32,7 @@ export const ThemeSettings = () => {
[setTheme] [setTheme]
)} )}
> >
<RadioButton <RadioButton value="system" data-testid="system-theme-trigger">
bold={true}
value="system"
data-testid="system-theme-trigger"
>
{t['system']()} {t['system']()}
</RadioButton> </RadioButton>
<RadioButton bold={true} value="light" data-testid="light-theme-trigger"> <RadioButton bold={true} value="light" data-testid="light-theme-trigger">
@ -117,7 +113,7 @@ export const AppearanceSettings = () => {
desc={t['Select the language for the interface.']()} desc={t['Select the language for the interface.']()}
> >
<div className={settingWrapper}> <div className={settingWrapper}>
<LanguageMenu triggerProps={{ size: 'small' }} /> <LanguageMenu />
</div> </div>
</SettingRow> </SettingRow>
{environment.isDesktop ? ( {environment.isDesktop ? (

View File

@ -45,7 +45,7 @@ const CommonMenu = () => {
disablePortal={true} disablePortal={true}
trigger="click" trigger="click"
> >
<IconButton data-testid="editor-option-menu" iconSize={[24, 24]}> <IconButton data-testid="editor-option-menu">
<MoreVerticalIcon /> <MoreVerticalIcon />
</IconButton> </IconButton>
</Menu> </Menu>
@ -138,7 +138,7 @@ const PageMenu = () => {
disablePortal={true} disablePortal={true}
trigger="click" trigger="click"
> >
<IconButton data-testid="editor-option-menu" iconSize={[24, 24]}> <IconButton data-testid="editor-option-menu">
<MoreVerticalIcon /> <MoreVerticalIcon />
</IconButton> </IconButton>
</Menu> </Menu>

View File

@ -51,13 +51,13 @@ export const LanguageMenu: React.FC = () => {
disablePortal={true} disablePortal={true}
> >
<StyledButton <StyledButton
type="plain"
icon={ icon={
<StyledArrowDownContainer> <StyledArrowDownContainer>
<ArrowDownSmallIcon /> <ArrowDownSmallIcon />
</StyledArrowDownContainer> </StyledArrowDownContainer>
} }
iconPosition="end" iconPosition="end"
noBorder={true}
data-testid="language-menu-button" data-testid="language-menu-button"
> >
<StyledCurrentLanguage> <StyledCurrentLanguage>

View File

@ -31,7 +31,6 @@ export const TrashButtonGroup = () => {
return ( return (
<> <>
<Button <Button
bold={true}
shape="round" shape="round"
style={{ marginRight: '24px' }} style={{ marginRight: '24px' }}
onClick={() => { onClick={() => {
@ -41,9 +40,8 @@ export const TrashButtonGroup = () => {
{t['Restore it']()} {t['Restore it']()}
</Button> </Button>
<Button <Button
bold={true}
shape="round" shape="round"
type="danger" type="error"
onClick={() => { onClick={() => {
setOpen(true); setOpen(true);
}} }}
@ -54,7 +52,7 @@ export const TrashButtonGroup = () => {
title={t['TrashButtonGroupTitle']()} title={t['TrashButtonGroupTitle']()}
content={t['TrashButtonGroupDescription']()} content={t['TrashButtonGroupDescription']()}
confirmText={t['Delete']()} confirmText={t['Delete']()}
confirmType="danger" confirmType="error"
open={open} open={open}
onConfirm={useCallback(() => { onConfirm={useCallback(() => {
jumpToSubPath(workspace.id, WorkspaceSubPath.ALL); jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);

View File

@ -14,8 +14,7 @@ export const Footer: FC = () => {
<StyledFooter data-testid="workspace-list-modal-footer"> <StyledFooter data-testid="workspace-list-modal-footer">
<StyledSignInButton <StyledSignInButton
data-testid="sign-in-button" data-testid="sign-in-button"
noBorder type="plain"
bold
icon={ icon={
<div className="circle"> <div className="circle">
<CloudWorkspaceIcon /> <CloudWorkspaceIcon />

View File

@ -50,8 +50,6 @@ export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
<ModalCloseButton <ModalCloseButton
top={6} top={6}
right={6} right={6}
size={[24, 24]}
iconSize={[15, 15]}
onClick={() => { onClick={() => {
onClose(); onClose();
}} }}

View File

@ -5,9 +5,9 @@ import { DropdownButton } from '@affine/component';
import { RadioButton, RadioButtonGroup } from '@affine/component'; import { RadioButton, RadioButtonGroup } from '@affine/component';
import { Menu } from '@affine/component'; import { Menu } from '@affine/component';
import { toast } from '@affine/component'; import { toast } from '@affine/component';
import { InformationIcon } from '@blocksuite/icons';
import type { Meta, StoryFn } from '@storybook/react'; import type { Meta, StoryFn } from '@storybook/react';
import { useState } from 'react'; import { useState } from 'react';
export default { export default {
title: 'AFFiNE/Button', title: 'AFFiNE/Button',
component: Button, component: Button,
@ -22,8 +22,9 @@ const Template: StoryFn<ButtonProps> = args => <Button {...args} />;
export const Primary = Template.bind(undefined); export const Primary = Template.bind(undefined);
Primary.args = { Primary.args = {
type: 'primary', type: 'primary',
children: 'This is a primary button', children: 'Content',
onClick: () => toast('Click button'), onClick: () => toast('Click button'),
icon: <InformationIcon />,
}; };
export const Default = Template.bind(undefined); export const Default = Template.bind(undefined);
@ -35,7 +36,7 @@ Default.args = {
export const Light = Template.bind(undefined); export const Light = Template.bind(undefined);
Light.args = { Light.args = {
type: 'light', type: 'error',
children: 'This is a light button', children: 'This is a light button',
onClick: () => toast('Click button'), onClick: () => toast('Click button'),
}; };
@ -49,7 +50,7 @@ Warning.args = {
export const Danger = Template.bind(undefined); export const Danger = Template.bind(undefined);
Danger.args = { Danger.args = {
type: 'danger', type: 'success',
children: 'This is a danger button', children: 'This is a danger button',
onClick: () => toast('Click button'), onClick: () => toast('Click button'),
}; };

View File

@ -0,0 +1,25 @@
/* deepscan-disable USELESS_ARROW_FUNC_BIND */
import { IconButton, type IconButtonProps } from '@affine/component';
import { toast } from '@affine/component';
import { InformationIcon } from '@blocksuite/icons';
import type { Meta, StoryFn } from '@storybook/react';
export default {
title: 'AFFiNE/IconButton',
component: IconButton,
} as Meta<IconButtonProps>;
const IconButtonTemplate: StoryFn<IconButtonProps> = args => {
return (
<>
<h1>This is icon button</h1>
<IconButton {...args} />
</>
);
};
export const Icon = IconButtonTemplate.bind(undefined);
Icon.args = {
children: <InformationIcon />,
onClick: () => toast('Click button'),
withoutPadding: true,
};

View File

@ -23,7 +23,6 @@ export const SidebarHeader = (props: SidebarHeaderProps) => {
<> <>
{environment.isMacOs && <div style={{ flex: 1 }} />} {environment.isMacOs && <div style={{ flex: 1 }} />}
<IconButton <IconButton
size="middle"
data-testid="app-sidebar-arrow-button-back" data-testid="app-sidebar-arrow-button-back"
disabled={props.router?.history.current === 0} disabled={props.router?.history.current === 0}
onClick={() => { onClick={() => {
@ -33,7 +32,6 @@ export const SidebarHeader = (props: SidebarHeaderProps) => {
<ArrowLeftSmallIcon /> <ArrowLeftSmallIcon />
</IconButton> </IconButton>
<IconButton <IconButton
size="middle"
data-testid="app-sidebar-arrow-button-forward" data-testid="app-sidebar-arrow-button-forward"
disabled={ disabled={
props.router props.router

View File

@ -342,9 +342,8 @@ const ImagePreviewModalImpl = (
<Button <Button
data-testid="previous-image-button" data-testid="previous-image-button"
icon={<ArrowLeftSmallIcon className={buttonIconStyle} />} icon={<ArrowLeftSmallIcon className={buttonIconStyle} />}
noBorder={true} type="plain"
className={buttonStyle} className={buttonStyle}
hoverColor={'-moz-initial'}
onClick={() => { onClick={() => {
assertExists(blockId); assertExists(blockId);
previousImageHandler(blockId); previousImageHandler(blockId);
@ -355,9 +354,8 @@ const ImagePreviewModalImpl = (
<Button <Button
data-testid="next-image-button" data-testid="next-image-button"
icon={<ArrowRightSmallIcon className={buttonIconStyle} />} icon={<ArrowRightSmallIcon className={buttonIconStyle} />}
noBorder={true}
className={buttonStyle} className={buttonStyle}
hoverColor={'-moz-initial'} type="plain"
onClick={() => { onClick={() => {
assertExists(blockId); assertExists(blockId);
nextImageHandler(blockId); nextImageHandler(blockId);
@ -374,8 +372,7 @@ const ImagePreviewModalImpl = (
<Button <Button
data-testid="fit-to-screen-button" data-testid="fit-to-screen-button"
icon={<ViewBarIcon className={buttonIconStyle} />} icon={<ViewBarIcon className={buttonIconStyle} />}
noBorder={true} type="plain"
hoverColor={'-moz-initial'}
className={buttonStyle} className={buttonStyle}
onClick={() => resetZoom()} onClick={() => resetZoom()}
/> />
@ -384,19 +381,17 @@ const ImagePreviewModalImpl = (
<Button <Button
data-testid="zoom-out-button" data-testid="zoom-out-button"
icon={<MinusIcon className={buttonIconStyle} />} icon={<MinusIcon className={buttonIconStyle} />}
noBorder={true}
className={buttonStyle} className={buttonStyle}
hoverColor={'-moz-initial'} type="plain"
onClick={zoomOut} onClick={zoomOut}
/> />
</Tooltip> </Tooltip>
<Tooltip content={'Reset Scale'} disablePortal={false}> <Tooltip content={'Reset Scale'} disablePortal={false}>
<Button <Button
data-testid="reset-scale-button" data-testid="reset-scale-button"
noBorder={true} type="plain"
size={'middle'} size={'large'}
className={scaleIndicatorButtonStyle} className={scaleIndicatorButtonStyle}
hoverColor={'-moz-initial'}
onClick={resetScale} onClick={resetScale}
> >
{`${(currentScale * 100).toFixed(0)}%`} {`${(currentScale * 100).toFixed(0)}%`}
@ -406,9 +401,8 @@ const ImagePreviewModalImpl = (
<Button <Button
data-testid="zoom-in-button" data-testid="zoom-in-button"
icon={<PlusIcon className={buttonIconStyle} />} icon={<PlusIcon className={buttonIconStyle} />}
noBorder={true}
className={buttonStyle} className={buttonStyle}
hoverColor={'-moz-initial'} type="plain"
onClick={() => zoomIn()} onClick={() => zoomIn()}
/> />
</Tooltip> </Tooltip>
@ -417,9 +411,8 @@ const ImagePreviewModalImpl = (
<Button <Button
data-testid="download-button" data-testid="download-button"
icon={<DownloadIcon className={buttonIconStyle} />} icon={<DownloadIcon className={buttonIconStyle} />}
noBorder={true} type="plain"
className={buttonStyle} className={buttonStyle}
hoverColor={'-moz-initial'}
onClick={() => { onClick={() => {
assertExists(blockId); assertExists(blockId);
downloadHandler(blockId).catch(err => { downloadHandler(blockId).catch(err => {
@ -432,9 +425,8 @@ const ImagePreviewModalImpl = (
<Button <Button
data-testid="copy-to-clipboard-button" data-testid="copy-to-clipboard-button"
icon={<CopyIcon className={buttonIconStyle} />} icon={<CopyIcon className={buttonIconStyle} />}
noBorder={true} type="plain"
className={buttonStyle} className={buttonStyle}
hoverColor={'-moz-initial'}
onClick={() => { onClick={() => {
if (!imageRef.current) { if (!imageRef.current) {
return; return;
@ -474,10 +466,9 @@ const ImagePreviewModalImpl = (
<Button <Button
data-testid="delete-button" data-testid="delete-button"
icon={<DeleteIcon className={buttonIconStyle} />} icon={<DeleteIcon className={buttonIconStyle} />}
noBorder={true} type="plain"
className={buttonStyle} className={buttonStyle}
onClick={() => blockId && deleteHandler(blockId)} onClick={() => blockId && deleteHandler(blockId)}
hoverColor="var(--affine-error-color)"
/> />
</Tooltip> </Tooltip>
</div> </div>

View File

@ -19,12 +19,7 @@ export const FavoriteTag = forwardRef<
> >
<IconButton <IconButton
ref={ref} ref={ref}
iconSize={[20, 20]} active={active}
style={{
color: active
? 'var(--affine-primary-color)'
: 'var(--affine-icon-color)',
}}
onClick={e => { onClick={e => {
e.stopPropagation(); e.stopPropagation();
onClick?.(e); onClick?.(e);

View File

@ -147,8 +147,6 @@ export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
onClick={() => { onClick={() => {
setOpen(true); setOpen(true);
}} }}
hoverBackground="var(--affine-background-error-color)"
hoverColor="var(--affine-error-color)"
> >
<DeletePermanentlyIcon /> <DeletePermanentlyIcon />
</IconButton> </IconButton>
@ -157,7 +155,7 @@ export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
title={t['Delete permanently?']()} title={t['Delete permanently?']()}
content={t['TrashButtonGroupDescription']()} content={t['TrashButtonGroupDescription']()}
confirmText={t['Delete']()} confirmText={t['Delete']()}
confirmType="danger" confirmType="error"
open={open} open={open}
onConfirm={() => { onConfirm={() => {
onPermanentlyDeletePage(); onPermanentlyDeletePage();

View File

@ -44,7 +44,7 @@ const ConfirmModal = ({
title: title || 'Untitled', title: title || 'Untitled',
})} })}
confirmText={t.Delete()} confirmText={t.Delete()}
confirmType="danger" confirmType="error"
{...confirmModalProps} {...confirmModalProps}
/> />
); );

View File

@ -207,9 +207,7 @@ export const CollectionList = ({
} }
> >
<Button <Button
size="small"
className={clsx(styles.viewButton)} className={clsx(styles.viewButton)}
hoverColor="var(--affine-icon-color)"
data-testid="collection-select" data-testid="collection-select"
> >
<Tooltip <Tooltip
@ -235,8 +233,6 @@ export const CollectionList = ({
<Button <Button
icon={<FilteredIcon />} icon={<FilteredIcon />}
className={clsx(styles.filterButton)} className={clsx(styles.filterButton)}
size="small"
hoverColor="var(--affine-icon-color)"
data-testid="create-first-filter" data-testid="create-first-filter"
> >
{t['com.affine.filter']()} {t['com.affine.filter']()}

View File

@ -56,12 +56,7 @@ export const EditCollectionModel = ({
background: 'var(--affine-background-primary-color)', background: 'var(--affine-background-primary-color)',
}} }}
> >
<ModalCloseButton <ModalCloseButton top={12} right={12} onClick={onClose} />
top={12}
right={12}
onClick={onClose}
hoverColor="var(--affine-icon-color)"
/>
{init ? ( {init ? (
<EditCollection <EditCollection
propertiesMeta={propertiesMeta} propertiesMeta={propertiesMeta}
@ -283,7 +278,7 @@ export const SaveCollectionButton = ({
<Button <Button
className={styles.saveButton} className={styles.saveButton}
onClick={() => changeShow(true)} onClick={() => changeShow(true)}
size="middle" size="large"
data-testid="save-as-collection" data-testid="save-as-collection"
> >
<div className={styles.saveButtonContainer}> <div className={styles.saveButtonContainer}>

View File

@ -1,10 +1,9 @@
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Modal, ModalCloseButton } from '../../..'; import { Modal, ModalCloseButton } from '../../..';
import { Button } from '../../../ui/button';
import { import {
StyledButton,
StyledButtonContent, StyledButtonContent,
StyledDangerButton,
StyledModalHeader, StyledModalHeader,
StyledModalWrapper, StyledModalWrapper,
StyledTextContent, StyledTextContent,
@ -33,8 +32,9 @@ export const PublicLinkDisableModal = ({
</StyledTextContent> </StyledTextContent>
<StyledButtonContent> <StyledButtonContent>
<StyledButton onClick={onClose}>{t['Cancel']()}</StyledButton> <Button onClick={onClose}>{t['Cancel']()}</Button>
<StyledDangerButton <Button
type="error"
data-testid="disable-public-link-confirm-button" data-testid="disable-public-link-confirm-button"
onClick={() => { onClick={() => {
onConfirmDisable(); onConfirmDisable();
@ -43,7 +43,7 @@ export const PublicLinkDisableModal = ({
style={{ marginLeft: '24px' }} style={{ marginLeft: '24px' }}
> >
{t['Disable']()} {t['Disable']()}
</StyledDangerButton> </Button>
</StyledButtonContent> </StyledButtonContent>
</StyledModalWrapper> </StyledModalWrapper>
</Modal> </Modal>

View File

@ -1,4 +1,4 @@
import { styled, TextButton } from '../../..'; import { styled } from '../../..';
export const StyledModalWrapper = styled('div')(() => { export const StyledModalWrapper = styled('div')(() => {
return { return {
@ -40,24 +40,3 @@ export const StyledButtonContent = styled('div')(() => {
justifyContent: 'center', justifyContent: 'center',
}; };
}); });
export const StyledButton = styled(TextButton)(() => {
return {
color: 'var(--affine-primary-color)',
height: '32px',
background: '#F3F0FF',
border: 'none',
borderRadius: '8px',
padding: '4px 20px',
};
});
export const StyledDangerButton = styled(TextButton)(() => {
return {
color: '#FF631F',
height: '32px',
background:
'linear-gradient(0deg, rgba(255, 99, 31, 0.1), rgba(255, 99, 31, 0.1)), #FFFFFF;',
border: 'none',
borderRadius: '8px',
padding: '4px 20px',
};
});

View File

@ -9,12 +9,13 @@ import type { FC } from 'react';
import { useRef } from 'react'; import { useRef } from 'react';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { Button } from '../../ui/button';
import { Menu } from '../../ui/menu/menu'; import { Menu } from '../../ui/menu/menu';
import { Export } from './export'; import { Export } from './export';
import { containerStyle, indicatorContainerStyle, tabStyle } from './index.css'; import { containerStyle, indicatorContainerStyle, tabStyle } from './index.css';
import { SharePage } from './share-page'; import { SharePage } from './share-page';
import { ShareWorkspace } from './share-workspace'; import { ShareWorkspace } from './share-workspace';
import { StyledIndicator, StyledShareButton, TabItem } from './styles'; import { StyledIndicator, TabItem } from './styles';
type SharePanel = 'SharePage' | 'Export' | 'ShareWorkspace'; type SharePanel = 'SharePage' | 'Export' | 'ShareWorkspace';
const MenuItems: Record<SharePanel, FC<ShareMenuProps>> = { const MenuItems: Record<SharePanel, FC<ShareMenuProps>> = {
SharePage: SharePage, SharePage: SharePage,
@ -134,15 +135,15 @@ export const ShareMenu: FC<ShareMenuProps> = props => {
setOpen(false); setOpen(false);
}} }}
> >
<StyledShareButton <Button
data-testid="share-menu-button" data-testid="share-menu-button"
onClick={() => { onClick={() => {
setOpen(!open); setOpen(!open);
}} }}
isShared={isPublic} type={isPublic ? 'primary' : undefined}
> >
<div>{isPublic ? 'Shared' : 'Share'}</div> <div>{isPublic ? 'Shared' : 'Share'}</div>
</StyledShareButton> </Button>
</Menu> </Menu>
); );
}; };

View File

@ -7,7 +7,7 @@ import type { FC } from 'react';
import { useState } from 'react'; import { useState } from 'react';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { toast } from '../..'; import { Button, toast } from '../..';
import { PublicLinkDisableModal } from './disable-public-link'; import { PublicLinkDisableModal } from './disable-public-link';
import { import {
descriptionStyle, descriptionStyle,
@ -15,26 +15,22 @@ import {
menuItemStyle, menuItemStyle,
} from './index.css'; } from './index.css';
import type { ShareMenuProps } from './share-menu'; import type { ShareMenuProps } from './share-menu';
import { import { StyledDisableButton, StyledInput, StyledLinkSpan } from './styles';
StyledButton,
StyledDisableButton,
StyledInput,
StyledLinkSpan,
} from './styles';
export const LocalSharePage: FC<ShareMenuProps> = props => { export const LocalSharePage: FC<ShareMenuProps> = props => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
return ( return (
<div className={menuItemStyle}> <div className={menuItemStyle}>
<div className={descriptionStyle}>{t['Shared Pages Description']()}</div> <div className={descriptionStyle}>{t['Shared Pages Description']()}</div>
<StyledButton <Button
type="primary"
data-testid="share-menu-enable-affine-cloud-button" data-testid="share-menu-enable-affine-cloud-button"
onClick={() => { onClick={() => {
props.onEnableAffineCloud(props.workspace as LocalWorkspace); props.onEnableAffineCloud(props.workspace as LocalWorkspace);
}} }}
> >
{t['Enable AFFiNE Cloud']()} {t['Enable AFFiNE Cloud']()}
</StyledButton> </Button>
</div> </div>
); );
}; };
@ -74,20 +70,20 @@ export const AffineSharePage: FC<ShareMenuProps> = props => {
value={isPublic ? sharingUrl : 'https://app.affine.pro/xxxx'} value={isPublic ? sharingUrl : 'https://app.affine.pro/xxxx'}
/> />
{!isPublic && ( {!isPublic && (
<StyledButton <Button
data-testid="affine-share-create-link" data-testid="affine-share-create-link"
onClick={onClickCreateLink} onClick={onClickCreateLink}
> >
{t['Create']()} {t['Create']()}
</StyledButton> </Button>
)} )}
{isPublic && ( {isPublic && (
<StyledButton <Button
data-testid="affine-share-copy-link" data-testid="affine-share-copy-link"
onClick={onClickCopyLink} onClick={onClickCopyLink}
> >
{t['Copy Link']()} {t['Copy Link']()}
</StyledButton> </Button>
)} )}
</div> </div>
<div className={descriptionStyle}> <div className={descriptionStyle}>

View File

@ -6,9 +6,9 @@ import { WorkspaceFlavour } from '@affine/env/workspace';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { FC } from 'react'; import type { FC } from 'react';
import { Button } from '../../ui/button';
import { descriptionStyle, menuItemStyle } from './index.css'; import { descriptionStyle, menuItemStyle } from './index.css';
import type { ShareMenuProps } from './share-menu'; import type { ShareMenuProps } from './share-menu';
import { StyledButton } from './styles';
const ShareLocalWorkspace: FC<ShareMenuProps<LocalWorkspace>> = props => { const ShareLocalWorkspace: FC<ShareMenuProps<LocalWorkspace>> = props => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
@ -17,14 +17,14 @@ const ShareLocalWorkspace: FC<ShareMenuProps<LocalWorkspace>> = props => {
<div className={descriptionStyle}> <div className={descriptionStyle}>
{t['Share Menu Public Workspace Description1']()} {t['Share Menu Public Workspace Description1']()}
</div> </div>
<StyledButton <Button
data-testid="share-menu-enable-affine-cloud-button" data-testid="share-menu-enable-affine-cloud-button"
onClick={() => { onClick={() => {
props.onOpenWorkspaceSettings(props.workspace); props.onOpenWorkspaceSettings(props.workspace);
}} }}
> >
{t['Open Workspace Settings']()} {t['Open Workspace Settings']()}
</StyledButton> </Button>
</div> </div>
); );
}; };
@ -42,14 +42,14 @@ const ShareAffineWorkspace: FC<
? t['Share Menu Public Workspace Description2']() ? t['Share Menu Public Workspace Description2']()
: t['Share Menu Public Workspace Description1']()} : t['Share Menu Public Workspace Description1']()}
</div> </div>
<StyledButton <Button
data-testid="share-menu-publish-to-web-button" data-testid="share-menu-publish-to-web-button"
onClick={() => { onClick={() => {
props.onOpenWorkspaceSettings(props.workspace); props.onOpenWorkspaceSettings(props.workspace);
}} }}
> >
{t['Open Workspace Settings']()} {t['Open Workspace Settings']()}
</StyledButton> </Button>
</div> </div>
); );
}; };

View File

@ -1,34 +1,4 @@
import { Button, displayFlex, styled, TextButton } from '../..'; import { Button, displayFlex, styled } from '../..';
export const StyledShareButton = styled(TextButton, {
shouldForwardProp: (prop: string) => prop !== 'isShared',
})<{ isShared?: boolean }>(({ isShared }) => {
return {
padding: '4px 8px',
marginLeft: '4px',
marginRight: '16px',
border: `1px solid ${
isShared ? 'var(--affine-primary-color)' : 'var(--affine-icon-color)'
}`,
color: isShared
? 'var(--affine-primary-color)'
: 'var(--affine-icon-color)',
borderRadius: '8px',
':hover': {
border: `1px solid ${'var(--affine-primary-color)'}`,
},
span: {
...displayFlex('center', 'center'),
},
};
});
export const StyledTabsWrapper = styled('div')(() => {
return {
...displayFlex('space-around', 'center'),
position: 'relative',
};
});
export const TabItem = styled('li')<{ isActive?: boolean }>(({ isActive }) => { export const TabItem = styled('li')<{ isActive?: boolean }>(({ isActive }) => {
{ {
@ -99,16 +69,6 @@ export const StyledInput = styled('input')(() => {
marginRight: '10px', marginRight: '10px',
}; };
}); });
export const StyledButton = styled(TextButton)(() => {
return {
color: 'var(--affine-primary-color)',
height: '32px',
background: '#F3F0FF',
border: 'none',
borderRadius: '8px',
padding: '4px 20px',
};
});
export const StyledDisableButton = styled(Button)(() => { export const StyledDisableButton = styled(Button)(() => {
return { return {
color: '#FF631F', color: '#FF631F',

View File

@ -1,71 +1,125 @@
import { Children, cloneElement, forwardRef } from 'react'; import clsx from 'clsx';
import {
type FC,
forwardRef,
type HTMLAttributes,
type PropsWithChildren,
type ReactElement,
useMemo,
} from 'react';
import type { ButtonProps } from './interface'; import { Loading } from '../loading';
import { Loading } from './loading'; import { button, buttonIcon } from './style.css';
import { StyledButton } from './styles'; export type ButtonType =
import { getSize } from './utils'; | 'default'
| 'primary'
| 'plain'
| 'error'
| 'warning'
| 'success'
| 'processing';
export type ButtonSize = 'default' | 'large' | 'extraLarge';
export type ButtonProps = PropsWithChildren &
Omit<HTMLAttributes<HTMLButtonElement>, 'type'> & {
type?: ButtonType;
disabled?: boolean;
icon?: ReactElement;
iconPosition?: 'start' | 'end';
shape?: 'default' | 'round' | 'circle';
block?: boolean;
size?: ButtonSize;
loading?: boolean;
};
const defaultProps = {
type: 'default',
disabled: false,
shape: 'default',
size: 'default',
iconPosition: 'start',
loading: false,
};
export type { ButtonProps }; const ButtonIcon: FC<ButtonProps> = props => {
const {
export const Button = forwardRef<HTMLButtonElement, ButtonProps>( size,
(
{
size = 'default',
disabled = false,
hoverBackground,
hoverColor,
hoverStyle,
shape = 'default',
icon, icon,
iconPosition = 'start', iconPosition = 'start',
type = 'default',
children, children,
bold = false, type,
loading = false, } = {
noBorder = false, ...defaultProps,
...props ...props,
}, };
ref const onlyIcon = icon && !children;
) => { return (
const { iconSize } = getSize(size); <div
className={clsx(buttonIcon, {
'color-white': type !== 'default' && type !== 'plain',
large: size === 'large',
extraLarge: size === 'extraLarge',
end: iconPosition === 'end' && !onlyIcon,
start: iconPosition === 'start' && !onlyIcon,
})}
>
{icon}
</div>
);
};
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(props, ref) => {
const {
children,
type,
disabled,
shape,
size,
icon: propsIcon,
iconPosition,
block,
loading,
...otherProps
} = {
...defaultProps,
...props,
};
const iconElement = const icon = useMemo(() => {
icon && if (loading) {
cloneElement(Children.only(icon), { return <Loading />;
width: iconSize, }
height: iconSize, return propsIcon;
className: `affine-button-icon ${icon.props.className ?? ''}`, }, [propsIcon, loading]);
});
return ( return (
<StyledButton <button
ref={ref} ref={ref}
className={clsx(button, {
primary: type === 'primary',
plain: type === 'plain',
error: type === 'error',
warning: type === 'warning',
success: type === 'success',
processing: type === 'processing',
large: size === 'large',
extraLarge: size === 'extraLarge',
disabled,
circle: shape === 'circle',
round: shape === 'round',
block,
loading,
})}
disabled={disabled} disabled={disabled}
size={size} data-disabled={disabled}
shape={shape} {...otherProps}
hoverBackground={hoverBackground}
hoverColor={hoverColor}
hoverStyle={hoverStyle}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
type={type}
bold={bold}
noBorder={noBorder}
{...props}
> >
{loading ? ( {icon && iconPosition === 'start' ? (
<Loading type={type}></Loading> <ButtonIcon {...props} icon={icon} />
) : ( ) : null}
<> <span>{children}</span>
{iconPosition === 'start' && iconElement} {icon && iconPosition === 'end' ? <ButtonIcon {...props} /> : null}
{children && <span>{children}</span>} </button>
{iconPosition === 'end' && iconElement}
</>
)}
</StyledButton>
); );
} }
); );
Button.displayName = 'Button'; Button.displayName = 'Button';
export default Button; export default Button;

View File

@ -1,73 +1,76 @@
import type { CSSProperties, HTMLAttributes, ReactElement } from 'react'; import clsx from 'clsx';
import type { HTMLAttributes, PropsWithChildren } from 'react';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
import { StyledIconButton } from './styles'; import { Loading } from '../loading';
const SIZE_SMALL = 'small' as const; import type { ButtonType } from './button';
const SIZE_MIDDLE = 'middle' as const; import { iconButton } from './style.css';
const SIZE_NORMAL = 'normal' as const;
// TODO: IconButton should merge into Button, but it has not been designed yet
const SIZE_CONFIG = {
[SIZE_SMALL]: {
iconSize: 16,
areaSize: 20,
},
[SIZE_MIDDLE]: {
iconSize: 20,
areaSize: 24,
},
[SIZE_NORMAL]: {
iconSize: 24,
areaSize: 32,
},
} as const;
export type IconButtonProps = { export type IconButtonSize = 'default' | 'large' | 'small' | 'extraSmall';
size?: export type IconButtonProps = PropsWithChildren &
| typeof SIZE_SMALL Omit<HTMLAttributes<HTMLButtonElement>, 'type'> & {
| typeof SIZE_MIDDLE type?: ButtonType;
| typeof SIZE_NORMAL
| [number, number];
iconSize?:
| typeof SIZE_SMALL
| typeof SIZE_MIDDLE
| typeof SIZE_NORMAL
| [number, number];
disabled?: boolean; disabled?: boolean;
hoverBackground?: CSSProperties['background']; size?: IconButtonSize;
hoverColor?: string; loading?: boolean;
hoverStyle?: CSSProperties; withoutPadding?: boolean;
children: ReactElement<HTMLAttributes<SVGElement>, 'svg'>; active?: boolean;
darker?: boolean; };
} & HTMLAttributes<HTMLButtonElement>; const defaultProps = {
type: 'plain',
disabled: false,
size: 'default',
loading: false,
withoutPadding: false,
active: false,
};
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>( export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
( (props, ref) => {
{ size = 'normal', iconSize, disabled = false, children, ...props }, const {
ref type,
) => { size,
iconSize = size; withoutPadding,
const [width, height] = Array.isArray(size) children,
? size disabled,
: [SIZE_CONFIG[size]['areaSize'], SIZE_CONFIG[size]['areaSize']]; loading,
const [iconWidth] = Array.isArray(iconSize) active,
? iconSize ...otherProps
: [SIZE_CONFIG[iconSize]['iconSize'], SIZE_CONFIG[iconSize]['iconSize']]; } = {
...defaultProps,
...props,
};
return ( return (
<StyledIconButton <button
ref={ref} ref={ref}
className={clsx(iconButton, {
'without-padding': withoutPadding,
primary: type === 'primary',
plain: type === 'plain',
error: type === 'error',
warning: type === 'warning',
success: type === 'success',
processing: type === 'processing',
large: size === 'large',
small: size === 'small',
'extra-small': size === 'extraSmall',
disabled,
loading,
active,
})}
disabled={disabled} disabled={disabled}
width={width} data-disabled={disabled}
height={height} {...otherProps}
borderRadius={iconWidth / 4}
fontSize={iconWidth}
{...props}
> >
{children} {loading ? <Loading /> : children}
</StyledIconButton> </button>
); );
} }
); );
IconButton.displayName = 'IconButton';
IconButton.displayName = 'IconButton';
export default IconButton; export default IconButton;

View File

@ -2,4 +2,3 @@ export * from './button';
export * from './dropdown'; export * from './dropdown';
export * from './icon-button'; export * from './icon-button';
export * from './radio'; export * from './radio';
export * from './text-button';

View File

@ -0,0 +1,349 @@
import { globalStyle, style } from '@vanilla-extract/css';
export const button = style({
display: 'inline-flex',
justifyContent: 'center',
alignItems: 'center',
userSelect: 'none',
touchAction: 'manipulation',
outline: '0',
border: '1px solid',
padding: '0 18px',
borderRadius: '8px',
fontSize: 'var(--affine-font-base)',
transition: 'all .3s',
['WebkitAppRegion' as string]: 'no-drag',
fontWeight: 600,
// changeable
height: '28px',
background: 'var(--affine-white)',
borderColor: 'var(--affine-border-color)',
color: 'var(--affine-text-primary-color)',
selectors: {
'&.text-bold': {
fontWeight: 600,
},
'&:hover': {
background: 'var(--affine-hover-color)',
},
'&.disabled, &.loading': {
opacity: '.4',
cursor: 'default',
color: 'var(--affine-disable-color)',
pointerEvents: 'none',
},
'&.disabled:hover, &.loading:hover': {
background: 'inherit',
},
'&.block': { display: 'flex', width: '100%' },
'&.circle': {
borderRadius: '50%',
},
'&.round': {
borderRadius: '14px',
},
// size
'&.large': {
height: '32px',
},
'&.round.large': {
borderRadius: '16px',
},
'&.extraLarge': {
height: '40px',
},
'&.round.extraLarge': {
borderRadius: '20px',
},
// type
'&.plain': {
color: 'var(--affine-text-primary-color)',
borderColor: 'transparent',
},
'&.primary': {
color: 'var(--affine-white)',
background: 'var(--affine-primary-color)',
borderColor: 'var(--affine-black-10)',
boxShadow: '0px 1px 2px 0px rgba(255, 255, 255, 0.25) inset',
},
'&.primary:hover': {
background:
'linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), var(--affine-primary-color)',
},
'&.primary.disabled': {
opacity: '.4',
cursor: 'default',
},
'&.primary.disabled:hover': {
background: 'var(--affine-primary-color)',
},
'&.error': {
color: 'var(--affine-white)',
background: 'var(--affine-error-color)',
borderColor: 'var(--affine-black-10)',
boxShadow: '0px 1px 2px 0px rgba(255, 255, 255, 0.25) inset',
},
'&.error:hover': {
background:
'linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), var(--affine-error-color)',
},
'&.error.disabled': {
opacity: '.4',
cursor: 'default',
},
'&.error.disabled:hover': {
background: 'var(--affine-error-color)',
},
'&.warning': {
color: 'var(--affine-white)',
background: 'var(--affine-warning-color)',
borderColor: 'var(--affine-black-10)',
boxShadow: '0px 1px 2px 0px rgba(255, 255, 255, 0.25) inset',
},
'&.warning:hover': {
background:
'linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), var(--affine-warning-color)',
},
'&.warning.disabled': {
opacity: '.4',
cursor: 'default',
},
'&.warning.disabled:hover': {
background: 'var(--affine-warning-color)',
},
'&.success': {
color: 'var(--affine-white)',
background: 'var(--affine-success-color)',
borderColor: 'var(--affine-black-10)',
boxShadow: '0px 1px 2px 0px rgba(255, 255, 255, 0.25) inset',
},
'&.success:hover': {
background:
'linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), var(--affine-success-color)',
},
'&.success.disabled': {
opacity: '.4',
cursor: 'default',
},
'&.success.disabled:hover': {
background: 'var(--affine-success-color)',
},
'&.processing': {
color: 'var(--affine-white)',
background: 'var(--affine-processing-color)',
borderColor: 'var(--affine-black-10)',
boxShadow: '0px 1px 2px 0px rgba(255, 255, 255, 0.25) inset',
},
'&.processing:hover': {
background:
'linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), var(--affine-processing-color)',
},
'&.processing.disabled': {
opacity: '.4',
cursor: 'default',
},
'&.processing.disabled:hover': {
background: 'var(--affine-processing-color)',
},
},
});
globalStyle(`${button} > span`, {
// flex: 1,
lineHeight: 1,
padding: '0 4px',
});
export const buttonIcon = style({
flexShrink: 0,
display: 'inline-flex',
justifyContent: 'center',
alignItems: 'center',
color: 'var(--affine-icon-color)',
fontSize: '16px',
width: '16px',
height: '16px',
selectors: {
'&.start': {
marginRight: '4px',
},
'&.end': {
marginLeft: '4px',
},
'&.large': {
fontSize: '20px',
width: '20px',
height: '20px',
},
'&.extraLarge': {
fontSize: '20px',
width: '20px',
height: '20px',
},
'&.color-white': {
color: 'var(--affine-white)',
},
},
});
export const iconButton = style({
display: 'inline-flex',
justifyContent: 'center',
alignItems: 'center',
userSelect: 'none',
touchAction: 'manipulation',
outline: '0',
border: '1px solid',
borderRadius: '4px',
transition: 'all .3s',
['WebkitAppRegion' as string]: 'no-drag',
// changeable
width: '24px',
height: '24px',
fontSize: '20px',
color: 'var(--affine-text-primary-color)',
borderColor: 'var(--affine-border-color)',
selectors: {
'&.without-padding': {
margin: '-2px',
},
'&.active': {
color: 'var(--affine-primary-color)',
},
'&:hover': {
background: 'var(--affine-hover-color)',
},
'&.disabled, &.loading': {
opacity: '.4',
cursor: 'default',
color: 'var(--affine-disable-color)',
pointerEvents: 'none',
},
'&.disabled:hover, &.loading:hover': {
background: 'inherit',
},
// size
'&.large': {
width: '32px',
height: '32px',
fontSize: '24px',
},
'&.large.without-padding': {
margin: '-4px',
},
'&.small': { width: '20px', height: '20px', fontSize: '16px' },
'&.extra-small': { width: '16px', height: '16px', fontSize: '12px' },
// type
'&.plain': {
color: 'var(--affine-icon-color)',
borderColor: 'transparent',
},
'&.plain.active': {
color: 'var(--affine-primary-color)',
},
'&.primary': {
color: 'var(--affine-white)',
background: 'var(--affine-primary-color)',
borderColor: 'var(--affine-black-10)',
boxShadow: '0px 1px 2px 0px rgba(255, 255, 255, 0.25) inset',
},
'&.primary:hover': {
background:
'linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), var(--affine-primary-color)',
},
'&.primary.disabled': {
opacity: '.4',
cursor: 'default',
},
'&.primary.disabled:hover': {
background: 'var(--affine-primary-color)',
},
'&.error': {
color: 'var(--affine-white)',
background: 'var(--affine-error-color)',
borderColor: 'var(--affine-black-10)',
boxShadow: '0px 1px 2px 0px rgba(255, 255, 255, 0.25) inset',
},
'&.error:hover': {
background:
'linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), var(--affine-error-color)',
},
'&.error.disabled': {
opacity: '.4',
cursor: 'default',
},
'&.error.disabled:hover': {
background: 'var(--affine-error-color)',
},
'&.warning': {
color: 'var(--affine-white)',
background: 'var(--affine-warning-color)',
borderColor: 'var(--affine-black-10)',
boxShadow: '0px 1px 2px 0px rgba(255, 255, 255, 0.25) inset',
},
'&.warning:hover': {
background:
'linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), var(--affine-warning-color)',
},
'&.warning.disabled': {
opacity: '.4',
cursor: 'default',
},
'&.warning.disabled:hover': {
background: 'var(--affine-warning-color)',
},
'&.success': {
color: 'var(--affine-white)',
background: 'var(--affine-success-color)',
borderColor: 'var(--affine-black-10)',
boxShadow: '0px 1px 2px 0px rgba(255, 255, 255, 0.25) inset',
},
'&.success:hover': {
background:
'linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), var(--affine-success-color)',
},
'&.success.disabled': {
opacity: '.4',
cursor: 'default',
},
'&.success.disabled:hover': {
background: 'var(--affine-success-color)',
},
'&.processing': {
color: 'var(--affine-white)',
background: 'var(--affine-processing-color)',
borderColor: 'var(--affine-black-10)',
boxShadow: '0px 1px 2px 0px rgba(255, 255, 255, 0.25) inset',
},
'&.processing:hover': {
background:
'linear-gradient(0deg, rgba(0, 0, 0, 0.04) 0%, rgba(0, 0, 0, 0.04) 100%), var(--affine-processing-color)',
},
'&.processing.disabled': {
opacity: '.4',
cursor: 'default',
},
'&.processing.disabled:hover': {
background: 'var(--affine-processing-color)',
},
},
});

View File

@ -1,123 +1,7 @@
import type { CSSProperties } from 'react';
import { displayInlineFlex, styled } from '../../styles'; import { displayInlineFlex, styled } from '../../styles';
import type { ButtonProps } from './interface'; import type { ButtonProps } from './interface';
import { getButtonColors, getSize } from './utils'; import { getButtonColors, getSize } from './utils';
export const StyledIconButton = styled('button', {
shouldForwardProp: prop => {
return ![
'borderRadius',
'top',
'right',
'width',
'height',
'hoverBackground',
'hoverColor',
'hoverStyle',
'fontSize',
].includes(prop as string);
},
})<{
width: number;
height: number;
borderRadius: number;
disabled?: boolean;
hoverBackground?: CSSProperties['background'];
hoverColor?: string;
hoverStyle?: CSSProperties;
// In some cases, button is in a normal hover status, it should be darkened
fontSize?: CSSProperties['fontSize'];
}>(({
width,
height,
borderRadius,
disabled,
hoverBackground,
hoverColor,
hoverStyle,
fontSize,
}) => {
return {
width,
height,
fontSize,
WebkitAppRegion: 'no-drag',
color: 'var(--affine-icon-color)',
...displayInlineFlex('center', 'center'),
...(disabled ? { cursor: 'not-allowed', pointerEvents: 'none' } : {}),
transition: 'background .15s',
borderRadius,
':hover': {
color: hoverColor ?? 'var(--affine-icon-color)',
background: hoverBackground || 'var(--affine-hover-color)',
...(hoverStyle ?? {}),
},
};
});
export const StyledTextButton = styled('button', {
shouldForwardProp: prop => {
return ![
'borderRadius',
'top',
'right',
'width',
'height',
'hoverBackground',
'hoverColor',
'hoverStyle',
'bold',
].includes(prop as string);
},
})<
Pick<
ButtonProps,
| 'size'
| 'disabled'
| 'hoverBackground'
| 'hoverColor'
| 'hoverStyle'
| 'shape'
| 'type'
| 'bold'
>
>(({
size = 'default',
disabled,
hoverBackground,
hoverColor,
hoverStyle,
bold = false,
shape = 'default',
// TODO: Implement type
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// type = 'default',
}) => {
const { fontSize, borderRadius, padding, height } = getSize(size);
return {
height,
paddingLeft: padding,
paddingRight: padding,
...displayInlineFlex('flex-start', 'center'),
position: 'relative',
...(disabled ? { cursor: 'not-allowed', pointerEvents: 'none' } : {}),
transition: 'background .15s',
// TODO: Implement circle shape
borderRadius: shape === 'default' ? borderRadius : height / 2,
fontSize,
fontWeight: bold ? '500' : '400',
':hover': {
color: hoverColor ?? 'var(--affine-primary-color)',
background: hoverBackground ?? 'var(--affine-hover-color)',
...(hoverStyle ?? {}),
},
};
});
export const StyledButton = styled('button', { export const StyledButton = styled('button', {
shouldForwardProp: prop => { shouldForwardProp: prop => {
return ![ return ![

View File

@ -1,53 +0,0 @@
import { Children, cloneElement, forwardRef } from 'react';
import type { ButtonProps } from './interface';
import { StyledTextButton } from './styles';
import { getSize } from './utils';
export const TextButton = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
size = 'default',
disabled = false,
hoverBackground,
hoverColor,
hoverStyle,
shape = 'default',
icon,
type = 'default',
children,
bold = false,
...props
},
ref
) => {
const { iconSize } = getSize(size);
return (
<StyledTextButton
ref={ref}
disabled={disabled}
size={size}
shape={shape}
hoverBackground={hoverBackground}
hoverColor={hoverColor}
hoverStyle={hoverStyle}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
type={type}
bold={bold}
{...props}
>
{icon &&
cloneElement(Children.only(icon), {
width: iconSize,
height: iconSize,
className: `affine-button-icon ${icon.props.className ?? ''}`,
})}
{children && <span>{children}</span>}
</StyledTextButton>
);
}
);
TextButton.displayName = 'TextButton';
export default TextButton;

View File

@ -17,7 +17,7 @@ export type ConfirmProps = {
confirmText?: string; confirmText?: string;
cancelText?: string; cancelText?: string;
// TODO: Confirm button's color should depend on confirm type // TODO: Confirm button's color should depend on confirm type
confirmType?: 'primary' | 'warning' | 'danger'; confirmType?: 'primary' | 'warning' | 'error';
buttonDirection?: 'row' | 'column'; buttonDirection?: 'row' | 'column';
onConfirm?: () => void; onConfirm?: () => void;
onCancel?: () => void; onCancel?: () => void;
@ -53,7 +53,6 @@ export const Confirm = ({
<StyledRowButtonWrapper> <StyledRowButtonWrapper>
<Button <Button
shape="round" shape="round"
bold={true}
onClick={() => { onClick={() => {
onCancel?.(); onCancel?.();
}} }}
@ -65,7 +64,6 @@ export const Confirm = ({
<Button <Button
type={confirmType} type={confirmType}
shape="round" shape="round"
bold={true}
onClick={() => { onClick={() => {
onConfirm?.(); onConfirm?.();
}} }}
@ -79,7 +77,6 @@ export const Confirm = ({
<Button <Button
type={confirmType} type={confirmType}
shape="round" shape="round"
bold={true}
onClick={() => { onClick={() => {
onConfirm?.(); onConfirm?.();
}} }}
@ -90,7 +87,6 @@ export const Confirm = ({
</Button> </Button>
<Button <Button
shape="round" shape="round"
bold={true}
onClick={() => { onClick={() => {
onCancel?.(); onCancel?.();
}} }}

View File

@ -0,0 +1 @@
export * from './loading';

View File

@ -0,0 +1,31 @@
import { assignInlineVars } from '@vanilla-extract/dynamic';
import type { FC } from 'react';
import { loading, speedVar } from './styles.css';
export type LoadingProps = {
size?: number;
speed?: number;
};
export const Loading: FC<LoadingProps> = ({ size, speed = 1.2 }) => {
return (
<svg
className={loading}
viewBox="0 0 1024 1024"
focusable="false"
data-icon="loading"
width={size ? `${size}px` : '.8em'}
height={size ? `${size}px` : '.8em'}
fill="currentColor"
aria-hidden="true"
style={{
...assignInlineVars({
[speedVar]: `${speed}s`,
}),
}}
>
<path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"></path>
</svg>
);
};

View File

@ -0,0 +1,17 @@
import { createVar, keyframes, style } from '@vanilla-extract/css';
export const speedVar = createVar('speedVar');
const rotate = keyframes({
'0%': { transform: 'rotate(0deg)' },
'50%': { transform: 'rotate(180deg)' },
'100%': { transform: 'rotate(360deg)' },
});
export const loading = style({
vars: {
[speedVar]: '1.5s',
},
textRendering: 'optimizeLegibility',
WebkitFontSmoothing: 'antialiased',
animation: `${rotate} ${speedVar} infinite linear`,
});

View File

@ -1,21 +1,20 @@
import { ArrowDownSmallIcon } from '@blocksuite/icons'; import { ArrowDownSmallIcon } from '@blocksuite/icons';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
import type { ButtonProps } from '../button'; import { Button, type ButtonProps } from '../button';
import { StyledButton } from './styles';
export const MenuTrigger = forwardRef<HTMLButtonElement, ButtonProps>( export const MenuTrigger = forwardRef<HTMLButtonElement, ButtonProps>(
({ children, ...props }, ref) => { ({ children, ...props }, ref) => {
return ( return (
<StyledButton <Button
type="plain"
ref={ref} ref={ref}
icon={<ArrowDownSmallIcon />} icon={<ArrowDownSmallIcon />}
iconPosition="end" iconPosition="end"
noBorder={true}
{...props} {...props}
> >
{children} {children}
</StyledButton> </Button>
); );
} }
); );

View File

@ -37,7 +37,7 @@ export const DebugContent: PluginUIAdapter['debugContent'] = () => {
)} )}
/> />
<Button <Button
size="middle" size="large"
onClick={() => { onClick={() => {
indexedDB.deleteDatabase(conversationHistoryDBName); indexedDB.deleteDatabase(conversationHistoryDBName);
location.reload(); location.reload();

View File

@ -53,20 +53,10 @@ export const Conversation = (props: ConversationProps): ReactElement => {
</div> </div>
{props.type === 'ai' ? ( {props.type === 'ai' ? (
<div className={styles.insertButtonsStyle}> <div className={styles.insertButtonsStyle}>
<Button <Button icon={<PlusIcon />} className={styles.insertButtonStyle}>
icon={<PlusIcon />}
size="small"
className={styles.insertButtonStyle}
hoverColor="var(--affine-text-primary-color)"
>
Insert list block only Insert list block only
</Button> </Button>
<Button <Button icon={<PlusIcon />} className={styles.insertButtonStyle}>
icon={<PlusIcon />}
size="small"
className={styles.insertButtonStyle}
hoverColor="var(--affine-text-primary-color)"
>
Insert all Insert all
</Button> </Button>
</div> </div>