mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 20:38:52 +00:00
feat: refactor button with new design (#3343)
This commit is contained in:
@@ -23,7 +23,6 @@ export const SidebarHeader = (props: SidebarHeaderProps) => {
|
||||
<>
|
||||
{environment.isMacOs && <div style={{ flex: 1 }} />}
|
||||
<IconButton
|
||||
size="middle"
|
||||
data-testid="app-sidebar-arrow-button-back"
|
||||
disabled={props.router?.history.current === 0}
|
||||
onClick={() => {
|
||||
@@ -33,7 +32,6 @@ export const SidebarHeader = (props: SidebarHeaderProps) => {
|
||||
<ArrowLeftSmallIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size="middle"
|
||||
data-testid="app-sidebar-arrow-button-forward"
|
||||
disabled={
|
||||
props.router
|
||||
|
||||
@@ -342,9 +342,8 @@ const ImagePreviewModalImpl = (
|
||||
<Button
|
||||
data-testid="previous-image-button"
|
||||
icon={<ArrowLeftSmallIcon className={buttonIconStyle} />}
|
||||
noBorder={true}
|
||||
type="plain"
|
||||
className={buttonStyle}
|
||||
hoverColor={'-moz-initial'}
|
||||
onClick={() => {
|
||||
assertExists(blockId);
|
||||
previousImageHandler(blockId);
|
||||
@@ -355,9 +354,8 @@ const ImagePreviewModalImpl = (
|
||||
<Button
|
||||
data-testid="next-image-button"
|
||||
icon={<ArrowRightSmallIcon className={buttonIconStyle} />}
|
||||
noBorder={true}
|
||||
className={buttonStyle}
|
||||
hoverColor={'-moz-initial'}
|
||||
type="plain"
|
||||
onClick={() => {
|
||||
assertExists(blockId);
|
||||
nextImageHandler(blockId);
|
||||
@@ -374,8 +372,7 @@ const ImagePreviewModalImpl = (
|
||||
<Button
|
||||
data-testid="fit-to-screen-button"
|
||||
icon={<ViewBarIcon className={buttonIconStyle} />}
|
||||
noBorder={true}
|
||||
hoverColor={'-moz-initial'}
|
||||
type="plain"
|
||||
className={buttonStyle}
|
||||
onClick={() => resetZoom()}
|
||||
/>
|
||||
@@ -384,19 +381,17 @@ const ImagePreviewModalImpl = (
|
||||
<Button
|
||||
data-testid="zoom-out-button"
|
||||
icon={<MinusIcon className={buttonIconStyle} />}
|
||||
noBorder={true}
|
||||
className={buttonStyle}
|
||||
hoverColor={'-moz-initial'}
|
||||
type="plain"
|
||||
onClick={zoomOut}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip content={'Reset Scale'} disablePortal={false}>
|
||||
<Button
|
||||
data-testid="reset-scale-button"
|
||||
noBorder={true}
|
||||
size={'middle'}
|
||||
type="plain"
|
||||
size={'large'}
|
||||
className={scaleIndicatorButtonStyle}
|
||||
hoverColor={'-moz-initial'}
|
||||
onClick={resetScale}
|
||||
>
|
||||
{`${(currentScale * 100).toFixed(0)}%`}
|
||||
@@ -406,9 +401,8 @@ const ImagePreviewModalImpl = (
|
||||
<Button
|
||||
data-testid="zoom-in-button"
|
||||
icon={<PlusIcon className={buttonIconStyle} />}
|
||||
noBorder={true}
|
||||
className={buttonStyle}
|
||||
hoverColor={'-moz-initial'}
|
||||
type="plain"
|
||||
onClick={() => zoomIn()}
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -417,9 +411,8 @@ const ImagePreviewModalImpl = (
|
||||
<Button
|
||||
data-testid="download-button"
|
||||
icon={<DownloadIcon className={buttonIconStyle} />}
|
||||
noBorder={true}
|
||||
type="plain"
|
||||
className={buttonStyle}
|
||||
hoverColor={'-moz-initial'}
|
||||
onClick={() => {
|
||||
assertExists(blockId);
|
||||
downloadHandler(blockId).catch(err => {
|
||||
@@ -432,9 +425,8 @@ const ImagePreviewModalImpl = (
|
||||
<Button
|
||||
data-testid="copy-to-clipboard-button"
|
||||
icon={<CopyIcon className={buttonIconStyle} />}
|
||||
noBorder={true}
|
||||
type="plain"
|
||||
className={buttonStyle}
|
||||
hoverColor={'-moz-initial'}
|
||||
onClick={() => {
|
||||
if (!imageRef.current) {
|
||||
return;
|
||||
@@ -474,10 +466,9 @@ const ImagePreviewModalImpl = (
|
||||
<Button
|
||||
data-testid="delete-button"
|
||||
icon={<DeleteIcon className={buttonIconStyle} />}
|
||||
noBorder={true}
|
||||
type="plain"
|
||||
className={buttonStyle}
|
||||
onClick={() => blockId && deleteHandler(blockId)}
|
||||
hoverColor="var(--affine-error-color)"
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -19,12 +19,7 @@ export const FavoriteTag = forwardRef<
|
||||
>
|
||||
<IconButton
|
||||
ref={ref}
|
||||
iconSize={[20, 20]}
|
||||
style={{
|
||||
color: active
|
||||
? 'var(--affine-primary-color)'
|
||||
: 'var(--affine-icon-color)',
|
||||
}}
|
||||
active={active}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onClick?.(e);
|
||||
|
||||
@@ -147,8 +147,6 @@ export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
hoverBackground="var(--affine-background-error-color)"
|
||||
hoverColor="var(--affine-error-color)"
|
||||
>
|
||||
<DeletePermanentlyIcon />
|
||||
</IconButton>
|
||||
@@ -157,7 +155,7 @@ export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
|
||||
title={t['Delete permanently?']()}
|
||||
content={t['TrashButtonGroupDescription']()}
|
||||
confirmText={t['Delete']()}
|
||||
confirmType="danger"
|
||||
confirmType="error"
|
||||
open={open}
|
||||
onConfirm={() => {
|
||||
onPermanentlyDeletePage();
|
||||
|
||||
@@ -44,7 +44,7 @@ const ConfirmModal = ({
|
||||
title: title || 'Untitled',
|
||||
})}
|
||||
confirmText={t.Delete()}
|
||||
confirmType="danger"
|
||||
confirmType="error"
|
||||
{...confirmModalProps}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -207,9 +207,7 @@ export const CollectionList = ({
|
||||
}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
className={clsx(styles.viewButton)}
|
||||
hoverColor="var(--affine-icon-color)"
|
||||
data-testid="collection-select"
|
||||
>
|
||||
<Tooltip
|
||||
@@ -235,8 +233,6 @@ export const CollectionList = ({
|
||||
<Button
|
||||
icon={<FilteredIcon />}
|
||||
className={clsx(styles.filterButton)}
|
||||
size="small"
|
||||
hoverColor="var(--affine-icon-color)"
|
||||
data-testid="create-first-filter"
|
||||
>
|
||||
{t['com.affine.filter']()}
|
||||
|
||||
@@ -56,12 +56,7 @@ export const EditCollectionModel = ({
|
||||
background: 'var(--affine-background-primary-color)',
|
||||
}}
|
||||
>
|
||||
<ModalCloseButton
|
||||
top={12}
|
||||
right={12}
|
||||
onClick={onClose}
|
||||
hoverColor="var(--affine-icon-color)"
|
||||
/>
|
||||
<ModalCloseButton top={12} right={12} onClick={onClose} />
|
||||
{init ? (
|
||||
<EditCollection
|
||||
propertiesMeta={propertiesMeta}
|
||||
@@ -283,7 +278,7 @@ export const SaveCollectionButton = ({
|
||||
<Button
|
||||
className={styles.saveButton}
|
||||
onClick={() => changeShow(true)}
|
||||
size="middle"
|
||||
size="large"
|
||||
data-testid="save-as-collection"
|
||||
>
|
||||
<div className={styles.saveButtonContainer}>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
|
||||
import { Modal, ModalCloseButton } from '../../..';
|
||||
import { Button } from '../../../ui/button';
|
||||
import {
|
||||
StyledButton,
|
||||
StyledButtonContent,
|
||||
StyledDangerButton,
|
||||
StyledModalHeader,
|
||||
StyledModalWrapper,
|
||||
StyledTextContent,
|
||||
@@ -33,8 +32,9 @@ export const PublicLinkDisableModal = ({
|
||||
</StyledTextContent>
|
||||
|
||||
<StyledButtonContent>
|
||||
<StyledButton onClick={onClose}>{t['Cancel']()}</StyledButton>
|
||||
<StyledDangerButton
|
||||
<Button onClick={onClose}>{t['Cancel']()}</Button>
|
||||
<Button
|
||||
type="error"
|
||||
data-testid="disable-public-link-confirm-button"
|
||||
onClick={() => {
|
||||
onConfirmDisable();
|
||||
@@ -43,7 +43,7 @@ export const PublicLinkDisableModal = ({
|
||||
style={{ marginLeft: '24px' }}
|
||||
>
|
||||
{t['Disable']()}
|
||||
</StyledDangerButton>
|
||||
</Button>
|
||||
</StyledButtonContent>
|
||||
</StyledModalWrapper>
|
||||
</Modal>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { styled, TextButton } from '../../..';
|
||||
import { styled } from '../../..';
|
||||
|
||||
export const StyledModalWrapper = styled('div')(() => {
|
||||
return {
|
||||
@@ -40,24 +40,3 @@ export const StyledButtonContent = styled('div')(() => {
|
||||
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',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -9,12 +9,13 @@ import type { FC } from 'react';
|
||||
import { useRef } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { Button } from '../../ui/button';
|
||||
import { Menu } from '../../ui/menu/menu';
|
||||
import { Export } from './export';
|
||||
import { containerStyle, indicatorContainerStyle, tabStyle } from './index.css';
|
||||
import { SharePage } from './share-page';
|
||||
import { ShareWorkspace } from './share-workspace';
|
||||
import { StyledIndicator, StyledShareButton, TabItem } from './styles';
|
||||
import { StyledIndicator, TabItem } from './styles';
|
||||
type SharePanel = 'SharePage' | 'Export' | 'ShareWorkspace';
|
||||
const MenuItems: Record<SharePanel, FC<ShareMenuProps>> = {
|
||||
SharePage: SharePage,
|
||||
@@ -134,15 +135,15 @@ export const ShareMenu: FC<ShareMenuProps> = props => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<StyledShareButton
|
||||
<Button
|
||||
data-testid="share-menu-button"
|
||||
onClick={() => {
|
||||
setOpen(!open);
|
||||
}}
|
||||
isShared={isPublic}
|
||||
type={isPublic ? 'primary' : undefined}
|
||||
>
|
||||
<div>{isPublic ? 'Shared' : 'Share'}</div>
|
||||
</StyledShareButton>
|
||||
</Button>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { FC } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { toast } from '../..';
|
||||
import { Button, toast } from '../..';
|
||||
import { PublicLinkDisableModal } from './disable-public-link';
|
||||
import {
|
||||
descriptionStyle,
|
||||
@@ -15,26 +15,22 @@ import {
|
||||
menuItemStyle,
|
||||
} from './index.css';
|
||||
import type { ShareMenuProps } from './share-menu';
|
||||
import {
|
||||
StyledButton,
|
||||
StyledDisableButton,
|
||||
StyledInput,
|
||||
StyledLinkSpan,
|
||||
} from './styles';
|
||||
import { StyledDisableButton, StyledInput, StyledLinkSpan } from './styles';
|
||||
|
||||
export const LocalSharePage: FC<ShareMenuProps> = props => {
|
||||
const t = useAFFiNEI18N();
|
||||
return (
|
||||
<div className={menuItemStyle}>
|
||||
<div className={descriptionStyle}>{t['Shared Pages Description']()}</div>
|
||||
<StyledButton
|
||||
<Button
|
||||
type="primary"
|
||||
data-testid="share-menu-enable-affine-cloud-button"
|
||||
onClick={() => {
|
||||
props.onEnableAffineCloud(props.workspace as LocalWorkspace);
|
||||
}}
|
||||
>
|
||||
{t['Enable AFFiNE Cloud']()}
|
||||
</StyledButton>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -74,20 +70,20 @@ export const AffineSharePage: FC<ShareMenuProps> = props => {
|
||||
value={isPublic ? sharingUrl : 'https://app.affine.pro/xxxx'}
|
||||
/>
|
||||
{!isPublic && (
|
||||
<StyledButton
|
||||
<Button
|
||||
data-testid="affine-share-create-link"
|
||||
onClick={onClickCreateLink}
|
||||
>
|
||||
{t['Create']()}
|
||||
</StyledButton>
|
||||
</Button>
|
||||
)}
|
||||
{isPublic && (
|
||||
<StyledButton
|
||||
<Button
|
||||
data-testid="affine-share-copy-link"
|
||||
onClick={onClickCopyLink}
|
||||
>
|
||||
{t['Copy Link']()}
|
||||
</StyledButton>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className={descriptionStyle}>
|
||||
|
||||
@@ -6,9 +6,9 @@ import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { Button } from '../../ui/button';
|
||||
import { descriptionStyle, menuItemStyle } from './index.css';
|
||||
import type { ShareMenuProps } from './share-menu';
|
||||
import { StyledButton } from './styles';
|
||||
|
||||
const ShareLocalWorkspace: FC<ShareMenuProps<LocalWorkspace>> = props => {
|
||||
const t = useAFFiNEI18N();
|
||||
@@ -17,14 +17,14 @@ const ShareLocalWorkspace: FC<ShareMenuProps<LocalWorkspace>> = props => {
|
||||
<div className={descriptionStyle}>
|
||||
{t['Share Menu Public Workspace Description1']()}
|
||||
</div>
|
||||
<StyledButton
|
||||
<Button
|
||||
data-testid="share-menu-enable-affine-cloud-button"
|
||||
onClick={() => {
|
||||
props.onOpenWorkspaceSettings(props.workspace);
|
||||
}}
|
||||
>
|
||||
{t['Open Workspace Settings']()}
|
||||
</StyledButton>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -42,14 +42,14 @@ const ShareAffineWorkspace: FC<
|
||||
? t['Share Menu Public Workspace Description2']()
|
||||
: t['Share Menu Public Workspace Description1']()}
|
||||
</div>
|
||||
<StyledButton
|
||||
<Button
|
||||
data-testid="share-menu-publish-to-web-button"
|
||||
onClick={() => {
|
||||
props.onOpenWorkspaceSettings(props.workspace);
|
||||
}}
|
||||
>
|
||||
{t['Open Workspace Settings']()}
|
||||
</StyledButton>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,34 +1,4 @@
|
||||
import { Button, displayFlex, styled, TextButton } 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',
|
||||
};
|
||||
});
|
||||
import { Button, displayFlex, styled } from '../..';
|
||||
|
||||
export const TabItem = styled('li')<{ isActive?: boolean }>(({ isActive }) => {
|
||||
{
|
||||
@@ -99,16 +69,6 @@ export const StyledInput = styled('input')(() => {
|
||||
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)(() => {
|
||||
return {
|
||||
color: '#FF631F',
|
||||
|
||||
@@ -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 { StyledButton } from './styles';
|
||||
import { getSize } from './utils';
|
||||
|
||||
export type { ButtonProps };
|
||||
import { Loading } from '../loading';
|
||||
import { button, buttonIcon } from './style.css';
|
||||
export type ButtonType =
|
||||
| '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,
|
||||
};
|
||||
|
||||
const ButtonIcon: FC<ButtonProps> = props => {
|
||||
const {
|
||||
size,
|
||||
icon,
|
||||
iconPosition = 'start',
|
||||
children,
|
||||
type,
|
||||
} = {
|
||||
...defaultProps,
|
||||
...props,
|
||||
};
|
||||
const onlyIcon = icon && !children;
|
||||
return (
|
||||
<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>(
|
||||
(
|
||||
{
|
||||
size = 'default',
|
||||
disabled = false,
|
||||
hoverBackground,
|
||||
hoverColor,
|
||||
hoverStyle,
|
||||
shape = 'default',
|
||||
icon,
|
||||
iconPosition = 'start',
|
||||
type = 'default',
|
||||
(props, ref) => {
|
||||
const {
|
||||
children,
|
||||
bold = false,
|
||||
loading = false,
|
||||
noBorder = false,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { iconSize } = getSize(size);
|
||||
type,
|
||||
disabled,
|
||||
shape,
|
||||
size,
|
||||
icon: propsIcon,
|
||||
iconPosition,
|
||||
block,
|
||||
loading,
|
||||
...otherProps
|
||||
} = {
|
||||
...defaultProps,
|
||||
...props,
|
||||
};
|
||||
|
||||
const iconElement =
|
||||
icon &&
|
||||
cloneElement(Children.only(icon), {
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
className: `affine-button-icon ${icon.props.className ?? ''}`,
|
||||
});
|
||||
const icon = useMemo(() => {
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
return propsIcon;
|
||||
}, [propsIcon, loading]);
|
||||
|
||||
return (
|
||||
<StyledButton
|
||||
<button
|
||||
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}
|
||||
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}
|
||||
noBorder={noBorder}
|
||||
{...props}
|
||||
data-disabled={disabled}
|
||||
{...otherProps}
|
||||
>
|
||||
{loading ? (
|
||||
<Loading type={type}></Loading>
|
||||
) : (
|
||||
<>
|
||||
{iconPosition === 'start' && iconElement}
|
||||
{children && <span>{children}</span>}
|
||||
{iconPosition === 'end' && iconElement}
|
||||
</>
|
||||
)}
|
||||
</StyledButton>
|
||||
{icon && iconPosition === 'start' ? (
|
||||
<ButtonIcon {...props} icon={icon} />
|
||||
) : null}
|
||||
<span>{children}</span>
|
||||
{icon && iconPosition === 'end' ? <ButtonIcon {...props} /> : null}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
);
|
||||
Button.displayName = 'Button';
|
||||
|
||||
export default Button;
|
||||
|
||||
@@ -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 { StyledIconButton } from './styles';
|
||||
const SIZE_SMALL = 'small' as const;
|
||||
const SIZE_MIDDLE = 'middle' as const;
|
||||
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;
|
||||
import { Loading } from '../loading';
|
||||
import type { ButtonType } from './button';
|
||||
import { iconButton } from './style.css';
|
||||
|
||||
export type IconButtonProps = {
|
||||
size?:
|
||||
| typeof SIZE_SMALL
|
||||
| typeof SIZE_MIDDLE
|
||||
| typeof SIZE_NORMAL
|
||||
| [number, number];
|
||||
iconSize?:
|
||||
| typeof SIZE_SMALL
|
||||
| typeof SIZE_MIDDLE
|
||||
| typeof SIZE_NORMAL
|
||||
| [number, number];
|
||||
disabled?: boolean;
|
||||
hoverBackground?: CSSProperties['background'];
|
||||
hoverColor?: string;
|
||||
hoverStyle?: CSSProperties;
|
||||
children: ReactElement<HTMLAttributes<SVGElement>, 'svg'>;
|
||||
darker?: boolean;
|
||||
} & HTMLAttributes<HTMLButtonElement>;
|
||||
export type IconButtonSize = 'default' | 'large' | 'small' | 'extraSmall';
|
||||
export type IconButtonProps = PropsWithChildren &
|
||||
Omit<HTMLAttributes<HTMLButtonElement>, 'type'> & {
|
||||
type?: ButtonType;
|
||||
disabled?: boolean;
|
||||
size?: IconButtonSize;
|
||||
loading?: boolean;
|
||||
withoutPadding?: boolean;
|
||||
active?: boolean;
|
||||
};
|
||||
const defaultProps = {
|
||||
type: 'plain',
|
||||
disabled: false,
|
||||
size: 'default',
|
||||
loading: false,
|
||||
withoutPadding: false,
|
||||
active: false,
|
||||
};
|
||||
|
||||
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
||||
(
|
||||
{ size = 'normal', iconSize, disabled = false, children, ...props },
|
||||
ref
|
||||
) => {
|
||||
iconSize = size;
|
||||
const [width, height] = Array.isArray(size)
|
||||
? size
|
||||
: [SIZE_CONFIG[size]['areaSize'], SIZE_CONFIG[size]['areaSize']];
|
||||
const [iconWidth] = Array.isArray(iconSize)
|
||||
? iconSize
|
||||
: [SIZE_CONFIG[iconSize]['iconSize'], SIZE_CONFIG[iconSize]['iconSize']];
|
||||
(props, ref) => {
|
||||
const {
|
||||
type,
|
||||
size,
|
||||
withoutPadding,
|
||||
children,
|
||||
disabled,
|
||||
loading,
|
||||
active,
|
||||
...otherProps
|
||||
} = {
|
||||
...defaultProps,
|
||||
...props,
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledIconButton
|
||||
<button
|
||||
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}
|
||||
width={width}
|
||||
height={height}
|
||||
borderRadius={iconWidth / 4}
|
||||
fontSize={iconWidth}
|
||||
{...props}
|
||||
data-disabled={disabled}
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
</StyledIconButton>
|
||||
{loading ? <Loading /> : children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
);
|
||||
IconButton.displayName = 'IconButton';
|
||||
|
||||
IconButton.displayName = 'IconButton';
|
||||
export default IconButton;
|
||||
|
||||
@@ -2,4 +2,3 @@ export * from './button';
|
||||
export * from './dropdown';
|
||||
export * from './icon-button';
|
||||
export * from './radio';
|
||||
export * from './text-button';
|
||||
|
||||
349
packages/component/src/ui/button/style.css.ts
Normal file
349
packages/component/src/ui/button/style.css.ts
Normal 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)',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,123 +1,7 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
import { displayInlineFlex, styled } from '../../styles';
|
||||
import type { ButtonProps } from './interface';
|
||||
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', {
|
||||
shouldForwardProp: prop => {
|
||||
return ![
|
||||
|
||||
@@ -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;
|
||||
@@ -17,7 +17,7 @@ export type ConfirmProps = {
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
// TODO: Confirm button's color should depend on confirm type
|
||||
confirmType?: 'primary' | 'warning' | 'danger';
|
||||
confirmType?: 'primary' | 'warning' | 'error';
|
||||
buttonDirection?: 'row' | 'column';
|
||||
onConfirm?: () => void;
|
||||
onCancel?: () => void;
|
||||
@@ -53,7 +53,6 @@ export const Confirm = ({
|
||||
<StyledRowButtonWrapper>
|
||||
<Button
|
||||
shape="round"
|
||||
bold={true}
|
||||
onClick={() => {
|
||||
onCancel?.();
|
||||
}}
|
||||
@@ -65,7 +64,6 @@ export const Confirm = ({
|
||||
<Button
|
||||
type={confirmType}
|
||||
shape="round"
|
||||
bold={true}
|
||||
onClick={() => {
|
||||
onConfirm?.();
|
||||
}}
|
||||
@@ -79,7 +77,6 @@ export const Confirm = ({
|
||||
<Button
|
||||
type={confirmType}
|
||||
shape="round"
|
||||
bold={true}
|
||||
onClick={() => {
|
||||
onConfirm?.();
|
||||
}}
|
||||
@@ -90,7 +87,6 @@ export const Confirm = ({
|
||||
</Button>
|
||||
<Button
|
||||
shape="round"
|
||||
bold={true}
|
||||
onClick={() => {
|
||||
onCancel?.();
|
||||
}}
|
||||
|
||||
1
packages/component/src/ui/loading/index.ts
Normal file
1
packages/component/src/ui/loading/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './loading';
|
||||
31
packages/component/src/ui/loading/loading.tsx
Normal file
31
packages/component/src/ui/loading/loading.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
17
packages/component/src/ui/loading/styles.css.ts
Normal file
17
packages/component/src/ui/loading/styles.css.ts
Normal 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`,
|
||||
});
|
||||
@@ -1,21 +1,20 @@
|
||||
import { ArrowDownSmallIcon } from '@blocksuite/icons';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import type { ButtonProps } from '../button';
|
||||
import { StyledButton } from './styles';
|
||||
import { Button, type ButtonProps } from '../button';
|
||||
|
||||
export const MenuTrigger = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ children, ...props }, ref) => {
|
||||
return (
|
||||
<StyledButton
|
||||
<Button
|
||||
type="plain"
|
||||
ref={ref}
|
||||
icon={<ArrowDownSmallIcon />}
|
||||
iconPosition="end"
|
||||
noBorder={true}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</StyledButton>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user