From a969231bbd680c2ad98d39c509157e5d5a40276c Mon Sep 17 00:00:00 2001 From: Jay Fong Date: Wed, 30 Jan 2019 16:27:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintignore | 1 + .vscode/settings.json | 3 + config/index.js | 1 - src/app.tsx | 3 +- src/components/Popup/index.module.scss | 42 +++++++++ src/components/Popup/index.tsx | 117 +++++++++++++++++++++++++ src/components/Sticky/index.tsx | 33 +++---- src/components/Transition/index.tsx | 95 ++++++++++---------- src/components/component.ts | 57 ++++++++++++ src/components/const.ts | 1 + src/components/index.ts | 3 +- src/pages/Popup/Popup.scss | 6 ++ src/pages/Popup/Popup.tsx | 37 ++++++++ src/pages/Transition/Transition.scss | 3 + src/pages/Transition/Transition.tsx | 3 +- src/pages/Transition/X.scss | 3 + src/pages/Transition/X.tsx | 11 +++ 17 files changed, 348 insertions(+), 71 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/components/Popup/index.module.scss create mode 100644 src/components/Popup/index.tsx create mode 100644 src/components/component.ts create mode 100644 src/components/const.ts create mode 100644 src/pages/Popup/Popup.scss create mode 100644 src/pages/Popup/Popup.tsx create mode 100644 src/pages/Transition/Transition.scss create mode 100644 src/pages/Transition/X.scss create mode 100644 src/pages/Transition/X.tsx diff --git a/.eslintignore b/.eslintignore index 6fc7bee..4508516 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ dist +node_modules diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3662b37 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} \ No newline at end of file diff --git a/config/index.js b/config/index.js index c203d97..c746f11 100644 --- a/config/index.js +++ b/config/index.js @@ -68,7 +68,6 @@ const config = { if (!cssNames.has(cssNameKey)) { cssNames.set(cssNameKey, `m-${componentName.toLowerCase() === local ? '' : (`${componentName.toLowerCase()}-`)}${local}`) } - console.log(...arguments) return cssNames.get(cssNameKey) }, }, diff --git a/src/app.tsx b/src/app.tsx index 334c697..68c0fbc 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -6,7 +6,8 @@ class App extends Taro.Component { pages: [ // 'pages/Home/Home', // 'pages/Sticky/Sticky', - 'pages/Transition/Transition', + // 'pages/Transition/X', + 'pages/Popup/Popup', ], window: { navigationBarTitleText: 'DEMO', diff --git a/src/components/Popup/index.module.scss b/src/components/Popup/index.module.scss new file mode 100644 index 0000000..cd08bb7 --- /dev/null +++ b/src/components/Popup/index.module.scss @@ -0,0 +1,42 @@ +@import '../../settings.scss'; + +.popup { + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + display: flex; + .mask { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(black, 0.6); + } + .content { + position: relative; + z-index: 1; + } + &.center { + justify-content: center; + align-items: center; + } + &.top { + justify-content: center; + align-items: flex-start; + } + &.bottom { + justify-content: center; + align-items: flex-end; + } + &.left { + justify-content: flex-start; + align-items: center; + } + &.right { + justify-content: flex-end; + align-items: center; + } +} diff --git a/src/components/Popup/index.tsx b/src/components/Popup/index.tsx new file mode 100644 index 0000000..70a1ebb --- /dev/null +++ b/src/components/Popup/index.tsx @@ -0,0 +1,117 @@ +import Taro from '@tarojs/taro' +import { View } from '@tarojs/components' +import { noop } from 'vtils' +import { component, RequiredProp } from '../component' +import MTransition from '../Transition' +import _ from './index.module.scss' + +type Position = 'center' | 'top' | 'bottom' | 'right' | 'left' + +const positionToTransitionName: { [key in Position]: MTransition['props']['name'] } = { + center: 'fade', + top: 'slideDown', + bottom: 'slideUp', + left: 'slideLeft', + right: 'slideRight', +} + +/** + * 弹出层组件。 + */ +class MPopup extends component({ + props: { + /** 弹出层是否可见 */ + visible: false as any as RequiredProp, + /** 点击遮罩是否可关闭 */ + maskClosable: true as boolean, + /** 动画时长,单位: ms */ + duration: 300 as number, + /** 弹出内容位置 */ + position: 'center' as Position, + /** 可见性变化事件 */ + onVisibleChange: noop as any as RequiredProp<(visible: boolean) => void>, + }, + state: { + /** zIndex 值 */ + zIndex: 0 as number, + /** 弹出层是否显示 */ + display: false as boolean, + }, +}) { + /** 起始 zIndex 值 */ + static startZIndex = 5000 + + /** 计数器 */ + transitionEndCounter = 0 + + componentWillMount() { + this.setState({ + zIndex: MPopup.startZIndex++, + display: this.props.visible, + }) + } + + componentWillReceiveProps(nextProps: MPopup['props']) { + if (nextProps.visible !== this.props.visible) { + this.setState({ + display: true, + }) + } + } + + handleTouchMove = (e: Event) => { + e.stopPropagation() + } + + handleMaskClick = () => { + if (this.props.maskClosable) { + this.props.onVisibleChange(false) + } + } + + handleTransitionEnd = () => { + this.transitionEndCounter++ + if (this.transitionEndCounter >= 2) { + this.transitionEndCounter = 0 + this.setState({ + display: this.props.visible, + }) + } + } + + render() { + const { visible, duration, position } = this.props + const { zIndex, display } = this.state + return ( + + + + + + + {this.props.children} + + + + ) + } +} + +export default MPopup diff --git a/src/components/Sticky/index.tsx b/src/components/Sticky/index.tsx index 46c4631..c20eae1 100644 --- a/src/components/Sticky/index.tsx +++ b/src/components/Sticky/index.tsx @@ -1,31 +1,23 @@ import Taro from '@tarojs/taro' import { View } from '@tarojs/components' import { Disposer } from 'vtils' +import { component } from '../component' +import { CUSTOM_CLASS } from '../const' import _ from './index.module.scss' /** * 吸顶组件。 */ -class MSticky extends Taro.Component<{}, { - /** 是否置顶 */ - fixed: boolean, - /** 内容高度,单位:px */ - contentHeight: number, -}> { - static options: wx.ComponentOptions = { - addGlobalClass: true, - } - +class MSticky extends component({ + state: { + /** 是否置顶 */ + fixed: false as boolean, + /** 内容高度,单位:px */ + contentHeight: 0 as number, + }, +}) { disposer = new Disposer() - constructor() { - super(...arguments) - this.state = { - fixed: false, - contentHeight: 0, - } - } - componentDidMount() { wx.createSelectorQuery() .in(this.$scope) @@ -51,11 +43,12 @@ class MSticky extends Taro.Component<{}, { } render() { + const { className } = this.props const { fixed, contentHeight } = this.state return ( + className={`${_.sticky} ${fixed && _.fixed} ${className} ${CUSTOM_CLASS}`} + style={contentHeight ? { height: `${contentHeight}px` } : {}}> {this.props.children} diff --git a/src/components/Transition/index.tsx b/src/components/Transition/index.tsx index b402697..889a605 100644 --- a/src/components/Transition/index.tsx +++ b/src/components/Transition/index.tsx @@ -1,52 +1,50 @@ import Taro from '@tarojs/taro' import { View } from '@tarojs/components' +import { noop } from 'vtils' +import { component, RequiredProp } from '../component' +import { CUSTOM_CLASS } from '../const' import _ from './index.module.scss' /** * 动画过渡组件。 */ -class MTransition extends Taro.Component<{ - /** 组件是否可见 */ - visible?: boolean, - /** 动画名称 */ - name?: ( - /** 淡入 */ - 'fade' | - /** 上滑淡入 */ - 'fadeUp' | - /** 下滑淡入 */ - 'fadeDown' | - /** 左滑淡入 */ - 'fadeLeft' | - /** 右滑淡入 */ - 'fadeRight' | - /** 上滑进入 */ - 'slideUp' | - /** 下滑进入 */ - 'slideDown' | - /** 左滑进入 */ - 'slideLeft' | - /** 右滑进入 */ - 'slideRight' - ), - /** 动画时长,单位: ms */ - duration?: number, -}, { - /** 动画类型 */ - type: 'Enter' | 'Leave', - /** 是否展示组件 */ - display: boolean, -}> { - static options: wx.ComponentOptions = { - addGlobalClass: true, - } - - static defaultProps: MTransition['props'] = { - visible: true, - name: 'fade', - duration: 1300, - } - +class MTransition extends component({ + props: { + /** 组件是否可见 */ + visible: true as any as RequiredProp, + /** 动画名称 */ + name: 'fade' as ( + /** 淡入 */ + 'fade' | + /** 上滑淡入 */ + 'fadeUp' | + /** 下滑淡入 */ + 'fadeDown' | + /** 左滑淡入 */ + 'fadeLeft' | + /** 右滑淡入 */ + 'fadeRight' | + /** 上滑进入 */ + 'slideUp' | + /** 下滑进入 */ + 'slideDown' | + /** 左滑进入 */ + 'slideLeft' | + /** 右滑进入 */ + 'slideRight' + ), + /** 动画时长,单位: ms */ + duration: 300 as number, + /** 动画结束事件 */ + onTransitionEnd: noop as () => void, + }, + state: { + /** 动画类型 */ + type: 'Enter' as 'Enter' | 'Leave', + /** 是否展示组件 */ + display: false as boolean, + }, +}) { componentWillMount() { if (this.props.visible) { this.setState({ @@ -68,18 +66,21 @@ class MTransition extends Taro.Component<{ } handleAnimationEnd = () => { - this.setState({ - display: this.props.visible, - }) + this.setState( + { display: this.props.visible }, + () => { + this.props.onTransitionEnd() + } + ) } render() { - const { name, duration } = this.props + const { name, duration, className } = this.props const { type, display } = this.state const animation = `${_[`${name}${type}`]} ${duration}ms both` return ( = Pick> +type PartialBy = Omit & Partial> +type RequiredProp = { + __REQUIRED__: true, + __TYPE__: T, +} + +const component = < + P extends StringKeyedObject, + S extends StringKeyedObject, + PP = PartialBy< + { [K in keyof P]: P[K] extends RequiredProp ? T : P[K] }, + { [K in keyof P]: P[K] extends RequiredProp ? never : K }[keyof P] + > & { + /** 应用级别、页面级别的类 */ + className?: string, + /** 组件级别的类 */ + [CUSTOM_CLASS]?: string, + /** 组件本身的类 */ + nativeClass?: string, + }, + SS = S +>( + { + props = {} as any, + state = {} as any, + }: { + props?: P, + state?: S, + } = {} as any +) => ( + class Component extends Taro.Component { + static externalClasses = [CUSTOM_CLASS] + + static options = { + addGlobalClass: true, + } + + static defaultProps = props + + constructor() { + super(...arguments) + this.props = props as any + this.state = state as any + } + } +) + +export { + RequiredProp, + component, +} diff --git a/src/components/const.ts b/src/components/const.ts new file mode 100644 index 0000000..9056db3 --- /dev/null +++ b/src/components/const.ts @@ -0,0 +1 @@ +export const CUSTOM_CLASS = 'custom-class' as 'customClass' diff --git a/src/components/index.ts b/src/components/index.ts index 3eadd2f..9e9b946 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,3 +1,4 @@ -// @index: export { default as M${variable/Block$//:pascal:} } from ${relpath} +// @index(^[A-Z]): export { default as M${variable/Block$//:pascal:} } from ${relpath} +export { default as MPopup } from './Popup' export { default as MSticky } from './Sticky' export { default as MTransition } from './Transition' diff --git a/src/pages/Popup/Popup.scss b/src/pages/Popup/Popup.scss new file mode 100644 index 0000000..db4149b --- /dev/null +++ b/src/pages/Popup/Popup.scss @@ -0,0 +1,6 @@ +.content { + background: white; + padding: 20px; + border-radius: 3px; + width: 60vw; +} diff --git a/src/pages/Popup/Popup.tsx b/src/pages/Popup/Popup.tsx new file mode 100644 index 0000000..18dd961 --- /dev/null +++ b/src/pages/Popup/Popup.tsx @@ -0,0 +1,37 @@ +import './Popup.scss' +import Taro from '@tarojs/taro' +import { View, Button } from '@tarojs/components' +import { range } from 'vtils' +import { MPopup, MSticky } from '../../components' +import { component } from '../../components/component' + +export default class Popup extends component({ + state: { + visible: false as boolean, + }, +}) { + toggleVisible = () => { + this.setState(_ => ({ + visible: !_.visible, + })) + } + + render() { + const { visible } = this.state + return ( + + + + hello + + + + + + {range(0, 100).map(index => ( + {index} + ))} + + ) + } +} diff --git a/src/pages/Transition/Transition.scss b/src/pages/Transition/Transition.scss new file mode 100644 index 0000000..c6dd2c8 --- /dev/null +++ b/src/pages/Transition/Transition.scss @@ -0,0 +1,3 @@ +.red { + color: red; +} diff --git a/src/pages/Transition/Transition.tsx b/src/pages/Transition/Transition.tsx index 669d18a..1e8dcfc 100644 --- a/src/pages/Transition/Transition.tsx +++ b/src/pages/Transition/Transition.tsx @@ -1,3 +1,4 @@ +import './Transition.scss' import Taro from '@tarojs/taro' import { View, Button } from '@tarojs/components' import { range } from 'vtils' @@ -23,7 +24,7 @@ export default class Transition extends Taro.Component<{}, { const { visible } = this.state return ( - + {range(0, 20).map(index => ( diff --git a/src/pages/Transition/X.scss b/src/pages/Transition/X.scss new file mode 100644 index 0000000..c6dd2c8 --- /dev/null +++ b/src/pages/Transition/X.scss @@ -0,0 +1,3 @@ +.red { + color: red; +} diff --git a/src/pages/Transition/X.tsx b/src/pages/Transition/X.tsx new file mode 100644 index 0000000..95be993 --- /dev/null +++ b/src/pages/Transition/X.tsx @@ -0,0 +1,11 @@ +import './X.scss' +import Taro from '@tarojs/taro' +import Transition from './Transition' + +export default class X extends Taro.Component { + render() { + return ( + + ) + } +}