From 4422e66fa2420901db1e00601e0ea26d1623ee95 Mon Sep 17 00:00:00 2001 From: "huang.jian" Date: Wed, 27 May 2020 14:16:04 +0800 Subject: [PATCH] feat: :sparkles: implement button component --- packages/Button/Button.css | 189 +++++++++++++++++++++++++++++++++++++ packages/Button/Button.tsx | 187 ++++++++++++++++++++++++++++++++++++ packages/Button/index.ts | 4 + 3 files changed, 380 insertions(+) create mode 100644 packages/Button/Button.css create mode 100644 packages/Button/Button.tsx create mode 100644 packages/Button/index.ts diff --git a/packages/Button/Button.css b/packages/Button/Button.css new file mode 100644 index 0000000..3e5bb1d --- /dev/null +++ b/packages/Button/Button.css @@ -0,0 +1,189 @@ +.van-button { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + padding: 0; + text-align: center; + vertical-align: middle; + -webkit-appearance: none; + -webkit-text-size-adjust: 100%; + height: 44px; + height: var(--button-default-height, 44px); + line-height: 20px; + line-height: var(--button-line-height, 20px); + font-size: 16px; + font-size: var(--button-default-font-size, 16px); + transition: opacity 0.2s; + transition: opacity var(--animation-duration-fast, 0.2s); + border-radius: 2px; + border-radius: var(--button-border-radius, 2px); +} +.van-button::before { + position: absolute; + top: 50%; + left: 50%; + width: 100%; + height: 100%; + border: inherit; + border-radius: inherit; + /* inherit parent's border radius */ + transform: translate(-50%, -50%); + opacity: 0; + content: ' '; + background-color: #000; + background-color: var(--black, #000); + border-color: #000; + border-color: var(--black, #000); +} +.van-button::after { + border-width: 0; +} +.van-button--active::before { + opacity: 0.15; +} +.van-button--unclickable::after { + display: none; +} +.van-button--default { + color: #323233; + color: var(--button-default-color, #323233); + background: #fff; + background: var(--button-default-background-color, #fff); + border: 1px solid #ebedf0; + border: var(--button-border-width, 1px) solid + var(--button-default-border-color, #ebedf0); +} +.van-button--primary { + color: #fff; + color: var(--button-primary-color, #fff); + background: #07c160; + background: var(--button-primary-background-color, #07c160); + border: 1px solid #07c160; + border: var(--button-border-width, 1px) solid + var(--button-primary-border-color, #07c160); +} +.van-button--info { + color: #fff; + color: var(--button-info-color, #fff); + background: #1989fa; + background: var(--button-info-background-color, #1989fa); + border: 1px solid #1989fa; + border: var(--button-border-width, 1px) solid + var(--button-info-border-color, #1989fa); +} +.van-button--danger { + color: #fff; + color: var(--button-danger-color, #fff); + background: #ee0a24; + background: var(--button-danger-background-color, #ee0a24); + border: 1px solid #ee0a24; + border: var(--button-border-width, 1px) solid + var(--button-danger-border-color, #ee0a24); +} +.van-button--warning { + color: #fff; + color: var(--button-warning-color, #fff); + background: #ff976a; + background: var(--button-warning-background-color, #ff976a); + border: 1px solid #ff976a; + border: var(--button-border-width, 1px) solid + var(--button-warning-border-color, #ff976a); +} +.van-button--plain { + background: #fff; + background: var(--button-plain-background-color, #fff); +} +.van-button--plain.van-button--primary { + color: #07c160; + color: var(--button-primary-background-color, #07c160); +} +.van-button--plain.van-button--info { + color: #1989fa; + color: var(--button-info-background-color, #1989fa); +} +.van-button--plain.van-button--danger { + color: #ee0a24; + color: var(--button-danger-background-color, #ee0a24); +} +.van-button--plain.van-button--warning { + color: #ff976a; + color: var(--button-warning-background-color, #ff976a); +} +.van-button--large { + width: 100%; + height: 50px; + height: var(--button-large-height, 50px); +} +.van-button--normal { + padding: 0 15px; + font-size: 14px; + font-size: var(--button-normal-font-size, 14px); +} +.van-button--small { + min-width: 60px; + min-width: var(--button-small-min-width, 60px); + height: 30px; + height: var(--button-small-height, 30px); + padding: 0 8px; + padding: 0 var(--padding-xs, 8px); + font-size: 12px; + font-size: var(--button-small-font-size, 12px); +} +.van-button--mini { + display: inline-block; + min-width: 50px; + min-width: var(--button-mini-min-width, 50px); + height: 22px; + height: var(--button-mini-height, 22px); + font-size: 10px; + font-size: var(--button-mini-font-size, 10px); +} +.van-button--mini + .van-button--mini { + margin-left: 5px; +} +.van-button--block { + display: flex; + width: 100%; +} +.van-button--round { + border-radius: 999px; + border-radius: var(--button-round-border-radius, 999px); +} +.van-button--square { + border-radius: 0; +} +.van-button--disabled { + opacity: 0.5; + opacity: var(--button-disabled-opacity, 0.5); +} +.van-button__text { + display: inline; +} +.van-button__loading-text, +.van-button__icon + .van-button__text:not(:empty) { + margin-left: 4px; +} +.van-button__icon { + min-width: 1em; + line-height: inherit !important; + vertical-align: top; +} +.van-button--hairline { + padding-top: 1px; + border-width: 0; +} +.van-button--hairline::after { + border-color: inherit; + border-width: 1px; + border-radius: calc(2px * 2); + border-radius: calc(var(--button-border-radius, 2px) * 2); +} +.van-button--hairline.van-button--round::after { + border-radius: 999px; + border-radius: var(--button-round-border-radius, 999px); +} +.van-button--hairline.van-button--square::after { + border-radius: 0; +} diff --git a/packages/Button/Button.tsx b/packages/Button/Button.tsx new file mode 100644 index 0000000..c6622fe --- /dev/null +++ b/packages/Button/Button.tsx @@ -0,0 +1,187 @@ +// packages +import React, { + FunctionComponent, + CSSProperties, + ReactElement, + cloneElement, +} from 'react'; +import clsx from 'clsx'; +import { + Button as WechatButton, + ButtonProps as WechatButtonProps, + View, +} from 'remax/wechat'; +// internal +import Loading from '../Loading'; +import withDefaultProps from '../tools/with-default-props-advance'; +import './Button.css'; + +// TODO - 遗漏 id, business-id, dataset 三属性,功能位置,待确认 + +// 默认值填充属性 +interface NeutralButtonProps { + // 移植属性 + plain: boolean; + block: boolean; + round: boolean; + square: boolean; + loading: boolean; + hairline: boolean; + disabled: boolean; + type: 'default' | 'primary' | 'info' | 'warning' | 'danger'; + size: 'normal' | 'large' | 'small' | 'mini'; + // 直接传递 loading 组件,不再进行属性透传 + loader: ReactElement; +} + +// 不包含默认值属性 +interface ExogenousButtonProps { + loadingText?: string; + color?: string; + // 新增属性 + // 直接传递 icon,不再进行属性透传 + icon?: ReactElement; + // host button property + hoverClassName?: string; + // 容器类名,用以覆盖内部 + className?: string; +} + +type HostButtonProps = Pick< + WechatButtonProps, + | 'lang' + | 'sessionFrom' + | 'sendMessageTitle' + | 'sendMessagePath' + | 'sendMessageImg' + | 'appParameter' + | 'showMessageCard' + | 'onGetUserInfo' + | 'onContact' + | 'onGetPhoneNumber' + | 'onError' + | 'onLaunchApp' + | 'onOpenSetting' +>; +type ButtonProps = NeutralButtonProps & ExogenousButtonProps & HostButtonProps; + +// scope +const DefaultButtonProps: NeutralButtonProps = { + type: 'default', + size: 'normal', + plain: false, + block: false, + round: false, + square: false, + loading: false, + hairline: false, + disabled: false, + loader: , +}; + +const deriveHostButtonStyle = (color: string, plain: boolean) => { + const style: CSSProperties = {}; + + if (color) { + style.color = plain ? color : '#ffffff'; + + // Use background instead of backgroundColor to make linear-gradient work + if (!plain) { + style.background = color; + } + + // hide border when color is linear-gradient + if (color.indexOf('gradient') !== -1) { + style.border = 0; + } else { + style.borderColor = color; + } + } + + return style; +}; + +const Button: FunctionComponent = (props) => { + const { + className, + hoverClassName, + type, + size, + block, + round, + plain, + square, + loading, + disabled, + hairline, + color, + icon, + loader, + loadingText, + children, + } = props; + const unclickable = disabled || loading; + const loadingColor = plain + ? color ?? '#c9c9c9' + : type === 'default' + ? '#c9c9c9' + : '#ffffff'; + const loadingHolder = + loader && + cloneElement(loader, { + color: loadingColor, + }); + const loadingTextHolder = loadingText && ( + {loadingText} + ); + const iconHolder = + icon && + cloneElement(icon, { + size: '1.2em', + className: 'van-button__icon', + }); + const staticHolder = {children}; + + // UI property + const classnames = { + container: clsx( + className, + 'van-button', + `van-button--${type}`, + `van-button--${size}`, + { + 'van-button--block': block, + 'van-button--round': round, + 'van-button--plain': plain, + 'van-button--square': square, + 'van-button--loading': loading, + 'van-button--disabled': disabled, + 'van-button--hairline': hairline, + 'van-button--unclickable': unclickable, + 'van-hairline--surround': hairline, + } + ), + hover: clsx(hoverClassName, 'van-button--active'), + }; + const stylesheets: Record<'container', CSSProperties> = { + container: deriveHostButtonStyle(color, plain), + }; + + return ( + + {loading && loadingHolder} + {loading && loadingTextHolder} + {!loading && iconHolder} + {!loading && staticHolder} + + ); +}; + +export default withDefaultProps< + ExogenousButtonProps & HostButtonProps, + NeutralButtonProps +>(DefaultButtonProps)(Button); diff --git a/packages/Button/index.ts b/packages/Button/index.ts new file mode 100644 index 0000000..8371ded --- /dev/null +++ b/packages/Button/index.ts @@ -0,0 +1,4 @@ +/** + * @description - nothing but export component + */ +export { default } from './Button';