From e82e8976ee10fa76e0cf31da377e8aa60501d73e Mon Sep 17 00:00:00 2001 From: 79E <5980844@qq.com> Date: Fri, 13 Jan 2023 15:24:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(Button):=20=E6=8C=89=E9=92=AE=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E6=96=B0=E5=A2=9E=E6=8C=89=E9=92=AE=E7=BB=84=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/button/README.md | 38 +++++++---- src/components/button/button-context.ts | 10 +++ src/components/button/button-group.tsx | 45 +++++++++++++ src/components/button/button.tsx | 67 +++++++++++++++----- src/components/button/demos/demo-group.tsx | 29 +++++++++ src/components/button/demos/demo.tsx | 68 +++++++++++--------- src/components/button/index.ts | 7 ++- src/components/button/styles/index.less | 73 ++++++++++++++++++++++ src/components/button/types.ts | 10 ++- 9 files changed, 286 insertions(+), 61 deletions(-) create mode 100644 src/components/button/button-context.ts create mode 100644 src/components/button/button-group.tsx create mode 100644 src/components/button/demos/demo-group.tsx diff --git a/src/components/button/README.md b/src/components/button/README.md index fa190cb..014f4b8 100644 --- a/src/components/button/README.md +++ b/src/components/button/README.md @@ -62,20 +62,36 @@ import { Button } from "aunt"; 通过 color 属性可以自定义按钮的颜色。 +### 按钮组 + +通过 Button.Group 包裹可以实现按钮组的概念。 + + ## 参数 -| 参数 | 说明 | 类型 | 默认值 | -| -------- | ----------------------------------------- | ------------------------------------------------------ | --------- | -| type | 统一设置按钮类型 | `'default'\|'primary'\|'success'\|'warning'\|'danger'` | `default` | -| size | 统一设置按钮尺寸 | `'large'\|'normal'\|'small'\|'mini'` | `normal` | -| block | 是否是块级元素 | `boolean` | `false` | +| 参数 | 说明 | 类型 | 默认值 | +| -------- | ------ | ------- | --- | +| type | 统一设置按钮类型 | `'default'\|'primary'\|'success'\|'warning'\|'danger'` | `default` | +| size | 统一设置按钮尺寸 | `'large'\|'normal'\|'small'\|'mini'` | `normal` | +| block | 是否是块级元素 | `boolean` | `false` | | color | 按钮颜色,支持传入 linear-gradient 渐变色 | `string` | `-` | -| disabled | 是否禁用 | `boolean` | `false` | -| shape | 按钮的形状 | `'default' \| 'square' \| 'round'` | `default` | -| plain | 是否为朴素按钮 | `boolean` | `false` | -| hairline | 是否使用 0.5px 边框 | `boolean` | `false` | - -## 事件 +| disabled | 是否禁用 | `boolean` | `false` | +| shape | 按钮的形状 | `'default' \| 'square' \| 'round'` | `default` | +| plain | 是否为朴素按钮 | `boolean` | `false` | +| hairline | 是否使用 0.5px 边框 | `boolean` | `false` | + + +### Button.Group +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| type | 统一设置按钮类型 | `string` | `default` | +| size | 统一设置按钮尺寸 | `string` | `normal` | +| iconPosition | 统一设置按钮图标展示位置 | `string` | `left` | +| block | 统一设置按钮为块级元素 | `boolean` | `false` | +| plain | 是否为朴素按钮组 | `boolean` | `false` | +| disabled | 是否禁用按钮组 | `boolean` | `false` | + +### 事件 | 事件名 | 说明 | 类型 | 默认值 | | ------- | -------------- | ----------------------------------------------------------------------------------- | ------ | diff --git a/src/components/button/button-context.ts b/src/components/button/button-context.ts new file mode 100644 index 0000000..83d896c --- /dev/null +++ b/src/components/button/button-context.ts @@ -0,0 +1,10 @@ +import React from 'react'; +import { ButtonGroupProps } from './types'; + +type ButtonContextType = { + parent?: ButtonGroupProps; +}; + +const ButtonContext = React.createContext({}); + +export default ButtonContext; diff --git a/src/components/button/button-group.tsx b/src/components/button/button-group.tsx new file mode 100644 index 0000000..951a269 --- /dev/null +++ b/src/components/button/button-group.tsx @@ -0,0 +1,45 @@ +import React, { useContext } from 'react'; +import ConfigProviderContext from '../config-provider/config-provider-context'; +import ButtonContext from './button-context'; +import { useNamespace } from '../../hooks'; +import { joinTrim } from '../../utils'; +import { ButtonGroupProps } from './types'; + +const ButtonGroup = (props: ButtonGroupProps) => { + const { shape = 'default', type = 'default' } = props; + + const internalClick = (e: React.MouseEvent) => { + if (props.disabled) return; + props.onClick?.(e); + }; + + const { prefix } = useContext(ConfigProviderContext); + const ns = useNamespace('button', prefix); + + return ( +
+ + {props.children} + +
+ ); +}; + +export default ButtonGroup; diff --git a/src/components/button/button.tsx b/src/components/button/button.tsx index de991f3..79a4ef0 100644 --- a/src/components/button/button.tsx +++ b/src/components/button/button.tsx @@ -2,6 +2,7 @@ import React, { CSSProperties, FunctionComponent, useContext, useMemo } from 're import { ButtonProps } from './types'; import Loading from '../loading'; import ConfigProviderContext from '../config-provider/config-provider-context'; +import ButtonContext from './button-context'; import { useNamespace } from '../../hooks'; import { joinTrim } from '../../utils'; @@ -9,40 +10,74 @@ export const Button: FunctionComponent> = props => { const { color, shape = 'default', - plain = false, + iconPosition = 'left', hairline = false, loading = false, - disabled = false, - type = 'default', - size = 'normal', - block = false, children, - iconPosition = 'left', icon, className, style, loadingText, + block, + plain, ...rest } = props; + const { parent } = useContext(ButtonContext); + + const currentSize = useMemo(() => props.size || parent?.size || 'normal', [ + props.size, + parent?.size, + ]); + + const currentType = useMemo(() => props.type || parent?.type || 'default', [ + props.type, + parent?.type, + ]); + + const currentPlain = useMemo(() => !!plain || !!parent?.plain, [plain, parent?.plain]); + + const currentBlock = useMemo(() => !!block || !!parent?.block, [parent?.block, block]); + + const currentIconPosition = useMemo(() => parent?.iconPosition || iconPosition, [ + parent?.iconPosition, + iconPosition, + ]); + + const currentDisabled = React.useMemo(() => props.disabled ?? parent?.disabled, [ + parent?.disabled, + props.disabled, + ]); + const { prefix } = useContext(ConfigProviderContext); const ns = useNamespace('button', prefix); const varClasses = useMemo(() => { return joinTrim([ ns.b(), - type ? ns.m(type) : '', - size ? ns.m(size) : '', + ns.m(currentType), + ns.m(currentSize), shape ? ns.em('shape', shape) : '', - plain ? ns.m('plain') : '', - block ? ns.m('block') : '', - disabled ? ns.m('disabled') : '', + currentPlain ? ns.m('plain') : '', + currentBlock ? ns.m('block') : '', + currentDisabled ? ns.m('disabled') : '', hairline ? ns.m('hairline') : '', icon ? ns.e('icon') : '', loading ? ns.e('loading') : '', className, ]); - }, [type, size, shape, plain, block, disabled, hairline, icon, loading, className]); + }, [ + currentBlock, + currentPlain, + currentDisabled, + currentType, + currentSize, + shape, + hairline, + icon, + loading, + className, + ]); const varStyles = useMemo(() => { const styles: CSSProperties = {}; @@ -59,7 +94,7 @@ export const Button: FunctionComponent> = props => { }, [plain, color, style]); const handleClick = (event: any) => { - if (!loading && !disabled && props.onClick) { + if (!loading && !currentDisabled && props.onClick) { props.onClick(event); } }; @@ -85,7 +120,7 @@ export const Button: FunctionComponent> = props => { ); @@ -108,9 +143,9 @@ export const Button: FunctionComponent> = props => { return (
- {iconPosition === 'left' && renderIcon('left')} + {currentIconPosition === 'left' && renderIcon('left')} {renderText()} - {iconPosition === 'right' && renderIcon('right')} + {currentIconPosition === 'right' && renderIcon('right')}
); }; diff --git a/src/components/button/demos/demo-group.tsx b/src/components/button/demos/demo-group.tsx new file mode 100644 index 0000000..e6ed79e --- /dev/null +++ b/src/components/button/demos/demo-group.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Button, Space, AuntIconArrowLeft, AuntIconArrowRight, AuntIconRefreshCcw } from 'aunt'; + +export default () => ( + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/src/components/button/demos/demo.tsx b/src/components/button/demos/demo.tsx index c950f21..db2122d 100644 --- a/src/components/button/demos/demo.tsx +++ b/src/components/button/demos/demo.tsx @@ -10,52 +10,58 @@ import DemoShape from './demo-shape'; import DemoSize from './demo-size'; import DemoButtonBlock from './demo-block'; import DemoColor from './demo-color'; +import DemoGroup from './demo-group'; import './index.less'; function Demo() { return ( -
- - - + <> +
+ + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + + +
+ + -
+ ); } diff --git a/src/components/button/index.ts b/src/components/button/index.ts index 11e73dc..59f9dce 100644 --- a/src/components/button/index.ts +++ b/src/components/button/index.ts @@ -1,7 +1,12 @@ import './styles/index.less'; -import { Button } from './button'; +import { Button as _Button } from './button'; +import ButtonGroup from './button-group'; export type { ButtonProps, ButtonType, ButtonSize, ButtonShape } from './types'; +const Button = Object.assign(_Button, { + Group: ButtonGroup, +}); + export { Button }; export default Button; diff --git a/src/components/button/styles/index.less b/src/components/button/styles/index.less index 657357a..5d580a7 100644 --- a/src/components/button/styles/index.less +++ b/src/components/button/styles/index.less @@ -203,4 +203,77 @@ display: none; } } + + &__group{ + display: inline-flex; + overflow: hidden; + border-width: var(--aunt-button-border-width); + border-style: solid; + + .@{class-prefix}-button{ + border-radius: 0; + border-width: 0; + border-right-width: var(--aunt-button-border-width); + flex: 1; + &__text{ + white-space: nowrap; + } + &:last-child { + border-width: 0; + } + } + + &--default{ + border-color: var(--aunt-button-border-default-color); + .@{class-prefix}-button{ + border-color: var(--aunt-button-border-default-color); + } + } + + &--primary{ + border-color: var(--aunt-button-border-primary-color); + .@{class-prefix}-button{ + border-color: var(--aunt-button-border-primary-color); + } + } + &--success{ + border-color: var(--aunt-button-border-success-color); + .@{class-prefix}-button{ + border-color: var(--aunt-button-border-success-color); + } + } + &--warning{ + border-color: var(--aunt-button-border-warning-color); + .@{class-prefix}-button{ + border-color: var(--aunt-button-border-warning-color); + } + } + &--danger{ + border-color: var(--aunt-button-border-danger-color); + .@{class-prefix}-button{ + border-color: var(--aunt-button-border-danger-color); + } + } + + &__shape{ + &--default{ + border-radius: var(--aunt-button-border-radius); + } + &--square{ + border-radius: 0; + } + &--round{ + border-radius: var(--aunt-border-radius-max); + } + } + + &--disabled { + cursor: not-allowed; + opacity: var(--aunt-button-disabled-opacity); + + .@{class-prefix}-button{ + --aunt-button-disabled-opacity: 1; + } + } + } } \ No newline at end of file diff --git a/src/components/button/types.ts b/src/components/button/types.ts index 843ac9a..e21ec52 100644 --- a/src/components/button/types.ts +++ b/src/components/button/types.ts @@ -83,7 +83,7 @@ export interface ButtonProps extends BaseTypeProps { * @name 按钮图标位置 * @default 'left' */ - iconPosition: IconPosition; + iconPosition?: IconPosition; /** * @name 按钮文字 * @default '' @@ -96,5 +96,11 @@ export interface ButtonProps extends BaseTypeProps { * @param event React.MouseEvent * @return void | Promise */ - onClick?: (event: React.MouseEvent) => void; + onClick?: (event: React.MouseEvent) => void; } + +export type ButtonGroupProps = BaseTypeProps & + Pick< + ButtonProps, + 'size' | 'type' | 'shape' | 'plain' | 'block' | 'disabled' | 'iconPosition' | 'onClick' + >;