mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-12 14:40:22 +08:00
feat: add button component
This commit is contained in:
@@ -0,0 +1,60 @@
|
|||||||
|
import {
|
||||||
|
HTMLAttributes,
|
||||||
|
cloneElement,
|
||||||
|
ReactElement,
|
||||||
|
Children,
|
||||||
|
CSSProperties,
|
||||||
|
forwardRef,
|
||||||
|
PropsWithChildren,
|
||||||
|
} from 'react';
|
||||||
|
import { StyledButton } from './styles';
|
||||||
|
|
||||||
|
import { ButtonProps } from './interface';
|
||||||
|
import { getSize } from './utils';
|
||||||
|
|
||||||
|
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
size = 'default',
|
||||||
|
disabled = false,
|
||||||
|
hoverBackground,
|
||||||
|
hoverColor,
|
||||||
|
hoverStyle,
|
||||||
|
shape = 'default',
|
||||||
|
icon,
|
||||||
|
type = 'default',
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const { iconSize } = getSize(size);
|
||||||
|
console.log('type', type);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledButton
|
||||||
|
ref={ref}
|
||||||
|
disabled={disabled}
|
||||||
|
size={size}
|
||||||
|
shape={shape}
|
||||||
|
hoverBackground={hoverBackground}
|
||||||
|
hoverColor={hoverColor}
|
||||||
|
hoverStyle={hoverStyle}
|
||||||
|
// @ts-ignore
|
||||||
|
type={type}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{icon &&
|
||||||
|
cloneElement(Children.only(icon), {
|
||||||
|
width: iconSize,
|
||||||
|
height: iconSize,
|
||||||
|
className: `affine-button-icon ${icon.props.className ?? ''}`,
|
||||||
|
})}
|
||||||
|
{children && <span>{children}</span>}
|
||||||
|
</StyledButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Button.displayName = 'Button';
|
||||||
|
|
||||||
|
export default Button;
|
||||||
@@ -11,7 +11,7 @@ import { StyledIconButton } from './styles';
|
|||||||
const SIZE_SMALL = 'small' as const;
|
const SIZE_SMALL = 'small' as const;
|
||||||
const SIZE_MIDDLE = 'middle' as const;
|
const SIZE_MIDDLE = 'middle' as const;
|
||||||
const SIZE_NORMAL = 'normal' as const;
|
const SIZE_NORMAL = 'normal' as const;
|
||||||
// TODO: Designer is not sure about the size of the icon button
|
// TODO: IconButton should merge into Button, but it has not been designed yet
|
||||||
const SIZE_CONFIG = {
|
const SIZE_CONFIG = {
|
||||||
[SIZE_SMALL]: {
|
[SIZE_SMALL]: {
|
||||||
iconSize: 16,
|
iconSize: 16,
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export * from './icon-button';
|
export * from './icon-button';
|
||||||
|
export * from './button';
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import {
|
||||||
|
CSSProperties,
|
||||||
|
HTMLAttributes,
|
||||||
|
PropsWithChildren,
|
||||||
|
ReactElement,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
export const SIZE_SMALL = 'small' as const;
|
||||||
|
export const SIZE_MIDDLE = 'middle' as const;
|
||||||
|
export const SIZE_DEFAULT = 'default' as const;
|
||||||
|
|
||||||
|
export type ButtonProps = PropsWithChildren &
|
||||||
|
Omit<HTMLAttributes<HTMLButtonElement>, 'type'> & {
|
||||||
|
size?: typeof SIZE_SMALL | typeof SIZE_MIDDLE | typeof SIZE_DEFAULT;
|
||||||
|
disabled?: boolean;
|
||||||
|
hoverBackground?: CSSProperties['background'];
|
||||||
|
hoverColor?: CSSProperties['color'];
|
||||||
|
hoverStyle?: CSSProperties;
|
||||||
|
icon?: ReactElement;
|
||||||
|
shape?: 'default' | 'round' | 'circle';
|
||||||
|
type?: 'primary' | 'warning' | 'danger' | 'default';
|
||||||
|
};
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { absoluteCenter, displayInlineFlex, styled } from '@/styles';
|
import { absoluteCenter, displayInlineFlex, styled } from '@/styles';
|
||||||
import { CSSProperties } from 'react';
|
import { CSSProperties } from 'react';
|
||||||
|
import { ButtonProps } from '@/ui/button/interface';
|
||||||
|
import { getSize, getButtonColors } from './utils';
|
||||||
|
|
||||||
export const StyledIconButton = styled.button<{
|
export const StyledIconButton = styled.button<{
|
||||||
width: number;
|
width: number;
|
||||||
@@ -53,3 +55,64 @@ export const StyledIconButton = styled.button<{
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
export const StyledButton = styled.button<
|
||||||
|
Pick<
|
||||||
|
ButtonProps,
|
||||||
|
| 'size'
|
||||||
|
| 'disabled'
|
||||||
|
| 'hoverBackground'
|
||||||
|
| 'hoverColor'
|
||||||
|
| 'hoverStyle'
|
||||||
|
| 'shape'
|
||||||
|
| 'type'
|
||||||
|
>
|
||||||
|
>(
|
||||||
|
({
|
||||||
|
theme,
|
||||||
|
size = 'default',
|
||||||
|
disabled,
|
||||||
|
hoverBackground,
|
||||||
|
hoverColor,
|
||||||
|
hoverStyle,
|
||||||
|
shape = 'default',
|
||||||
|
type = 'default',
|
||||||
|
}) => {
|
||||||
|
const { fontSize, borderRadius, padding, height } = getSize(size);
|
||||||
|
|
||||||
|
return {
|
||||||
|
height,
|
||||||
|
paddingLeft: padding,
|
||||||
|
paddingRight: padding,
|
||||||
|
border: '1px solid',
|
||||||
|
...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,
|
||||||
|
'.affine-button-icon': {
|
||||||
|
color: theme.colors.iconColor,
|
||||||
|
},
|
||||||
|
'>span': {
|
||||||
|
marginLeft: '5px',
|
||||||
|
},
|
||||||
|
// @ts-ignore
|
||||||
|
...getButtonColors(theme, type, {
|
||||||
|
hoverBackground,
|
||||||
|
hoverColor,
|
||||||
|
hoverStyle,
|
||||||
|
}),
|
||||||
|
|
||||||
|
//
|
||||||
|
// ':hover': {
|
||||||
|
// color: hoverColor ?? theme.colors.primaryColor,
|
||||||
|
// background: hoverBackground ?? theme.colors.hoverBackground,
|
||||||
|
// '.affine-button-icon':{
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// ...(hoverStyle ?? {}),
|
||||||
|
// },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { AffineTheme } from '@/styles';
|
||||||
|
import {
|
||||||
|
SIZE_SMALL,
|
||||||
|
SIZE_MIDDLE,
|
||||||
|
SIZE_DEFAULT,
|
||||||
|
ButtonProps,
|
||||||
|
} from './interface';
|
||||||
|
|
||||||
|
// TODO: Designer is not sure about the size, Now, is just use default size
|
||||||
|
export const SIZE_CONFIG = {
|
||||||
|
[SIZE_SMALL]: {
|
||||||
|
iconSize: 16,
|
||||||
|
fontSize: 16,
|
||||||
|
borderRadius: 6,
|
||||||
|
height: 26,
|
||||||
|
padding: 24,
|
||||||
|
},
|
||||||
|
[SIZE_MIDDLE]: {
|
||||||
|
iconSize: 20,
|
||||||
|
fontSize: 16,
|
||||||
|
borderRadius: 6,
|
||||||
|
height: 32,
|
||||||
|
padding: 24,
|
||||||
|
},
|
||||||
|
[SIZE_DEFAULT]: {
|
||||||
|
iconSize: 24,
|
||||||
|
fontSize: 16,
|
||||||
|
height: 38,
|
||||||
|
padding: 24,
|
||||||
|
borderRadius: 6,
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const getSize = (
|
||||||
|
size: typeof SIZE_SMALL | typeof SIZE_MIDDLE | typeof SIZE_DEFAULT
|
||||||
|
) => {
|
||||||
|
return SIZE_CONFIG[size];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getButtonColors = (
|
||||||
|
theme: AffineTheme,
|
||||||
|
type: ButtonProps['type'],
|
||||||
|
extend?: {
|
||||||
|
hoverBackground: ButtonProps['hoverBackground'];
|
||||||
|
hoverColor: ButtonProps['hoverColor'];
|
||||||
|
hoverStyle: ButtonProps['hoverStyle'];
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'primary':
|
||||||
|
return {
|
||||||
|
background: theme.colors.primaryColor,
|
||||||
|
color: '#fff',
|
||||||
|
borderColor: theme.colors.primaryColor,
|
||||||
|
'.affine-button-icon': {
|
||||||
|
color: '#fff',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case 'warning':
|
||||||
|
return {
|
||||||
|
background: theme.colors.warningBackground,
|
||||||
|
color: theme.colors.warningColor,
|
||||||
|
borderColor: theme.colors.warningBackground,
|
||||||
|
'.affine-button-icon': {
|
||||||
|
color: theme.colors.warningColor,
|
||||||
|
},
|
||||||
|
':hover': {
|
||||||
|
borderColor: theme.colors.warningColor,
|
||||||
|
color: extend?.hoverColor,
|
||||||
|
background: extend?.hoverBackground,
|
||||||
|
...extend?.hoverStyle,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case 'danger':
|
||||||
|
return {
|
||||||
|
background: theme.colors.errorBackground,
|
||||||
|
color: theme.colors.errorColor,
|
||||||
|
borderColor: theme.colors.errorBackground,
|
||||||
|
'.affine-button-icon': {
|
||||||
|
color: theme.colors.errorColor,
|
||||||
|
},
|
||||||
|
':hover': {
|
||||||
|
borderColor: theme.colors.errorColor,
|
||||||
|
color: extend?.hoverColor,
|
||||||
|
background: extend?.hoverBackground,
|
||||||
|
...extend?.hoverStyle,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
color: theme.colors.popoverColor,
|
||||||
|
borderColor: theme.colors.borderColor,
|
||||||
|
':hover': {
|
||||||
|
borderColor: theme.colors.primaryColor,
|
||||||
|
color: extend?.hoverColor ?? theme.colors.primaryColor,
|
||||||
|
'.affine-button-icon': {
|
||||||
|
color: extend?.hoverColor ?? theme.colors.primaryColor,
|
||||||
|
background: extend?.hoverBackground,
|
||||||
|
...extend?.hoverStyle,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -5,9 +5,8 @@ import {
|
|||||||
StyledConfirmContent,
|
StyledConfirmContent,
|
||||||
StyledConfirmTitle,
|
StyledConfirmTitle,
|
||||||
StyledModalWrapper,
|
StyledModalWrapper,
|
||||||
StyledButton,
|
|
||||||
} from '@/ui/confirm/styles';
|
} from '@/ui/confirm/styles';
|
||||||
|
import { Button } from '@/ui/button';
|
||||||
export type ConfirmProps = {
|
export type ConfirmProps = {
|
||||||
title?: string;
|
title?: string;
|
||||||
content?: string;
|
content?: string;
|
||||||
@@ -40,23 +39,26 @@ export const Confirm = ({
|
|||||||
<StyledConfirmContent>{content}</StyledConfirmContent>
|
<StyledConfirmContent>{content}</StyledConfirmContent>
|
||||||
|
|
||||||
<StyledButtonWrapper>
|
<StyledButtonWrapper>
|
||||||
<StyledButton
|
<Button
|
||||||
|
shape="round"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
onCancel?.();
|
onCancel?.();
|
||||||
}}
|
}}
|
||||||
|
style={{ marginRight: '24px' }}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</StyledButton>
|
</Button>
|
||||||
<StyledButton
|
<Button
|
||||||
confirmType={confirmType}
|
type={confirmType}
|
||||||
|
shape="round"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
onConfirm?.();
|
onConfirm?.();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{confirmText}
|
{confirmText}
|
||||||
</StyledButton>
|
</Button>
|
||||||
</StyledButtonWrapper>
|
</StyledButtonWrapper>
|
||||||
</StyledModalWrapper>
|
</StyledModalWrapper>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -78,21 +78,3 @@ const getButtonColors = (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StyledButton = styled.button<Pick<ConfirmProps, 'confirmType'>>(
|
|
||||||
({ theme, confirmType }) => {
|
|
||||||
return {
|
|
||||||
width: '100px',
|
|
||||||
height: '38px',
|
|
||||||
borderRadius: '19px',
|
|
||||||
border: '1px solid',
|
|
||||||
...getButtonColors(theme, confirmType),
|
|
||||||
fontSize: theme.font.sm,
|
|
||||||
fontWeight: 500,
|
|
||||||
|
|
||||||
'&:first-of-type': {
|
|
||||||
marginRight: '24px',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|||||||
Reference in New Issue
Block a user