├── .npmrc ├── packages ├── feedback │ ├── .npmrc │ ├── src │ │ ├── index.ts │ │ ├── PropTypes.ts │ │ └── TouchFeedback.tsx │ ├── tsconfig.json │ ├── examples │ │ ├── index.less │ │ ├── index.mdx │ │ └── Example.tsx │ ├── tests │ │ ├── __snapshots__ │ │ │ └── feedback.test.tsx.snap │ │ └── feedback.test.tsx │ ├── types-tests │ │ └── base.tsx │ ├── package.json │ ├── CHANGELOG.md │ └── README.md ├── pull-to-refresh │ ├── .npmrc │ ├── tsconfig.json │ ├── src │ │ ├── index.ts │ │ ├── StaticRenderer.tsx │ │ ├── PropsType.ts │ │ ├── index.less │ │ └── PullToRefresh.tsx │ ├── examples │ │ ├── index.mdx │ │ ├── Example2.tsx │ │ └── Example1.tsx │ ├── types-tests │ │ └── base.tsx │ ├── tests │ │ ├── __snapshots__ │ │ │ └── PullToRefresh.test.tsx.snap │ │ └── PullToRefresh.test.tsx │ ├── package.json │ ├── CHANGELOG.md │ └── README.md └── dialog │ ├── src │ ├── index.ts │ ├── index.less │ ├── IDialogPropTypes.ts │ ├── LazyRenderBox.tsx │ ├── style │ │ ├── mask.less │ │ └── dialog.less │ ├── DialogWrap.tsx │ └── Dialog.tsx │ ├── tsconfig.json │ ├── examples │ ├── index.mdx │ └── Example.tsx │ ├── CHANGELOG.md │ ├── types-tests │ └── base.tsx │ ├── package.json │ ├── tests │ └── dialog.test.tsx │ └── README.md ├── .prettierrc ├── .gitignore ├── lerna.json ├── .fatherrc.ts ├── tsconfig.json ├── .stylelintrc ├── scripts └── publish.js ├── README.md ├── package.json └── .circleci └── config.yml /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /packages/feedback/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /packages/pull-to-refresh/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /packages/feedback/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './TouchFeedback'; 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .doc/ 2 | .docz/ 3 | coverage/ 4 | node_modules/ 5 | dist/ 6 | es/ 7 | lib/ 8 | -------------------------------------------------------------------------------- /packages/dialog/src/index.ts: -------------------------------------------------------------------------------- 1 | import './index.less'; 2 | 3 | export { default } from './DialogWrap'; 4 | -------------------------------------------------------------------------------- /packages/dialog/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/feedback/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "npm", 3 | "packages": ["packages/*"], 4 | "version": "independent" 5 | } 6 | -------------------------------------------------------------------------------- /packages/pull-to-refresh/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/feedback/examples/index.less: -------------------------------------------------------------------------------- 1 | .normal { 2 | color: #000; 3 | } 4 | 5 | .active { 6 | font-size: 40px; 7 | } 8 | -------------------------------------------------------------------------------- /packages/dialog/src/index.less: -------------------------------------------------------------------------------- 1 | @prefixCls: rmc-dialog; 2 | 3 | @import './style/dialog.less'; 4 | @import './style/mask.less'; 5 | -------------------------------------------------------------------------------- /packages/pull-to-refresh/src/index.ts: -------------------------------------------------------------------------------- 1 | import './index.less'; 2 | 3 | import PullToRefresh from './PullToRefresh'; 4 | 5 | export default PullToRefresh; 6 | -------------------------------------------------------------------------------- /packages/dialog/examples/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Dialog 3 | route: /dialog 4 | --- 5 | 6 | import Example from './Example'; 7 | 8 | ### Example 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/feedback/examples/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feedback 3 | route: /feedback 4 | --- 5 | 6 | import Example from './Example'; 7 | 8 | ### Example 9 | 10 | 11 | -------------------------------------------------------------------------------- /.fatherrc.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | esm: { type: 'rollup' }, 3 | cjs: { type: 'rollup' }, 4 | runtimeHelpers: true, 5 | doc: { 6 | typescript: true, 7 | }, 8 | extractCSS: true, 9 | }; 10 | -------------------------------------------------------------------------------- /packages/feedback/tests/__snapshots__/feedback.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`basic base. 1`] = `"
click to active
"`; 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "module": "esnext", 5 | "target": "es5", 6 | "jsx": "preserve", 7 | "noEmit": true 8 | }, 9 | "exclude": ["node_modules"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/feedback/src/PropTypes.ts: -------------------------------------------------------------------------------- 1 | export interface ITouchProps { 2 | disabled?: boolean; 3 | activeClassName?: string; 4 | activeStyle?: any; 5 | children?: any; 6 | } 7 | 8 | export interface ITouchState { 9 | active: boolean; 10 | } 11 | -------------------------------------------------------------------------------- /packages/pull-to-refresh/examples/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: PullToRefresh 3 | route: /pull-to-refresh 4 | --- 5 | 6 | import Example1 from './Example1'; 7 | import Example2 from './Example2'; 8 | 9 | ### Example1 10 | 11 | 12 | 13 | ### Example2 14 | 15 | 16 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-idiomatic-order", 4 | "stylelint-config-standard" 5 | ], 6 | "rules": { 7 | "selector-pseudo-class-no-unknown": [true, { 8 | ignorePseudoClasses: ["global"] 9 | }], 10 | "block-closing-brace-newline-before": null 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/pull-to-refresh/src/StaticRenderer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export default class StaticRenderer extends React.Component { 4 | shouldComponentUpdate(nextProps: any) { 5 | return nextProps.shouldUpdate; 6 | } 7 | render() { 8 | return
{this.props.render()}
; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/dialog/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [0.0.1](https://github.com/react-component/mobile/compare/@antd-mobile/dialog@0.0.1-alpha.0...@antd-mobile/dialog@0.0.1) (2019-06-13) 7 | 8 | **Note:** Version bump only for package @antd-mobile/dialog 9 | -------------------------------------------------------------------------------- /packages/feedback/types-tests/base.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import TouchFeedback from '../src'; 3 | 4 | export default ( 5 | <> 6 | 7 |
111
8 |
9 | 14 |
111
15 |
16 | 17 | ); 18 | -------------------------------------------------------------------------------- /packages/feedback/examples/Example.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import TouchFeedback from '../src'; 3 | 4 | import './index.less'; 5 | 6 | const Example: React.FunctionComponent = () => ( 7 | 8 |
console.log('click div')} 15 | > 16 | click to active 17 |
18 |
19 | ); 20 | 21 | export default Example; 22 | -------------------------------------------------------------------------------- /packages/pull-to-refresh/src/PropsType.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface Indicator { 4 | activate?: React.ReactNode; 5 | deactivate?: React.ReactNode; 6 | release?: React.ReactNode; 7 | finish?: React.ReactNode; 8 | } 9 | 10 | export interface IPropsType { 11 | onRefresh: () => void; 12 | getScrollContainer?: () => React.ReactNode; 13 | direction?: 'down' | 'up'; 14 | refreshing?: boolean; 15 | distanceToRefresh?: number; 16 | indicator?: Indicator; 17 | prefixCls?: string; 18 | className?: string; 19 | damping?: number; 20 | style?: React.CSSProperties; 21 | } 22 | -------------------------------------------------------------------------------- /packages/pull-to-refresh/src/index.less: -------------------------------------------------------------------------------- 1 | @pull-to-refresh: rmc-pull-to-refresh; 2 | 3 | .@{pull-to-refresh} { 4 | &-content { 5 | &-wrapper { 6 | overflow: hidden; 7 | } 8 | 9 | transform-origin: left top 0; 10 | } 11 | 12 | &-transition { 13 | transition: transform 0.3s; 14 | } 15 | 16 | &-indicator { 17 | height: 25px; 18 | color: grey; 19 | line-height: 25px; 20 | text-align: center; 21 | } 22 | 23 | &-down .@{pull-to-refresh}-indicator { 24 | margin-top: -25px; 25 | } 26 | 27 | &-up .@{pull-to-refresh}-indicator { 28 | margin-bottom: -25px; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/pull-to-refresh/types-tests/base.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PullToRefresh from '../src'; 3 | 4 | const noop = () => null; 5 | const style = { width: 100 }; 6 | 7 | export default ( 8 | <> 9 | 10 | 11 | 23 | 111 24 | 25 | 26 | ); 27 | -------------------------------------------------------------------------------- /packages/pull-to-refresh/tests/__snapshots__/PullToRefresh.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`basic all props 1`] = `"
111
pull
"`; 4 | 5 | exports[`basic base. 1`] = `"
pull
Item 1
"`; 6 | -------------------------------------------------------------------------------- /packages/feedback/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@antd-mobile/feedback", 3 | "version": "0.1.2", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/react-component/mobile.git" 7 | }, 8 | "types": "dist/index.d.ts", 9 | "main": "dist/index.js", 10 | "module": "dist/index.esm.js", 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "license": "MIT", 15 | "files": [ 16 | "dist", 17 | "CHANGELOG.md", 18 | "README.md" 19 | ], 20 | "dependencies": { 21 | "@babel/runtime": "^7.4.5", 22 | "classnames": "^2.2.6" 23 | }, 24 | "devDependencies": { 25 | "@types/classnames": "^2.2.8", 26 | "react": "^16.8.6" 27 | }, 28 | "peerDependencies": { 29 | "react": "*" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/dialog/src/IDialogPropTypes.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | 3 | interface IDialogPropTypes { 4 | className?: string; 5 | style?: {}; 6 | mask?: boolean; 7 | afterClose?: () => void; 8 | onClose?: (e: any) => void; 9 | closable?: boolean; 10 | maskClosable?: boolean; 11 | visible?: boolean; 12 | title?: ReactNode; 13 | footer?: ReactNode; 14 | transitionName?: string; 15 | maskTransitionName?: string; 16 | animation?: any; 17 | maskAnimation?: any; 18 | wrapStyle?: {}; 19 | bodyStyle?: {}; 20 | maskStyle?: {}; 21 | prefixCls?: string; 22 | wrapClassName?: string; 23 | onAnimateLeave?: () => void; 24 | zIndex?: number; 25 | maskProps?: any; 26 | wrapProps?: any; 27 | } 28 | 29 | export default IDialogPropTypes; 30 | -------------------------------------------------------------------------------- /packages/pull-to-refresh/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@antd-mobile/pull-to-refresh", 3 | "version": "0.3.2", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/react-component/mobile.git" 7 | }, 8 | "types": "dist/index.d.ts", 9 | "main": "dist/index.js", 10 | "module": "dist/index.esm.js", 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "license": "MIT", 15 | "files": [ 16 | "dist", 17 | "CHANGELOG.md", 18 | "README.md" 19 | ], 20 | "dependencies": { 21 | "@babel/runtime": "^7.4.5", 22 | "classnames": "^2.2.6" 23 | }, 24 | "devDependencies": { 25 | "@types/classnames": "^2.2.8", 26 | "react": "^16.8.6" 27 | }, 28 | "peerDependencies": { 29 | "react": "*" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/dialog/types-tests/base.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Dialog from '../src'; 3 | 4 | const style = { 5 | color: 'red', 6 | }; 7 | const noop = () => null; 8 | 9 | export default ( 10 | <> 11 | 12 | null} 18 | closable 19 | maskClosable 20 | visible 21 | title={
} 22 | footer={
} 23 | transitionName="" 24 | maskTransitionName="" 25 | wrapStyle={style} 26 | bodyStyle={style} 27 | maskStyle={style} 28 | prefixCls="" 29 | wrapClassName="" 30 | onAnimateLeave={noop} 31 | zIndex={1} 32 | > 33 |
111
34 |
35 | 36 | ); 37 | -------------------------------------------------------------------------------- /packages/dialog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@antd-mobile/dialog", 3 | "version": "0.0.1", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/react-component/mobile.git" 7 | }, 8 | "types": "dist/index.d.ts", 9 | "main": "dist/index.js", 10 | "module": "dist/index.esm.js", 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "license": "MIT", 15 | "files": [ 16 | "dist", 17 | "CHANGELOG.md", 18 | "README.md" 19 | ], 20 | "dependencies": { 21 | "@babel/runtime": "^7.4.5", 22 | "classnames": "^2.2.6", 23 | "rc-animate": "2.x" 24 | }, 25 | "devDependencies": { 26 | "@types/classnames": "^2.2.8", 27 | "react": "^16.8.6", 28 | "react-dom": "^16.8.6" 29 | }, 30 | "peerDependencies": { 31 | "react": "*", 32 | "react-dom": "*" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /scripts/publish.js: -------------------------------------------------------------------------------- 1 | const shell = require('shelljs'); 2 | 3 | function exec(str) { 4 | const s = shell.exec(str); 5 | if (s.code !== 0) { 6 | throw s.stderr; 7 | } 8 | } 9 | 10 | const $BRANCH = process.env.BRANCH; 11 | 12 | try { 13 | exec('git config --global user.name "antd-mobile-bot"'); 14 | exec('git config --global user.email "antd-mobile@cloud.alipay.com"'); 15 | 16 | exec('git rev-parse --abbrev-ref HEAD'); 17 | 18 | if ($BRANCH === 'alpha') { 19 | exec('npm run pub:alpha'); 20 | shell.echo('Publish alpha success!!'); 21 | } else { 22 | exec('npm run pub'); 23 | shell.echo('Publish success!!'); 24 | } 25 | exec('git push origin $(git rev-parse --abbrev-ref HEAD):develop'); 26 | shell.echo('Push to github develop success!!'); 27 | } catch (error) { 28 | console.error('Publish error'); 29 | process.exit(1); 30 | } 31 | -------------------------------------------------------------------------------- /packages/dialog/src/LazyRenderBox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface ILazyRenderBoxPropTypes { 4 | className?: string; 5 | visible?: boolean; 6 | hiddenClassName?: string; 7 | role?: string; 8 | style?: {}; 9 | } 10 | 11 | export default class LazyRenderBox extends React.Component< 12 | ILazyRenderBoxPropTypes 13 | > { 14 | shouldComponentUpdate(nextProps) { 15 | return !!nextProps.hiddenClassName || !!nextProps.visible; 16 | } 17 | render() { 18 | let className = this.props.className; 19 | if (!!this.props.hiddenClassName && !this.props.visible) { 20 | className += ` ${this.props.hiddenClassName}`; 21 | } 22 | const props: any = { ...this.props }; 23 | delete props.hiddenClassName; 24 | delete props.visible; 25 | props.className = className; 26 | return
; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/feedback/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [0.1.2](https://github.com/react-component/mobile/compare/@antd-mobile/feedback@0.1.2-alpha.0...@antd-mobile/feedback@0.1.2) (2019-06-13) 7 | 8 | **Note:** Version bump only for package @antd-mobile/feedback 9 | 10 | 11 | 12 | 13 | 14 | ## [0.1.1](https://github.com/react-component/mobile/compare/@antd-mobile/feedback@0.1.1-alpha.0...@antd-mobile/feedback@0.1.1) (2019-06-12) 15 | 16 | **Note:** Version bump only for package @antd-mobile/feedback 17 | 18 | 19 | 20 | 21 | 22 | # 0.1.0 (2019-06-11) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * change start ([d3b0d0a](https://github.com/react-component/mobile/commit/d3b0d0a)) 28 | * style format ([007540a](https://github.com/react-component/mobile/commit/007540a)) 29 | 30 | 31 | ### Features 32 | 33 | * add feedback ([da315f2](https://github.com/react-component/mobile/commit/da315f2)) 34 | -------------------------------------------------------------------------------- /packages/feedback/README.md: -------------------------------------------------------------------------------- 1 | # @antd-mobile/feedback 2 | 3 | --- 4 | 5 | :active pseudo-class with react/preact for mobile 6 | 7 | [![NPM version][npm-image]][npm-url] 8 | 9 | [npm-image]: http://img.shields.io/npm/v/@antd-mobile/feedback.svg?style=flat-square 10 | [npm-url]: http://npmjs.org/package/@antd-mobile/feedback 11 | [download-image]: https://img.shields.io/npm/dm/@antd-mobile/feedback.svg?style=flat-square 12 | [download-url]: https://npmjs.org/package/@antd-mobile/feedback 13 | 14 | ## API 15 | 16 | ### props 17 | 18 | | name | description | type | default | 19 | | --------------- | --------------------------------------------------------------------------- | ------- | ------- | 20 | | disabled | | boolean | false | 21 | | activeClassName | className applied to child when active | string | | 22 | | activeStyle | style applied to child when active (set to false to disable click feedback) | object | - | 23 | -------------------------------------------------------------------------------- /packages/pull-to-refresh/tests/PullToRefresh.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from 'enzyme'; 3 | import { html } from 'cheerio'; 4 | import PullToRefresh from '../src'; 5 | 6 | describe('basic', () => { 7 | it('base.', () => { 8 | const wrapper = render( 9 | null}> 10 |
Item 1
11 |
, 12 | ); 13 | expect(html(wrapper)).toMatchSnapshot(); 14 | }); 15 | it('all props', () => { 16 | const noop = () => null; 17 | const style = { width: 100 }; 18 | 19 | const wrapper = render( 20 | 32 | 111 33 | , 34 | ); 35 | expect(html(wrapper)).toMatchSnapshot(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/dialog/tests/dialog.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from 'enzyme'; 3 | import { html } from 'cheerio'; 4 | import Dialog from '../src'; 5 | 6 | describe('basic', () => { 7 | // Invariant Violation: Portals are not currently supported by the server renderer. Render them conditionally so that they only appear on the client render. 8 | it.skip('base.', () => { 9 | const style = { 10 | color: 'red', 11 | }; 12 | const noop = () => null; 13 | const wrapper = render( 14 | null} 20 | closable 21 | maskClosable 22 | visible 23 | title={
} 24 | footer={
} 25 | transitionName="" 26 | maskTransitionName="" 27 | wrapStyle={style} 28 | bodyStyle={style} 29 | maskStyle={style} 30 | prefixCls="" 31 | wrapClassName="" 32 | onAnimateLeave={noop} 33 | zIndex={1} 34 | > 35 |
111
36 |
, 37 | ); 38 | expect(html(wrapper)).toMatchSnapshot(); 39 | }); 40 | }); 41 | 42 | // TODO: 待补充这个复杂的测试 43 | // https://github.com/react-component/m-dialog/blob/master/tests/index.js 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mobile 2 | 3 | ant design mobile components 4 | 5 | ### Packages 6 | 7 | - @antd-mobile/dialog 8 | - @antd-mobile/feedback 9 | - @antd-mobile/pull-to-refresh 10 | 11 | ### Develop 12 | 13 | 1. Checkout from develop branch 14 | 2. Create a pull request from `your-branch` to develop 15 | 3. If you are block by husky when push to `your-branch`, The `npm run format` may fix it 16 | 4. Angular commit message is taken https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-guidelines 17 | 18 | ### Release 19 | 20 | 1. Create a pull request(pr) from develop to master 21 | 2. Merge the pull request 22 | 3. Have a coffee, and the circleci will release automatically 23 | 4. Note, after release, the develop branch will also update by bot. You can't merge pull request util the branch develop is up-to-date 24 | 25 | ### Release alpha 26 | 27 | As the Release above, but you need merge develop to alpha branch instead of master 28 | 29 | ### Doc/Develop 30 | 31 | You can add examples dir to each package with `*.mdx` as what `pull-to-refresh` done 32 | 33 | It will auto update after Release(or Release alpha) by circleci 34 | 35 | `npm run start` can run examples locally 36 | 37 | ### License 38 | 39 | react-component-mobile is released under the MIT license. 40 | -------------------------------------------------------------------------------- /packages/dialog/src/style/mask.less: -------------------------------------------------------------------------------- 1 | .@{prefixCls} { 2 | &-mask { 3 | position: fixed; 4 | z-index: 1050; 5 | top: 0; 6 | right: 0; 7 | bottom: 0; 8 | left: 0; 9 | height: 100%; 10 | background-color: rgb(55, 55, 55); 11 | background-color: rgba(55, 55, 55, 0.6); 12 | filter: alpha(opacity=50); 13 | 14 | &-hidden { 15 | display: none; 16 | } 17 | } 18 | 19 | .fade-effect() { 20 | animation-duration: 0.3s; 21 | animation-fill-mode: both; 22 | animation-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2); 23 | } 24 | 25 | &-fade-enter, 26 | &-fade-appear { 27 | animation-play-state: paused; 28 | opacity: 0; 29 | .fade-effect(); 30 | } 31 | 32 | &-fade-leave { 33 | .fade-effect(); 34 | 35 | animation-play-state: paused; 36 | } 37 | 38 | &-fade-enter&-fade-enter-active, 39 | &-fade-appear&-fade-appear-active { 40 | animation-name: rcDialogFadeIn; 41 | animation-play-state: running; 42 | } 43 | 44 | &-fade-leave&-fade-leave-active { 45 | animation-name: rcDialogFadeOut; 46 | animation-play-state: running; 47 | } 48 | 49 | @keyframes rcDialogFadeIn { 50 | 0% { 51 | opacity: 0; 52 | } 53 | 54 | 100% { 55 | opacity: 1; 56 | } 57 | } 58 | 59 | @keyframes rcDialogFadeOut { 60 | 0% { 61 | opacity: 1; 62 | } 63 | 64 | 100% { 65 | opacity: 0; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/pull-to-refresh/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [0.3.2](https://github.com/react-component/mobile/compare/@antd-mobile/pull-to-refresh@0.3.2-alpha.0...@antd-mobile/pull-to-refresh@0.3.2) (2019-06-13) 7 | 8 | **Note:** Version bump only for package @antd-mobile/pull-to-refresh 9 | 10 | 11 | 12 | 13 | 14 | ## [0.3.1](https://github.com/react-component/mobile/compare/@antd-mobile/pull-to-refresh@0.3.1-alpha.0...@antd-mobile/pull-to-refresh@0.3.1) (2019-06-12) 15 | 16 | **Note:** Version bump only for package @antd-mobile/pull-to-refresh 17 | 18 | 19 | 20 | 21 | 22 | # [0.3.0](https://github.com/react-component/mobile/compare/@antd-mobile/pull-to-refresh@0.2.3...@antd-mobile/pull-to-refresh@0.3.0) (2019-06-11) 23 | 24 | 25 | ### Features 26 | 27 | * add feedback ([da315f2](https://github.com/react-component/mobile/commit/da315f2)) 28 | 29 | 30 | 31 | 32 | 33 | ## [0.2.3](https://github.com/react-component/mobile/compare/@antd-mobile/pull-to-refresh@0.2.3-alpha.0...@antd-mobile/pull-to-refresh@0.2.3) (2019-06-10) 34 | 35 | **Note:** Version bump only for package @antd-mobile/pull-to-refresh 36 | 37 | 38 | 39 | 40 | 41 | ## [0.2.1](https://github.com/react-component/mobile/compare/v0.1.1...v0.2.1) (2019-06-10) 42 | 43 | **Note:** Version bump only for package @antd-mobile/pull-to-refresh 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-component-mobile", 3 | "private": true, 4 | "scripts": { 5 | "compile": "father build", 6 | "lint": "npm run prettier -- -l && npm run stylelint", 7 | "format": "npm run prettier -- --write && npm run stylelint -- --fix", 8 | "prettier": "prettier \"packages/*/{src,tests,examples,types-tests}/**/*.{ts,tsx}\"", 9 | "stylelint": "stylelint \"packages/*/{src,tests,examples}/**/*.{less,css}\"", 10 | "ci": "npm run lint && npm run types-test && npm run test && npm run compile", 11 | "start": "father doc dev", 12 | "doc:deploy": "father doc build && father doc deploy", 13 | "test": "umi-test --coverage", 14 | "types-test": "tsc", 15 | "codecov": "cat ./coverage/lcov.info | codecov", 16 | "bootstrap": "lerna bootstrap --hoist", 17 | "prepub": "npm run ci", 18 | "prepub:alpha": "npm run ci", 19 | "pub:alpha": "lerna publish prerelease --yes --dist-tag alpha -m \"release: [skip ci]\"", 20 | "pub": "lerna publish --yes --conventional-commits --conventional-graduate --changelog-preset angular -m \"release: [skip ci]\"" 21 | }, 22 | "devDependencies": { 23 | "@types/enzyme": "^3.9.3", 24 | "@types/jest": "^24.0.13", 25 | "@types/react": "^16.8.19", 26 | "@types/react-dom": "^16.8.4", 27 | "codecov": "^3.5.0", 28 | "father": "^2.6.5", 29 | "husky": "^2.4.0", 30 | "lerna": "^3.15.0", 31 | "prettier": "^1.18.2", 32 | "shelljs": "^0.8.3", 33 | "stylelint": "^10.1.0", 34 | "stylelint-config-idiomatic-order": "^7.0.0", 35 | "stylelint-config-standard": "^18.3.0", 36 | "umi-test": "^1.5.10" 37 | }, 38 | "husky": { 39 | "hooks": { 40 | "prepush": "npm run ci" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/pull-to-refresh/examples/Example2.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PullToRefresh from '../src'; 3 | 4 | const Lv: React.FunctionComponent<{ 5 | pullToRefresh: React.FunctionComponentElement; 6 | }> = props => { 7 | const el = React.useRef(null); 8 | const forceUpdate = React.useState()[1]; 9 | 10 | React.useLayoutEffect(() => { 11 | if (props.pullToRefresh) { 12 | forceUpdate(null); 13 | } 14 | }, []); 15 | 16 | let child = props.children; 17 | if (props.pullToRefresh) { 18 | child = React.cloneElement( 19 | props.pullToRefresh, 20 | { 21 | getScrollContainer: () => el.current, 22 | }, 23 | child, 24 | ); 25 | } 26 | return ( 27 |
28 | {child} 29 |
30 | ); 31 | }; 32 | 33 | const Example2: React.FunctionComponent = props => { 34 | const [refreshing, setRefreshing] = React.useState(false); 35 | 36 | React.useEffect(() => { 37 | document.body.style.overflowY = navigator.userAgent.match( 38 | /Android|iPhone|iPad|iPod/i, 39 | ) 40 | ? 'hidden' 41 | : 'auto'; 42 | }, []); 43 | 44 | return ( 45 | { 52 | setRefreshing(true); 53 | setTimeout(() => { 54 | setRefreshing(false); 55 | }, 1000); 56 | }} 57 | /> 58 | } 59 | > 60 | {[1, 2, 3, 4, 5, 6, 7].map(i => ( 61 |
62 | item {i} 63 |
64 | ))} 65 |
66 | ); 67 | }; 68 | 69 | export default Example2; 70 | -------------------------------------------------------------------------------- /packages/pull-to-refresh/examples/Example1.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PullToRefresh from '../src'; 3 | 4 | const Example1: React.FunctionComponent = props => { 5 | const [refreshing, setRefreshing] = React.useState(false); 6 | const [switchContainer, setSwitchContainer] = React.useState(false); 7 | 8 | return ( 9 |
10 | 16 | 17 | {/* todos: 现在如果 getScrollContainer 变化,需要设置新 key 来触发 componentWillUnmount */} 18 | document.body } 23 | : {})} 24 | className="forTest" 25 | direction="down" 26 | refreshing={refreshing} 27 | onRefresh={() => { 28 | setRefreshing(true); 29 | setTimeout(() => { 30 | setRefreshing(false); 31 | }, 1000); 32 | }} 33 | indicator={{ deactivate: '下拉' }} 34 | damping={150} 35 | > 36 | {[1, 2, 3, 4, 5, 6, 7].map(i => ( 37 |
alert(1)} 41 | > 42 | item {i} 43 |
44 | ))} 45 |
46 | 47 |
#qrcode, .highlight{ display: none }' 51 | : '', 52 | }} 53 | /> 54 |
55 | ); 56 | }; 57 | 58 | export default Example1; 59 | -------------------------------------------------------------------------------- /packages/dialog/examples/Example.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import Dialog from '../src'; 4 | 5 | const Example: React.FunctionComponent = props => { 6 | const [visible, setVisible] = React.useState(false); 7 | const [visible2, setVisible2] = React.useState(false); 8 | const [center, setCenter] = React.useState(false); 9 | 10 | const onClick = () => { 11 | setVisible(true); 12 | }; 13 | 14 | const onClose = () => { 15 | setVisible(false); 16 | }; 17 | 18 | const onCenter = e => { 19 | setCenter(e.target.checked); 20 | }; 21 | 22 | const showDialog2 = () => { 23 | setVisible2(true); 24 | }; 25 | 26 | const getDialog1 = () => { 27 | const wrapClassName = center ? 'center' : ''; 28 | 29 | return ( 30 | 37 |

click to show dialog2

38 |
43 |
44 | ); 45 | }; 46 | 47 | const getDialog2 = () => { 48 | return ( 49 | { 54 | setVisible2(false); 55 | }} 56 | > 57 |

basic modal

58 |
63 |
64 | ); 65 | }; 66 | 67 | return ( 68 |
74 | 83 |

84 | 87 |   88 | 92 |

93 | {getDialog1()} 94 | {getDialog2()} 95 |
96 | ); 97 | }; 98 | 99 | export default Example; 100 | -------------------------------------------------------------------------------- /packages/pull-to-refresh/README.md: -------------------------------------------------------------------------------- 1 | # @antd-mobile/pull-to-refresh 2 | 3 | --- 4 | 5 | React Mobile PullToRefresh Component. 6 | 7 | [![NPM version][npm-image]][npm-url] 8 | 9 | [npm-image]: http://img.shields.io/npm/v/@antd-mobile/pull-to-refresh.svg?style=flat-square 10 | [npm-url]: http://npmjs.org/package/@antd-mobile/pull-to-refresh 11 | [download-image]: https://img.shields.io/npm/dm/@antd-mobile/pull-to-refresh.svg?style=flat-square 12 | [download-url]: https://npmjs.org/package/@antd-mobile/pull-to-refresh 13 | 14 | ## Screenshots 15 | 16 | 17 | 18 | ## API 19 | 20 | ### props 21 | 22 | | name | description | type | default | 23 | | ----------------- | ------------------------------------------------------- | ---------- | ----------------------------------------------------------------------------------- | 24 | | direction | pull direction, can be `up` or `down` | String | `down` | 25 | | distanceToRefresh | distance to pull to refresh | number | 50 | 26 | | refreshing | Whether the view should be indicating an active refresh | bool | false | 27 | | onRefresh | Called when the view starts refreshing. | () => void | - | 28 | | indicator | indicator config | Object | `{ activate: 'release', deactivate: 'pull', release: 'loading', finish: 'finish' }` | 29 | | className | additional css class of root dom node | String | - | 30 | | prefixCls | prefix class | String | 'rmc-pull-to-refresh' | 31 | | damping | pull damping, suggest less than 200 | number | 100 | 32 | -------------------------------------------------------------------------------- /packages/feedback/tests/feedback.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render, mount } from 'enzyme'; 3 | import { html } from 'cheerio'; 4 | import TouchFeedback from '../src'; 5 | 6 | describe('basic', () => { 7 | it('base.', () => { 8 | const wrapper = render( 9 | 14 |
15 | click to active 16 |
17 |
, 18 | ); 19 | expect(html(wrapper)).toMatchSnapshot(); 20 | }); 21 | 22 | it('works ok', async () => { 23 | const instance = mount( 24 | 25 |
26 | click to active 27 |
28 |
, 29 | ); 30 | 31 | const d = instance.find(TouchFeedback); 32 | const n = d.find('div').getDOMNode(); 33 | 34 | d.simulate('touchstart'); 35 | expect(d.state()).toHaveProperty('active', true); 36 | expect(n.className).toBe('normal active'); 37 | expect(n.getAttribute('style')).toBe('color: red;'); 38 | 39 | d.simulate('touchend'); 40 | expect(d.state()).toHaveProperty('active', false); 41 | expect(n.className).toBe('normal'); 42 | expect(n.getAttribute('style')).toBe('color: rgb(0, 0, 0);'); 43 | }); 44 | 45 | it('activeStyle false', async () => { 46 | const instance = mount( 47 | 48 |
49 | click to active 50 |
51 |
, 52 | ); 53 | 54 | const d = instance.find(TouchFeedback); 55 | const n = d.find('div').getDOMNode(); 56 | 57 | d.simulate('touchstart'); 58 | expect(n.className).toBe('normal'); 59 | }); 60 | 61 | it('disabled', async () => { 62 | const instance = mount( 63 | 68 |
69 | click to active 70 |
71 |
, 72 | ); 73 | 74 | const d = instance.find(TouchFeedback); 75 | const n = d.find('div').getDOMNode(); 76 | 77 | d.simulate('touchstart'); 78 | expect(n.className).toBe('normal'); 79 | expect(n.getAttribute('style')).toBe('color: rgb(0, 0, 0);'); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /packages/feedback/src/TouchFeedback.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import classNames from 'classnames'; 3 | import { ITouchProps, ITouchState } from './PropTypes'; 4 | 5 | export default class TouchFeedback extends React.Component< 6 | ITouchProps, 7 | ITouchState 8 | > { 9 | static defaultProps = { 10 | disabled: false, 11 | }; 12 | 13 | state = { 14 | active: false, 15 | }; 16 | 17 | componentDidUpdate() { 18 | if (this.props.disabled && this.state.active) { 19 | this.setState({ 20 | active: false, 21 | }); 22 | } 23 | } 24 | 25 | triggerEvent(type, isActive, ev) { 26 | const eventType = `on${type}`; 27 | const { children } = this.props; 28 | 29 | if (children.props[eventType]) { 30 | children.props[eventType](ev); 31 | } 32 | if (isActive !== this.state.active) { 33 | this.setState({ 34 | active: isActive, 35 | }); 36 | } 37 | } 38 | 39 | onTouchStart = e => { 40 | this.triggerEvent('TouchStart', true, e); 41 | }; 42 | 43 | onTouchMove = e => { 44 | this.triggerEvent('TouchMove', false, e); 45 | }; 46 | 47 | onTouchEnd = e => { 48 | this.triggerEvent('TouchEnd', false, e); 49 | }; 50 | 51 | onTouchCancel = e => { 52 | this.triggerEvent('TouchCancel', false, e); 53 | }; 54 | 55 | onMouseDown = e => { 56 | // pc simulate mobile 57 | this.triggerEvent('MouseDown', true, e); 58 | }; 59 | 60 | onMouseUp = e => { 61 | this.triggerEvent('MouseUp', false, e); 62 | }; 63 | 64 | onMouseLeave = e => { 65 | this.triggerEvent('MouseLeave', false, e); 66 | }; 67 | 68 | render() { 69 | const { children, disabled, activeClassName, activeStyle } = this.props; 70 | 71 | const events = disabled 72 | ? undefined 73 | : { 74 | onTouchStart: this.onTouchStart, 75 | onTouchMove: this.onTouchMove, 76 | onTouchEnd: this.onTouchEnd, 77 | onTouchCancel: this.onTouchCancel, 78 | onMouseDown: this.onMouseDown, 79 | onMouseUp: this.onMouseUp, 80 | onMouseLeave: this.onMouseLeave, 81 | }; 82 | 83 | const child = React.Children.only(children); 84 | 85 | if (!disabled && this.state.active) { 86 | let { style, className } = child.props; 87 | 88 | if (activeStyle !== false) { 89 | if (activeStyle) { 90 | style = { ...style, ...activeStyle }; 91 | } 92 | className = classNames(className, activeClassName); 93 | } 94 | 95 | return React.cloneElement(child, { 96 | className, 97 | style, 98 | ...events, 99 | }); 100 | } 101 | 102 | return React.cloneElement(child, events); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /packages/dialog/README.md: -------------------------------------------------------------------------------- 1 | # @antd-mobile/dialog 2 | 3 | --- 4 | 5 | react dialog component for mobile 6 | 7 | [![NPM version][npm-image]][npm-url] 8 | 9 | [npm-image]: http://img.shields.io/npm/v/@antd-mobile/dialog.svg?style=flat-square 10 | [npm-url]: http://npmjs.org/package/@antd-mobile/dialog 11 | [download-image]: https://img.shields.io/npm/dm/@antd-mobile/dialog.svg?style=flat-square 12 | [download-url]: https://npmjs.org/package/@antd-mobile/dialog 13 | 14 | ## Screenshot 15 | 16 | 17 | 18 | ## API 19 | 20 | ### props 21 | 22 | | name | description | type | default | 23 | | ------------------ | --------------------------------------------------- | ------------- | ------------- | 24 | | prefixCls | The dialog dom node's prefixCls | String | `rmc-dialog` | 25 | | className | additional className for dialog | String | | 26 | | wrapClassName | additional className for dialog wrap | String | | 27 | | style | Root style for dialog element.Such as width, height | Object | {} | 28 | | zIndex | z-index | Number | | 29 | | bodyStyle | body style for dialog body element.Such as height | Object | {} | 30 | | maskStyle | style for mask element. | Object | {} | 31 | | visible | current dialog's visible status | Boolean | false | 32 | | animation | part of dialog animation css class name | String | | 33 | | maskAnimation | part of dialog's mask animation css class name | String | | 34 | | transitionName | dialog animation css class name | String | | 35 | | maskTransitionName | mask animation css class name | String | | 36 | | title | Title of the dialog | String | React.Element | | 37 | | footer | footer of the dialog | React.Element | | 38 | | closable | whether show close button | Boolean | true | 39 | | mask | whether show mask | Boolean | true | 40 | | maskClosable | whether click mask to close | Boolean | true | 41 | | onClose | called when click close button or mask | function | | 42 | -------------------------------------------------------------------------------- /packages/dialog/src/style/dialog.less: -------------------------------------------------------------------------------- 1 | .@{prefixCls} { 2 | position: relative; 3 | width: auto; 4 | margin: 10px; 5 | 6 | &-wrap { 7 | position: fixed; 8 | z-index: 1050; 9 | top: 0; 10 | right: 0; 11 | bottom: 0; 12 | left: 0; 13 | overflow: auto; 14 | outline: 0; 15 | -webkit-overflow-scrolling: touch; 16 | } 17 | 18 | &-title { 19 | margin: 0; 20 | font-size: 14px; 21 | font-weight: bold; 22 | line-height: 21px; 23 | } 24 | 25 | &-content { 26 | position: relative; 27 | border: none; 28 | background-clip: padding-box; 29 | background-color: #fff; 30 | border-radius: 6px 6px; 31 | } 32 | 33 | &-close { 34 | position: absolute; 35 | top: 12px; 36 | right: 20px; 37 | border: 0; 38 | background: transparent; 39 | color: #000; 40 | cursor: pointer; 41 | filter: alpha(opacity=20); 42 | font-size: 21px; 43 | font-weight: 700; 44 | line-height: 1; 45 | opacity: 0.2; 46 | text-decoration: none; 47 | text-shadow: 0 1px 0 #fff; 48 | 49 | &-x::after { 50 | content: '×'; 51 | } 52 | 53 | &:hover { 54 | filter: alpha(opacity=100); 55 | opacity: 1; 56 | text-decoration: none; 57 | } 58 | } 59 | 60 | &-header { 61 | padding: 13px 20px 14px 20px; 62 | border-bottom: 1px solid #e9e9e9; 63 | background: #fff; 64 | border-radius: 5px 5px 0 0; 65 | color: #666; 66 | } 67 | 68 | &-body { 69 | padding: 20px; 70 | } 71 | 72 | &-footer { 73 | padding: 10px 20px 10px 10px; 74 | border-top: 1px solid #e9e9e9; 75 | border-radius: 0 0 5px 5px; 76 | text-align: right; 77 | } 78 | 79 | .effect() { 80 | animation-duration: 0.3s; 81 | animation-fill-mode: both; 82 | } 83 | 84 | &-zoom-enter, 85 | &-zoom-appear { 86 | animation-play-state: paused; 87 | animation-timing-function: cubic-bezier(0.08, 0.82, 0.17, 1); 88 | opacity: 0; 89 | .effect(); 90 | } 91 | 92 | &-zoom-leave { 93 | .effect(); 94 | 95 | animation-play-state: paused; 96 | animation-timing-function: cubic-bezier(0.6, 0.04, 0.98, 0.34); 97 | } 98 | 99 | &-zoom-enter&-zoom-enter-active, 100 | &-zoom-appear&-zoom-appear-active { 101 | animation-name: rcDialogZoomIn; 102 | animation-play-state: running; 103 | } 104 | 105 | &-zoom-leave&-zoom-leave-active { 106 | animation-name: rcDialogZoomOut; 107 | animation-play-state: running; 108 | } 109 | 110 | @keyframes rcDialogZoomIn { 111 | 0% { 112 | opacity: 0; 113 | transform: scale(0, 0); 114 | } 115 | 116 | 100% { 117 | opacity: 1; 118 | transform: scale(1, 1); 119 | } 120 | } 121 | 122 | @keyframes rcDialogZoomOut { 123 | 0% { 124 | transform: scale(1, 1); 125 | } 126 | 127 | 100% { 128 | opacity: 0; 129 | transform: scale(0, 0); 130 | } 131 | } 132 | } 133 | 134 | @media (min-width: 768px) { 135 | .@{prefixCls} { 136 | width: 600px; 137 | margin: 30px auto; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /packages/dialog/src/DialogWrap.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import Dialog from './Dialog'; 4 | import IDialogPropTypes from './IDialogPropTypes'; 5 | 6 | function noop() {} 7 | 8 | const IS_REACT_16 = !!ReactDOM.createPortal; 9 | 10 | const CAN_USE_DOM = !!( 11 | typeof window !== 'undefined' && 12 | window.document && 13 | window.document.createElement 14 | ); 15 | 16 | export default class DialogWrap extends React.Component { 17 | static defaultProps = { 18 | visible: false, 19 | prefixCls: 'rmc-dialog', 20 | onClose: noop, 21 | }; 22 | 23 | _component: any; 24 | container: any; 25 | 26 | componentDidMount() { 27 | if (this.props.visible) { 28 | this.componentDidUpdate(); 29 | } 30 | } 31 | 32 | shouldComponentUpdate({ visible }) { 33 | return !!(this.props.visible || visible); 34 | } 35 | 36 | componentWillUnmount() { 37 | if (this.props.visible) { 38 | if (!IS_REACT_16) { 39 | this.renderDialog(false); 40 | } else { 41 | // TODO for react@16 createPortal animation 42 | this.removeContainer(); 43 | } 44 | } else { 45 | this.removeContainer(); 46 | } 47 | } 48 | 49 | componentDidUpdate() { 50 | if (!IS_REACT_16) { 51 | this.renderDialog(this.props.visible); 52 | } 53 | } 54 | 55 | saveRef = node => { 56 | if (IS_REACT_16) { 57 | this._component = node; 58 | } 59 | }; 60 | 61 | getComponent = visible => { 62 | const props = { ...this.props }; 63 | ['visible', 'onAnimateLeave'].forEach(key => { 64 | if (props.hasOwnProperty(key)) { 65 | delete props[key]; 66 | } 67 | }); 68 | return ( 69 | 75 | ); 76 | }; 77 | 78 | removeContainer = () => { 79 | if (this.container) { 80 | if (!IS_REACT_16) { 81 | ReactDOM.unmountComponentAtNode(this.container); 82 | } 83 | (this.container as any).parentNode.removeChild(this.container); 84 | this.container = null; 85 | } 86 | }; 87 | 88 | getContainer = () => { 89 | if (!this.container) { 90 | const container = document.createElement('div'); 91 | const containerId = `${ 92 | this.props.prefixCls 93 | }-container-${new Date().getTime()}`; 94 | container.setAttribute('id', containerId); 95 | document.body.appendChild(container); 96 | this.container = container; 97 | } 98 | return this.container; 99 | }; 100 | 101 | renderDialog(visible) { 102 | ReactDOM.unstable_renderSubtreeIntoContainer( 103 | this, 104 | this.getComponent(visible), 105 | this.getContainer(), 106 | ); 107 | } 108 | 109 | render() { 110 | if (!CAN_USE_DOM) { 111 | return null; 112 | } 113 | 114 | const { visible } = this.props; 115 | if (IS_REACT_16 && (visible || this._component)) { 116 | return (ReactDOM as any).createPortal( 117 | this.getComponent(visible), 118 | this.getContainer(), 119 | ); 120 | } 121 | return null as any; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | working_directory: &working_directory ~/workspace 3 | 4 | base: &base 5 | working_directory: *working_directory 6 | docker: 7 | - image: circleci/node:12 8 | steps: 9 | - checkout 10 | - restore_cache: 11 | key: rmc-{{ .Environment.CIRCLE_JOB }}-{{ .Branch }}-{{ checksum "package.json" }} 12 | - run: 13 | name: Install dependencies 14 | command: npm i 15 | - run: 16 | name: Lerna Bootstrap 17 | command: npm run bootstrap 18 | - run: 19 | name: Build 20 | command: npm run ci 21 | - run: 22 | name: Report coverage 23 | command: npm run codecov 24 | - save_cache: 25 | key: rmc-{{ .Environment.CIRCLE_JOB }}-{{ .Branch }}-{{ checksum "package.json" }} 26 | paths: 27 | - 'node_modules' 28 | 29 | jobs: 30 | build_node_10: 31 | <<: *base 32 | docker: 33 | - image: circleci/node:10 34 | 35 | build_node_12: 36 | <<: *base 37 | docker: 38 | - image: circleci/node:12 39 | 40 | publish_alpha: 41 | <<: *base 42 | steps: 43 | - checkout 44 | - restore_cache: 45 | key: rmc-{{ .Environment.CIRCLE_JOB }}-{{ .Branch }}-{{ checksum "package.json" }} 46 | - run: 47 | name: Install dependencies 48 | command: npm i 49 | - run: 50 | name: Lerna Bootstrap 51 | command: npm run bootstrap 52 | - run: 53 | name: Publish alpha 54 | command: | 55 | set -o errexit 56 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc 57 | BRANCH=alpha node ./scripts/publish.js 58 | - run: 59 | name: Report coverage 60 | command: npm run codecov 61 | - run: 62 | name: Doc 63 | command: npm run doc:deploy 64 | - save_cache: 65 | key: rmc-{{ .Environment.CIRCLE_JOB }}-{{ .Branch }}-{{ checksum "package.json" }} 66 | paths: 67 | - 'node_modules' 68 | 69 | publish: 70 | <<: *base 71 | steps: 72 | - checkout 73 | - restore_cache: 74 | key: rmc-{{ .Environment.CIRCLE_JOB }}-{{ .Branch }}-{{ checksum "package.json" }} 75 | - run: 76 | name: Install dependencies 77 | command: npm i 78 | - run: 79 | name: Lerna Bootstrap 80 | command: npm run bootstrap 81 | - run: 82 | name: Publish 83 | command: | 84 | set -o errexit 85 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc 86 | node ./scripts/publish.js 87 | - run: 88 | name: Report coverage 89 | command: npm run codecov 90 | - run: 91 | name: Doc 92 | command: npm run doc:deploy 93 | - save_cache: 94 | key: rmc-{{ .Environment.CIRCLE_JOB }}-{{ .Branch }}-{{ checksum "package.json" }} 95 | paths: 96 | - 'node_modules' 97 | 98 | workflows: 99 | version: 2 100 | build: 101 | jobs: 102 | - build_node_10 103 | - build_node_12 104 | 105 | release-alpha: 106 | jobs: 107 | - publish_alpha: 108 | filters: 109 | branches: 110 | only: 111 | - alpha 112 | 113 | release: 114 | jobs: 115 | - publish: 116 | filters: 117 | branches: 118 | only: 119 | - master 120 | -------------------------------------------------------------------------------- /packages/dialog/src/Dialog.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Animate from 'rc-animate'; 3 | import LazyRenderBox from './LazyRenderBox'; 4 | import IDialogPropTypes from './IDialogPropTypes'; 5 | 6 | function noop() {} 7 | 8 | export default class Dialog extends React.Component { 9 | static defaultProps = { 10 | afterClose: noop, 11 | className: '', 12 | mask: true, 13 | visible: false, 14 | closable: true, 15 | maskClosable: true, 16 | prefixCls: 'rmc-dialog', 17 | onClose: noop, 18 | }; 19 | 20 | dialogRef: any; 21 | bodyRef: any; 22 | headerRef: any; 23 | footerRef: any; 24 | wrapRef: any; 25 | 26 | componentWillUnmount() { 27 | // fix: react@16 no dismissing animation 28 | document.body.style.overflow = ''; 29 | if (this.wrapRef) { 30 | this.wrapRef.style.display = 'none'; 31 | } 32 | } 33 | 34 | getZIndexStyle() { 35 | const style: any = {}; 36 | const props = this.props; 37 | if (props.zIndex !== undefined) { 38 | style.zIndex = props.zIndex; 39 | } 40 | return style; 41 | } 42 | 43 | getWrapStyle() { 44 | const wrapStyle = this.props.wrapStyle || {}; 45 | return { ...this.getZIndexStyle(), ...wrapStyle }; 46 | } 47 | 48 | getMaskStyle() { 49 | const maskStyle = this.props.maskStyle || {}; 50 | return { ...this.getZIndexStyle(), ...maskStyle }; 51 | } 52 | 53 | getMaskTransitionName() { 54 | const props = this.props; 55 | let transitionName = props.maskTransitionName; 56 | const animation = props.maskAnimation; 57 | if (!transitionName && animation) { 58 | transitionName = `${props.prefixCls}-${animation}`; 59 | } 60 | return transitionName; 61 | } 62 | 63 | getTransitionName() { 64 | const props = this.props; 65 | let transitionName = props.transitionName; 66 | const animation = props.animation; 67 | if (!transitionName && animation) { 68 | transitionName = `${props.prefixCls}-${animation}`; 69 | } 70 | return transitionName; 71 | } 72 | 73 | getMaskElement() { 74 | const props = this.props; 75 | let maskElement; 76 | if (props.mask) { 77 | const maskTransition = this.getMaskTransitionName(); 78 | maskElement = ( 79 | 87 | ); 88 | if (maskTransition) { 89 | maskElement = ( 90 | 97 | {maskElement} 98 | 99 | ); 100 | } 101 | } 102 | return maskElement; 103 | } 104 | 105 | getDialogElement = () => { 106 | const props = this.props; 107 | const closable = props.closable; 108 | const prefixCls = props.prefixCls; 109 | 110 | let footer; 111 | if (props.footer) { 112 | footer = ( 113 |
(this.footerRef = el)} 116 | > 117 | {props.footer} 118 |
119 | ); 120 | } 121 | 122 | let header; 123 | if (props.title) { 124 | header = ( 125 |
(this.headerRef = el)} 128 | > 129 |
{props.title}
130 |
131 | ); 132 | } 133 | 134 | let closer; 135 | if (closable) { 136 | closer = ( 137 | 144 | ); 145 | } 146 | 147 | const transitionName = this.getTransitionName(); 148 | const dialogElement = ( 149 | (this.dialogRef = el)} 153 | style={props.style || {}} 154 | className={`${prefixCls} ${props.className || ''}`} 155 | visible={props.visible} 156 | > 157 |
158 | {closer} 159 | {header} 160 |
(this.bodyRef = el)} 164 | > 165 | {props.children} 166 |
167 | {footer} 168 |
169 |
170 | ); 171 | return ( 172 | 181 | {dialogElement} 182 | 183 | ); 184 | }; 185 | 186 | onAnimateAppear = () => { 187 | document.body.style.overflow = 'hidden'; 188 | }; 189 | 190 | onAnimateLeave = () => { 191 | document.body.style.overflow = ''; 192 | if (this.wrapRef) { 193 | this.wrapRef.style.display = 'none'; 194 | } 195 | if (this.props.onAnimateLeave) { 196 | this.props.onAnimateLeave(); 197 | } 198 | if (this.props.afterClose) { 199 | this.props.afterClose(); 200 | } 201 | }; 202 | 203 | close = e => { 204 | if (this.props.onClose) { 205 | this.props.onClose(e); 206 | } 207 | }; 208 | 209 | onMaskClick = e => { 210 | if (e.target === e.currentTarget) { 211 | this.close(e); 212 | } 213 | }; 214 | 215 | render() { 216 | const { props } = this; 217 | const { prefixCls, maskClosable } = props; 218 | const style = this.getWrapStyle(); 219 | if (props.visible) { 220 | style.display = null; 221 | } 222 | return ( 223 |
224 | {this.getMaskElement()} 225 |
(this.wrapRef = el)} 228 | onClick={maskClosable ? this.onMaskClick : undefined} 229 | role="dialog" 230 | aria-labelledby={props.title} 231 | style={style} 232 | {...props.wrapProps} 233 | > 234 | {this.getDialogElement()} 235 |
236 |
237 | ); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /packages/pull-to-refresh/src/PullToRefresh.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import classNames from 'classnames'; 3 | import { IPropsType } from './PropsType'; 4 | import StaticRenderer from './StaticRenderer'; 5 | 6 | function setTransform(nodeStyle: any, value: any) { 7 | nodeStyle.transform = value; 8 | nodeStyle.webkitTransform = value; 9 | nodeStyle.MozTransform = value; 10 | } 11 | 12 | const isWebView = 13 | typeof navigator !== 'undefined' && 14 | /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(navigator.userAgent); 15 | const DOWN = 'down'; 16 | const UP = 'up'; 17 | const INDICATOR = { 18 | activate: 'release', 19 | deactivate: 'pull', 20 | release: 'loading', 21 | finish: 'finish', 22 | }; 23 | 24 | let supportsPassive = false; 25 | try { 26 | const opts = Object.defineProperty({}, 'passive', { 27 | get() { 28 | supportsPassive = true; 29 | }, 30 | }); 31 | window.addEventListener('test', null as any, opts); 32 | } catch (e) { 33 | // empty 34 | } 35 | const willPreventDefault = supportsPassive ? { passive: false } : false; 36 | // const willNotPreventDefault = supportsPassive ? { passive: true } : false; 37 | 38 | type ICurrSt = 'activate' | 'deactivate' | 'release' | 'finish'; 39 | 40 | interface IState { 41 | currSt: ICurrSt; 42 | dragOnEdge: boolean; 43 | } 44 | 45 | export default class PullToRefresh extends React.Component { 46 | static defaultProps = { 47 | prefixCls: 'rmc-pull-to-refresh', 48 | getScrollContainer: () => undefined, 49 | direction: DOWN, 50 | distanceToRefresh: 25, 51 | damping: 100, 52 | indicator: INDICATOR, 53 | }; 54 | 55 | // https://github.com/yiminghe/zscroller/blob/2d97973287135745818a0537712235a39a6a62a1/src/Scroller.js#L355 56 | // currSt: `activate` / `deactivate` / `release` / `finish` 57 | state = { 58 | currSt: 'deactivate' as ICurrSt, 59 | dragOnEdge: false, 60 | }; 61 | 62 | containerRef: any; 63 | contentRef: any; 64 | _to: any; 65 | _ScreenY: any; 66 | _startScreenY: any; 67 | _lastScreenY: any; 68 | _timer: any; 69 | 70 | _isMounted = false; 71 | 72 | shouldUpdateChildren = false; 73 | 74 | shouldComponentUpdate(nextProps: any) { 75 | this.shouldUpdateChildren = this.props.children !== nextProps.children; 76 | return true; 77 | } 78 | 79 | componentDidUpdate(prevProps: any) { 80 | if ( 81 | prevProps === this.props || 82 | prevProps.refreshing === this.props.refreshing 83 | ) { 84 | return; 85 | } 86 | // triggerPullToRefresh 需要尽可能减少 setState 次数 87 | this.triggerPullToRefresh(); 88 | } 89 | 90 | componentDidMount() { 91 | // `getScrollContainer` most likely return React.Node at the next tick. Need setTimeout 92 | setTimeout(() => { 93 | this.init(this.props.getScrollContainer() || this.containerRef); 94 | this.triggerPullToRefresh(); 95 | this._isMounted = true; 96 | }); 97 | } 98 | 99 | componentWillUnmount() { 100 | // Should have no setTimeout here! 101 | this.destroy(this.props.getScrollContainer() || this.containerRef); 102 | } 103 | 104 | triggerPullToRefresh = () => { 105 | // 在初始化时、用代码 自动 触发 pullToRefresh 106 | // 注意:当 direction 为 up 时,当 visible length < content length 时、则看不到效果 107 | // 添加this._isMounted的判断,否则组建一实例化,currSt就会是finish 108 | if (!this.state.dragOnEdge && this._isMounted) { 109 | if (this.props.refreshing) { 110 | if (this.props.direction === UP) { 111 | this._lastScreenY = -this.props.distanceToRefresh - 1; 112 | } 113 | if (this.props.direction === DOWN) { 114 | this._lastScreenY = this.props.distanceToRefresh + 1; 115 | } 116 | // change dom need after setState 117 | this.setState({ currSt: 'release' }, () => 118 | this.setContentStyle(this._lastScreenY), 119 | ); 120 | } else { 121 | this.setState({ currSt: 'finish' }, () => this.reset()); 122 | } 123 | } 124 | }; 125 | 126 | init = (ele: any) => { 127 | if (!ele) { 128 | // like return in destroy fn ???!! 129 | return; 130 | } 131 | this._to = { 132 | touchstart: this.onTouchStart.bind(this, ele), 133 | touchmove: this.onTouchMove.bind(this, ele), 134 | touchend: this.onTouchEnd.bind(this, ele), 135 | touchcancel: this.onTouchEnd.bind(this, ele), 136 | }; 137 | Object.keys(this._to).forEach(key => { 138 | ele.addEventListener(key, this._to[key], willPreventDefault); 139 | }); 140 | }; 141 | 142 | destroy = (ele: any) => { 143 | if (!this._to || !ele) { 144 | // componentWillUnmount fire before componentDidMount, like forceUpdate ???!! 145 | return; 146 | } 147 | Object.keys(this._to).forEach(key => { 148 | ele.removeEventListener(key, this._to[key]); 149 | }); 150 | }; 151 | 152 | onTouchStart = (_ele: any, e: any) => { 153 | this._ScreenY = this._startScreenY = e.touches[0].screenY; 154 | // 一开始 refreshing 为 true 时 this._lastScreenY 有值 155 | this._lastScreenY = this._lastScreenY || 0; 156 | }; 157 | 158 | isEdge = (ele: any, direction: string) => { 159 | const container = this.props.getScrollContainer(); 160 | if (container && container === document.body) { 161 | // In chrome61 `document.body.scrollTop` is invalid 162 | const scrollNode = document.scrollingElement 163 | ? document.scrollingElement 164 | : document.body; 165 | if (direction === UP) { 166 | return ( 167 | scrollNode.scrollHeight - scrollNode.scrollTop <= window.innerHeight 168 | ); 169 | } 170 | if (direction === DOWN) { 171 | return scrollNode.scrollTop <= 0; 172 | } 173 | } 174 | if (direction === UP) { 175 | return ele.scrollHeight - ele.scrollTop === ele.clientHeight; 176 | } 177 | if (direction === DOWN) { 178 | return ele.scrollTop <= 0; 179 | } 180 | // 补全 branch, test 才过的了,但是实际上代码永远不会走到这里,这里为了保证代码的一致性,返回 undefined 181 | return undefined; 182 | }; 183 | 184 | damping = (dy: number): number => { 185 | if (Math.abs(this._lastScreenY) > this.props.damping) { 186 | return 0; 187 | } 188 | 189 | const ratio = 190 | Math.abs(this._ScreenY - this._startScreenY) / window.screen.height; 191 | dy *= (1 - ratio) * 0.6; 192 | 193 | return dy; 194 | }; 195 | 196 | onTouchMove = (ele: any, e: any) => { 197 | // 使用 pageY 对比有问题 198 | const _screenY = e.touches[0].screenY; 199 | const { direction } = this.props; 200 | 201 | // 拖动方向不符合的不处理 202 | if ( 203 | (direction === UP && this._startScreenY < _screenY) || 204 | (direction === DOWN && this._startScreenY > _screenY) 205 | ) { 206 | return; 207 | } 208 | 209 | if (this.isEdge(ele, direction)) { 210 | if (!this.state.dragOnEdge) { 211 | // 当用户开始往上滑的时候isEdge还是false的话,会导致this._ScreenY不是想要的,只有当isEdge为true时,再上滑,才有意义 212 | // 下面这行代码解决了上面这个问题 213 | this._ScreenY = this._startScreenY = e.touches[0].screenY; 214 | 215 | this.setState({ dragOnEdge: true }); 216 | } 217 | e.preventDefault(); 218 | // add stopPropagation with fastclick will trigger content onClick event. why? 219 | // ref https://github.com/ant-design/ant-design-mobile/issues/2141 220 | // e.stopPropagation(); 221 | 222 | const _diff = Math.round(_screenY - this._ScreenY); 223 | this._ScreenY = _screenY; 224 | this._lastScreenY += this.damping(_diff); 225 | 226 | this.setContentStyle(this._lastScreenY); 227 | 228 | if (Math.abs(this._lastScreenY) < this.props.distanceToRefresh) { 229 | if (this.state.currSt !== 'deactivate') { 230 | // console.log('back to the distance'); 231 | this.setState({ currSt: 'deactivate' }); 232 | } 233 | } else { 234 | if (this.state.currSt === 'deactivate') { 235 | // console.log('reach to the distance'); 236 | this.setState({ currSt: 'activate' }); 237 | } 238 | } 239 | 240 | // https://github.com/ant-design/ant-design-mobile/issues/573#issuecomment-339560829 241 | // iOS UIWebView issue, It seems no problem in WKWebView 242 | if (isWebView && e.changedTouches[0].clientY < 0) { 243 | this.onTouchEnd(); 244 | } 245 | } 246 | }; 247 | 248 | onTouchEnd = () => { 249 | if (this.state.dragOnEdge) { 250 | this.setState({ dragOnEdge: false }); 251 | } 252 | if (this.state.currSt === 'activate') { 253 | this.setState({ currSt: 'release' }); 254 | this._timer = setTimeout(() => { 255 | if (!this.props.refreshing) { 256 | this.setState({ currSt: 'finish' }, () => this.reset()); 257 | } 258 | this._timer = undefined; 259 | }, 1000); 260 | this.props.onRefresh(); 261 | } else { 262 | this.reset(); 263 | } 264 | }; 265 | 266 | reset = () => { 267 | this._lastScreenY = 0; 268 | this.setContentStyle(0); 269 | }; 270 | 271 | setContentStyle = (ty: number) => { 272 | // todos: Why sometimes do not have `this.contentRef` ? 273 | if (this.contentRef) { 274 | setTransform(this.contentRef.style, `translate3d(0px,${ty}px,0)`); 275 | } 276 | }; 277 | 278 | render() { 279 | const props = { ...this.props }; 280 | 281 | delete props.damping; 282 | 283 | const { 284 | className, 285 | prefixCls, 286 | children, 287 | getScrollContainer, 288 | direction, 289 | onRefresh, 290 | refreshing, 291 | indicator, 292 | distanceToRefresh, 293 | ...restProps 294 | } = props; 295 | 296 | const renderChildren = ( 297 | children} 300 | /> 301 | ); 302 | 303 | const renderRefresh = (cls: string) => { 304 | const cla = classNames( 305 | cls, 306 | !this.state.dragOnEdge && `${prefixCls}-transition`, 307 | ); 308 | return ( 309 |
310 |
(this.contentRef = el)}> 311 | {direction === UP ? renderChildren : null} 312 |
313 | {indicator[this.state.currSt] || INDICATOR[this.state.currSt]} 314 |
315 | {direction === DOWN ? renderChildren : null} 316 |
317 |
318 | ); 319 | }; 320 | 321 | if (getScrollContainer()) { 322 | return renderRefresh(`${prefixCls}-content ${prefixCls}-${direction}`); 323 | } 324 | return ( 325 |
(this.containerRef = el)} 327 | className={classNames( 328 | className, 329 | prefixCls, 330 | `${prefixCls}-${direction}`, 331 | )} 332 | {...restProps} 333 | > 334 | {renderRefresh(`${prefixCls}-content`)} 335 |
336 | ); 337 | } 338 | } 339 | --------------------------------------------------------------------------------