feat(button-group): add component

feat(button-group): buttons arranged vertically
This commit is contained in:
unix
2020-05-10 23:39:00 +08:00
parent 374c58622d
commit fcd42f1700
8 changed files with 278 additions and 46 deletions

View 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)

View 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)

View File

@@ -0,0 +1,3 @@
import ButtonGroup from './button-group'
export default ButtonGroup

View File

@@ -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};

View 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,
}
}

View File

@@ -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

View 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>