diff --git a/@types/accordion.d.ts b/@types/accordion.d.ts index bc952a614..7b4c14d56 100644 --- a/@types/accordion.d.ts +++ b/@types/accordion.d.ts @@ -10,6 +10,10 @@ export interface AtAccordionProps extends AtComponent { icon?: AtIconBaseProps + isAnimation?: boolean + + hasBorder?: boolean + onClick?: CommonEventFunction } diff --git a/docs/markdown/accordion.md b/docs/markdown/accordion.md index 65a60a193..aca3acb69 100644 --- a/docs/markdown/accordion.md +++ b/docs/markdown/accordion.md @@ -23,36 +23,63 @@ import { AtAccordion } from 'taro-ui' ## 一般用法 -说明 +说明: + +* 该组件为受控组件,开发者通过 open 来控制组件开关状态,可通过触发 onClick 函数时修改 open 实现状态切换 :::demo -```html - - - - - - - +```js +import Taro from '@tarojs/taro' +import { View } from '@tarojs/components' +import { AtAccordion, AtList, AtListItem } from 'taro-ui' + +export default class Index extends Taro.Component { + constructor () { + super(...arguments) + this.state = { + open: false, + } + } + handleClick (value) { + this.setState({ + open: value + }) + } + render () { + + return ( + + + + + + + + ) + } +} + + ``` ::: @@ -94,10 +121,12 @@ import { AtAccordion } from 'taro-ui' | ---------- | -------------------------------------- | ------- | ------------------------------------------------------------------- | -------- | | open | 是否默认开启 | Boolean | - | false | | title | 标题 | String | - | - | +| hasBorder | 是否有头部下划线 | Boolean | - | true | +| isAnimation | 是否开启动画 (v2.0.0-beta.3 支持)| Boolean | - | true | | icon | 图标,仅支持 AtIcon 支持的类型,object 属性有 value color size | object | - | - | ## 事件 | 事件名称 | 说明 | 返回参数 | |---------- |-------------- |---------- | -| onClick | 点击头部触发事件 | event | +| onClick | 点击头部触发事件 | (open,event) => void | diff --git a/src/common/utils.ts b/src/common/utils.ts index 568ff94de..3882c6c8f 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -219,6 +219,33 @@ function pxTransform(size) { return Taro.pxTransform(size) } + +function linear(t, b, c, d) { + return (c * t / d) + b +} + +function easeOut(from, to, callback) { + if (from === to || typeof from !== 'number') { + return + } + + const change = to - from + const dur = 1000 + const sTime = +new Date() + const isLarger = to >= from + + function step() { + from = linear(+new Date() - sTime, from, change, dur) + if ((isLarger && from >= to) || (!isLarger && to >= from)) { + callback(to) + return + } + callback(from) + requestAnimationFrame(step) + } + step() +} + export { delay, delayQuerySelector, @@ -229,5 +256,6 @@ export { pxTransform, handleTouchScroll, delayGetClientRect, - delayGetScrollOffset + delayGetScrollOffset, + easeOut } diff --git a/src/components/accordion/index.js b/src/components/accordion/index.js index 5953fde76..d9b6542c2 100644 --- a/src/components/accordion/index.js +++ b/src/components/accordion/index.js @@ -3,62 +3,50 @@ import PropTypes from 'prop-types' import classNames from 'classnames' import { View, Text } from '@tarojs/components' import AtComponent from '../../common/component' -import { delayQuerySelector, uuid } from '../../common/utils' +import { delayQuerySelector, initTestEnv, easeOut } from '../../common/utils' +initTestEnv() + +// 文档 export default class AtAccordion extends AtComponent { constructor () { super(...arguments) - this.bodyHeight = 0 // body 高度 - this.accordionId = this.props.isTest ? 'accordion-AOTU2018' : `accordion-${uuid()}` + this.isCompleted = true this.state = { - isOpen: !!this.props.open, // 组件是否展开 - wrapperHeight: '', + wrapperHeight: '' } } - handleClick (e) { - this.switch() - this.props.onClick(e) - } - - componentDidMount () { - this.initData() - } - - componentWillReceiveProps () { - this.initData() + handleClick = event => { + const { open } = this.props + this.props.onClick(!open, event) } - initData () { - const { isOpen } = this.state - const env = Taro.getEnv() + toggleWithAnimation () { + const { open, isAnimation } = this.props + if (!this.isCompleted || !isAnimation) return - if (env === Taro.ENV_TYPE.WEB) { - setTimeout(() => { - this.accordionRef = document.getElementById(this.accordionId) - this.bodyHeight = this.accordionRef.getBoundingClientRect().height - - this.setState({ - wrapperHeight: isOpen ? this.bodyHeight : 0 - }) - }, 500) - } else if (env === Taro.ENV_TYPE.WEAPP || env === Taro.ENV_TYPE.ALIPAY) { - delayQuerySelector(this, `#${this.accordionId}`) - .then(rect => { - this.bodyHeight = rect[0].height || 0 + this.isCompleted = false + delayQuerySelector(this, '.at-accordion__content', 0) + .then(rect => { + const height = parseInt(rect[0].height) + const startHeight = open ? height : 0 + const endHeight = open ? 0 : height + easeOut(startHeight, endHeight, value => { + if (value === endHeight) { + this.isCompleted = true + } this.setState({ - wrapperHeight: isOpen ? this.bodyHeight : 0 + wrapperHeight: value }) }) - } + }) } - switch () { - const { isOpen } = this.state - this.setState({ - isOpen: !isOpen, - wrapperHeight: isOpen ? 0 : this.bodyHeight - }) + componentWillReceiveProps (nextProps) { + if (nextProps.open !== this.props.open) { + this.toggleWithAnimation() + } } render () { @@ -68,61 +56,61 @@ export default class AtAccordion extends AtComponent { title, icon, hasBorder, + open } = this.props - const { wrapperHeight, isOpen } = this.state + const { wrapperHeight } = this.state + + const isAnimationStart = open && !this.isCompleted && wrapperHeight < 2 - const iconClass = classNames({ + const rootCls = classNames('at-accordion', className) + const iconCls = classNames({ 'at-icon': true, [`at-icon-${icon && icon.value}`]: icon && icon.value, 'at-accordion__icon': true, }) - const headerClass = classNames('at-accordion__header', { + const headerCls = classNames('at-accordion__header', { 'at-accordion__header--noborder': !hasBorder }) - const arrowClass = classNames('at-accordion__arrow', { - 'at-accordion__arrow--folded': !!isOpen + const arrowCls = classNames('at-accordion__arrow', { + 'at-accordion__arrow--folded': !!open + }) + const contentCls = classNames('at-accordion__content', { + 'at-accordion__content--inactive': (!open && this.isCompleted) || isAnimationStart }) - const contentStyle = { - height: `${wrapperHeight}px` - } const iconStyle = { color: (icon && icon.color) || '', fontSize: (icon && `${icon.size}px`) || '', } + const contentStyle = { height: `${wrapperHeight}px` } - return ( - - - {icon && icon.value && } - {title} - - - - - - {this.props.children} + if (this.isCompleted || isAnimationStart) { + contentStyle.height = '' + } + + return + + {icon && icon.value && } + {title} + + - ) + + {this.props.children} + + } } AtAccordion.defaultProps = { - isTest: false, open: false, customStyle: '', className: '', title: '', icon: {}, hasBorder: true, + isAnimation: true, onClick: () => {}, } @@ -135,8 +123,8 @@ AtAccordion.propTypes = { PropTypes.array, PropTypes.string ]), - isTest: PropTypes.bool, open: PropTypes.bool, + isAnimation: PropTypes.bool, title: PropTypes.string, icon: PropTypes.object, hasBorder: PropTypes.bool, diff --git a/src/components/accordion/index.test.js b/src/components/accordion/index.test.js index eaf46cc7c..46252d6c0 100644 --- a/src/components/accordion/index.test.js +++ b/src/components/accordion/index.test.js @@ -6,32 +6,23 @@ import AtAccordion from '../../../.temp/components/accordion' describe('AtAccordion Snap', () => { it('render initial AtAccordion', () => { - const component = renderToString( - - - ) + const component = renderToString() expect(component).toMatchSnapshot() }) it('render AtAccordion -- props title', () => { - const component = renderToString( - - - ) + const component = renderToString() expect(component).toMatchSnapshot() }) it('render AtAccordion -- props open', () => { - const component = renderToString( - - - ) + const component = renderToString() expect(component).toMatchSnapshot() }) it('render AtAccordion -- props icon', () => { const component = renderToString( - + ) diff --git a/src/pages/layout/accordion/index.js b/src/pages/layout/accordion/index.js index ac338970f..5e84d8eff 100644 --- a/src/pages/layout/accordion/index.js +++ b/src/pages/layout/accordion/index.js @@ -9,11 +9,24 @@ export default class CardPage extends Taro.Component { navigationBarTitleText: 'Taro UI' } - onClick (e) { - console.log(e) + constructor () { + super(...arguments) + this.state = { + value1: false, + value2: true, + value3: false + } + } + + onClick (stateName, value) { + this.setState({ + [stateName]: value + }) } render () { + const { value1, value2, value3 } = this.state + return ( @@ -23,8 +36,9 @@ export default class CardPage extends Taro.Component { - + 配置图标 - +