mirror of
https://github.com/zhigang1992/react.git
synced 2026-03-26 06:55:07 +08:00
feat(button-group): add component
feat(button-group): buttons arranged vertically
This commit is contained in:
20
components/button-group/button-group-context.ts
Normal file
20
components/button-group/button-group-context.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react'
|
||||
import { NormalSizes, ButtonTypes } from '../utils/prop-types'
|
||||
|
||||
export interface ButtonGroupConfig {
|
||||
size?: NormalSizes
|
||||
type?: ButtonTypes
|
||||
ghost?: boolean
|
||||
disabled?: boolean
|
||||
isButtonGroup: boolean
|
||||
}
|
||||
|
||||
const defaultContext = {
|
||||
isButtonGroup: false,
|
||||
disabled: false,
|
||||
}
|
||||
|
||||
export const ButtonGroupContext = React.createContext<ButtonGroupConfig>(defaultContext)
|
||||
|
||||
export const useButtonGroupContext = (): ButtonGroupConfig =>
|
||||
React.useContext<ButtonGroupConfig>(ButtonGroupContext)
|
||||
116
components/button-group/button-group.tsx
Normal file
116
components/button-group/button-group.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
import { NormalSizes, ButtonTypes } from '../utils/prop-types'
|
||||
import { ButtonGroupContext, ButtonGroupConfig } from './button-group-context'
|
||||
import { getButtonColors } from '../button/styles'
|
||||
|
||||
interface Props {
|
||||
disabled?: boolean
|
||||
vertical?: boolean
|
||||
ghost?: boolean
|
||||
size?: NormalSizes
|
||||
type?: ButtonTypes
|
||||
className?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
disabled: false,
|
||||
vertical: false,
|
||||
ghost: false,
|
||||
size: 'medium' as NormalSizes,
|
||||
type: 'default' as ButtonTypes,
|
||||
className: '',
|
||||
}
|
||||
|
||||
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
|
||||
export type ButtonGroupProps = Props & typeof defaultProps & NativeAttrs
|
||||
|
||||
const ButtonGroup: React.FC<React.PropsWithChildren<ButtonGroupProps>> = ({
|
||||
disabled,
|
||||
size,
|
||||
type,
|
||||
ghost,
|
||||
vertical,
|
||||
children,
|
||||
className,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const initialValue = useMemo<ButtonGroupConfig>(
|
||||
() => ({
|
||||
disabled,
|
||||
size,
|
||||
type,
|
||||
ghost,
|
||||
isButtonGroup: true,
|
||||
}),
|
||||
[disabled, size, type],
|
||||
)
|
||||
|
||||
const { border } = useMemo(() => {
|
||||
const results = getButtonColors(theme, type, disabled, ghost)
|
||||
if (!ghost && type !== 'default')
|
||||
return {
|
||||
...results,
|
||||
border: theme.palette.background,
|
||||
}
|
||||
return results
|
||||
}, [theme, type, disabled, ghost])
|
||||
|
||||
return (
|
||||
<ButtonGroupContext.Provider value={initialValue}>
|
||||
<div className={`btn-group ${vertical ? 'vertical' : 'horizontal'} ${className}`}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
.btn-group {
|
||||
display: inline-flex;
|
||||
border-radius: ${theme.layout.radius};
|
||||
margin: ${theme.layout.gapQuarter};
|
||||
border: 1px solid ${border};
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
height: min-content;
|
||||
}
|
||||
|
||||
.vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.btn-group :global(.btn) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-group :global(.btn .text) {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.horizontal :global(.btn:not(:first-child)) {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-left: 1px solid ${border};
|
||||
}
|
||||
|
||||
.horizontal :global(.btn:not(:last-child)) {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.vertical :global(.btn:not(:first-child)) {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-top: 1px solid ${border};
|
||||
}
|
||||
|
||||
.vertical :global(.btn:not(:last-child)) {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
</ButtonGroupContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const MemoButtonGroup = React.memo(ButtonGroup)
|
||||
|
||||
export default withDefaults(MemoButtonGroup, defaultProps)
|
||||
3
components/button-group/index.ts
Normal file
3
components/button-group/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import ButtonGroup from './button-group'
|
||||
|
||||
export default ButtonGroup
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { useRef, useState, MouseEvent, useMemo } from 'react'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import { ButtonTypes, NormalSizes } from '../utils/prop-types'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
import ButtonDrip from './button.drip'
|
||||
import ButtonLoading from '../loading'
|
||||
import ButtonIcon from './button-icon'
|
||||
import { ButtonTypes, NormalSizes } from '../utils/prop-types'
|
||||
import { filterPropsWithGroup, getButtonChildrenWithIcon } from './utils'
|
||||
import { useButtonGroupContext } from '../button-group/button-group-context'
|
||||
import { getButtonColors, getButtonCursor, getButtonHoverColors, getButtonSize } from './styles'
|
||||
|
||||
interface Props {
|
||||
@@ -37,27 +38,30 @@ const defaultProps = {
|
||||
type NativeAttrs = Omit<React.ButtonHTMLAttributes<any>, keyof Props>
|
||||
export type ButtonProps = Props & typeof defaultProps & NativeAttrs
|
||||
|
||||
const Button: React.FC<React.PropsWithChildren<ButtonProps>> = ({
|
||||
children,
|
||||
disabled,
|
||||
type,
|
||||
loading,
|
||||
shadow,
|
||||
ghost,
|
||||
effect,
|
||||
onClick,
|
||||
auto,
|
||||
size,
|
||||
icon,
|
||||
iconRight,
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
const Button: React.FC<React.PropsWithChildren<ButtonProps>> = ({ ...btnProps }) => {
|
||||
const theme = useTheme()
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
const [dripShow, setDripShow] = useState<boolean>(false)
|
||||
const [dripX, setDripX] = useState<number>(0)
|
||||
const [dripY, setDripY] = useState<number>(0)
|
||||
const groupConfig = useButtonGroupContext()
|
||||
const {
|
||||
children,
|
||||
disabled,
|
||||
type,
|
||||
loading,
|
||||
shadow,
|
||||
ghost,
|
||||
effect,
|
||||
onClick,
|
||||
auto,
|
||||
size,
|
||||
icon,
|
||||
iconRight,
|
||||
className,
|
||||
...props
|
||||
} = filterPropsWithGroup(btnProps, groupConfig)
|
||||
|
||||
const { bg, border, color } = useMemo(() => getButtonColors(theme, type, disabled, ghost), [
|
||||
theme,
|
||||
type,
|
||||
@@ -99,35 +103,19 @@ const Button: React.FC<React.PropsWithChildren<ButtonProps>> = ({
|
||||
onClick && onClick(event)
|
||||
}
|
||||
|
||||
const childrenWithIcon = useMemo(() => {
|
||||
const hasIcon = icon || iconRight
|
||||
const isRight = Boolean(iconRight)
|
||||
const paddingForAutoMode =
|
||||
auto || size === 'mini'
|
||||
? `calc(var(--zeit-ui-button-height) / 2 + var(--zeit-ui-button-padding) * .5)`
|
||||
: 0
|
||||
if (!hasIcon) return <div className="text">{children}</div>
|
||||
return (
|
||||
<>
|
||||
<ButtonIcon isRight={isRight}>{hasIcon}</ButtonIcon>
|
||||
<div className={`text ${isRight ? 'right' : 'left'}`}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
.left {
|
||||
padding-left: ${paddingForAutoMode};
|
||||
}
|
||||
.right {
|
||||
padding-right: ${paddingForAutoMode};
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}, [children, icon, auto, size])
|
||||
const childrenWithIcon = useMemo(
|
||||
() =>
|
||||
getButtonChildrenWithIcon(auto, size, children, {
|
||||
icon,
|
||||
iconRight,
|
||||
}),
|
||||
[auto, size, children, icon, iconRight],
|
||||
)
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={buttonRef}
|
||||
type="button"
|
||||
className={`btn ${className}`}
|
||||
disabled={disabled}
|
||||
onClick={clickHandler}
|
||||
@@ -159,7 +147,8 @@ const Button: React.FC<React.PropsWithChildren<ButtonProps>> = ({
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s ease 0s;
|
||||
transition: background-color 200ms ease 0ms, box-shadow 200ms ease 0ms,
|
||||
border 200ms ease 0ms;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: ${color};
|
||||
|
||||
56
components/button/utils.tsx
Normal file
56
components/button/utils.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import React, { ReactNode } from 'react'
|
||||
import { NormalSizes } from '../utils/prop-types'
|
||||
import ButtonIcon from './button-icon'
|
||||
import { ButtonProps } from 'components/button/button'
|
||||
import { ButtonGroupConfig } from 'components/button-group/button-group-context'
|
||||
|
||||
export const getButtonChildrenWithIcon = (
|
||||
auto: boolean,
|
||||
size: NormalSizes,
|
||||
children: ReactNode,
|
||||
icons: {
|
||||
icon?: React.ReactNode
|
||||
iconRight?: React.ReactNode
|
||||
} = {},
|
||||
) => {
|
||||
const { icon, iconRight } = icons
|
||||
const hasIcon = icon || iconRight
|
||||
const isRight = Boolean(iconRight)
|
||||
const paddingForAutoMode =
|
||||
auto || size === 'mini'
|
||||
? `calc(var(--zeit-ui-button-height) / 2 + var(--zeit-ui-button-padding) * .5)`
|
||||
: 0
|
||||
if (!hasIcon) return <div className="text">{children}</div>
|
||||
return (
|
||||
<>
|
||||
<ButtonIcon isRight={isRight}>{hasIcon}</ButtonIcon>
|
||||
<div className={`text ${isRight ? 'right' : 'left'}`}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
.left {
|
||||
padding-left: ${paddingForAutoMode};
|
||||
}
|
||||
.right {
|
||||
padding-right: ${paddingForAutoMode};
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const filterPropsWithGroup = (
|
||||
props: React.PropsWithChildren<ButtonProps>,
|
||||
config: ButtonGroupConfig,
|
||||
): ButtonProps => {
|
||||
if (!config.isButtonGroup) return props
|
||||
return {
|
||||
...props,
|
||||
auto: true,
|
||||
shadow: false,
|
||||
ghost: config.ghost || props.ghost,
|
||||
size: config.size || props.size,
|
||||
type: config.type || props.type,
|
||||
disabled: config.disabled || props.disabled,
|
||||
}
|
||||
}
|
||||
@@ -53,3 +53,4 @@ export { default as Divider } from './divider'
|
||||
export { default as User } from './user'
|
||||
export { default as Page } from './page'
|
||||
export { default as Grid } from './grid'
|
||||
export { default as ButtonGroup } from './button-group'
|
||||
|
||||
File diff suppressed because one or more lines are too long
47
pages/en-us/components/button-group.mdx
Normal file
47
pages/en-us/components/button-group.mdx
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Layout, Playground, Attributes } from 'lib/components'
|
||||
import { Button, Spacer, ButtonGroup } from 'components'
|
||||
|
||||
export const meta = {
|
||||
title: 'Button-Group',
|
||||
group: 'Data Entry',
|
||||
}
|
||||
|
||||
## Button Group
|
||||
|
||||
A set of related buttons.
|
||||
|
||||
<Playground
|
||||
title="Basic"
|
||||
scope={{ Button, ButtonGroup }}
|
||||
code={`
|
||||
<ButtonGroup ghost type="success">
|
||||
<Button >Action</Button>
|
||||
<span />
|
||||
<Button >Action</Button>
|
||||
</ButtonGroup>
|
||||
`} />
|
||||
|
||||
<Attributes edit="/pages/en-us/components/button-group.mdx">
|
||||
<Attributes.Title>Button.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **type** | button type | `ButtonTypes` | [ButtonTypes](#buttontypes) | `default` |
|
||||
| **size** | button size | `NormalSizes` | [NormalSizes](#normalsizes) | `medium` |
|
||||
| ... | native props | `ButtonHTMLAttributes` | `'id', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>ButtonTypes</Attributes.Title>
|
||||
|
||||
```ts
|
||||
type ButtonTypes = 'default' | 'secondary' | 'success' | 'warning' | 'error' | 'abort'
|
||||
```
|
||||
|
||||
<Attributes.Title>NormalSizes</Attributes.Title>
|
||||
|
||||
```ts
|
||||
type NormalSizes = 'mini' | 'small' | 'medium' | 'large'
|
||||
```
|
||||
|
||||
</Attributes>
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>
|
||||
Reference in New Issue
Block a user