├── .circleci
└── config.yml
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .umirc.ts
├── LICENSE
├── README.md
├── README.zh-CN.md
├── docs
├── getting-started
│ ├── index.md
│ ├── index.zh-CN.md
│ ├── installation.md
│ └── installation.zh-CN.md
├── index.md
└── index.zh-CN.md
├── package.json
├── scripts
├── build.js
└── pubDoc.js
├── src
├── form.ts
├── index.ts
├── useCascadeSearch
│ └── index.ts
├── useCascadeSelect
│ ├── index.md
│ ├── index.ts
│ └── index.zh-CN.md
├── useForm
│ ├── index.md
│ ├── index.ts
│ └── index.zh-CN.md
├── useFormTable
│ ├── index.md
│ ├── index.ts
│ └── index.zh-CN.md
├── useModal
│ ├── index.md
│ ├── index.ts
│ └── index.zh-CN.md
├── useModalForm
│ ├── index.md
│ ├── index.ts
│ └── index.zh-CN.md
├── useSearchResult
│ └── index.ts
└── useStepsForm
│ ├── index.md
│ ├── index.ts
│ └── index.zh-CN.md
├── tests
├── useCascadeSearch.test.js
├── useCascadeSelect.test.js
├── useForm.test.js
├── useFormTable.test.js
├── useModal.test.js
├── useModalForm.test.js
├── useSearchResult.test.js
└── useStepsForm.test.js
├── tsconfig.json
├── typings.d.ts
└── yarn.lock
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | jobs:
3 | lint:
4 | docker:
5 | - image: circleci/node:latest
6 | steps:
7 | - checkout
8 | - restore_cache:
9 | keys:
10 | - yarn-packages-{{ checksum "yarn.lock" }}
11 | - run: yarn install --frozen-lockfile
12 | - save_cache:
13 | paths:
14 | - node_modules
15 | key: yarn-packages-{{ checksum "yarn.lock" }}
16 | - run: yarn lint
17 | test:
18 | docker:
19 | - image: circleci/node:latest
20 | steps:
21 | - checkout
22 | - restore_cache:
23 | keys:
24 | - yarn-packages-{{ checksum "yarn.lock" }}
25 | - run: yarn install --frozen-lockfile
26 | - save_cache:
27 | paths:
28 | - node_modules
29 | key: yarn-packages-{{ checksum "yarn.lock" }}
30 | - run: yarn cov
31 | - run: bash <(curl -s https://codecov.io/bash)
32 | build:
33 | docker:
34 | - image: circleci/node:latest
35 | steps:
36 | - checkout
37 | - restore_cache:
38 | keys:
39 | - yarn-packages-{{ checksum "yarn.lock" }}
40 | - run: yarn install --frozen-lockfile
41 | - save_cache:
42 | paths:
43 | - node_modules
44 | key: yarn-packages-{{ checksum "yarn.lock" }}
45 | - run: yarn build
46 |
47 | workflows:
48 | version: 2
49 | lint_test_build:
50 | jobs:
51 | - lint
52 | - test
53 | - build
54 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [require.resolve('@umijs/fabric/dist/eslint')],
3 | rules: {
4 | 'import/no-unresolved': 0,
5 | 'no-underscore-dangle': 0,
6 | 'import/no-extraneous-dependencies': 0,
7 | 'global-require': 0,
8 | 'import/no-dynamic-require': 0,
9 | 'react/sort-comp': 0,
10 | 'jsx-a11y/aria-role': 0,
11 | 'dot-notation': 0,
12 | 'consistent-return': 0,
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log*
3 | yarn-error.log
4 | coverage
5 | dist
6 | es
7 | lib
8 | .DS_Store
9 | .umi
10 | .umi-production
11 | .umi-test
12 | .env.local
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | **/*.svg
2 | **/*.ejs
3 | **/*.html
4 | package.json
5 | .umi
6 | .umi-production
7 | .umi-test
8 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "printWidth": 80,
5 | "overrides": [
6 | {
7 | "files": ".prettierrc",
8 | "options": { "parser": "json" }
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/.umirc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'dumi';
2 |
3 | export default defineConfig({
4 | title: 'Sunflower',
5 | mode: 'site',
6 | base: '/sunflower/',
7 | publicPath: '/sunflower/',
8 | exportStatic: {},
9 | extraBabelPlugins: [
10 | [
11 | 'babel-plugin-import',
12 | {
13 | libraryName: 'antd',
14 | libraryDirectory: 'es',
15 | style: true,
16 | },
17 | ],
18 | ],
19 | navs: {
20 | 'zh-CN': [
21 | null,
22 | { title: 'GitHub', path: 'https://github.com/ant-design/sunflower' },
23 | ],
24 | 'en-US': [
25 | null,
26 | { title: 'GitHub', path: 'https://github.com/ant-design/sunflower' },
27 | ],
28 | },
29 | });
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT LICENSE
2 |
3 | Copyright (c) 2019-present Alipay Industry Technology https://www.alipay.com/
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sunflower
2 |
3 | Process Components for antd.
4 |
5 | [![NPM version][npm-image]][npm-url]
6 | [![build status][circleci-image]][circleci-url]
7 |
8 | [circleci-image]: https://img.shields.io/circleci/build/github/ant-design/sunflower/master.svg?style=flat-square
9 | [circleci-url]: https://circleci.com/gh/ant-design/sunflower/tree/master
10 | [npm-image]: https://img.shields.io/npm/v/sunflower-antd.svg?style=flat
11 | [npm-url]: https://www.npmjs.com/package/sunflower-antd
12 |
13 | English | [简体中文](./README.zh-CN.md)
14 |
15 | ## Features
16 |
17 | - Support antd4, antd3
18 | - The relationship between antd components is expressed using react-hooks, and process components are used to simplify development
19 | - Process Components are extracted from the actual business processes and used immediately
20 |
21 | ## Why
22 |
23 | Usually, we use multiple components of antd to complete a process. For example, if you want to complete the function of "using Table to display the list after Form search", you need to deal with the relationship between "Form" and "Table", including query, pagination, etc.
24 |
25 | Is there a way to simplify the maintenance of relationships between components? This is what sunflower is for. React-hooks that describe a scene are called "Process Components". Sunflower is a series of antd-based "Process Components".
26 |
27 | The following is an example of a "Form & Table" scenario. You only need the following code to complete functions such as querying and paging. `useFormTable` is a react-hooks, which will return the props of the antd component, etc. You can give these props to the antd component to complete the connection between the components.
28 |
29 | ```js
30 | import React from 'react';
31 | import { useFormTable } from 'sunflower-antd';
32 | import { Input, Button, Table, Form } from 'antd';
33 |
34 | export default props => {
35 | const { formProps, tableProps } = useFormTable({
36 | async search(values) {
37 | const res = await request(values);
38 | return res;
39 | },
40 | });
41 |
42 | return (
43 |
44 |
46 |
47 |
48 |
49 |
50 |
51 | Search
52 |
53 |
54 |
55 |
56 |
66 |
67 | );
68 | };
69 | ```
70 |
71 | ## Document
72 |
73 | - [English](https://ant-design.github.io/sunflower/getting-started)
74 | - [简体中文](https://ant-design.github.io/sunflower/zh-CN/getting-started)
75 |
76 | ## Usage
77 |
78 | ```
79 | $ npm i sunflower-antd --save
80 | // or
81 | $ yarn add sunflower-antd
82 | ```
83 |
84 | ## Development
85 |
86 | ```
87 | $ yarn
88 | $ yarn start
89 | ```
90 |
--------------------------------------------------------------------------------
/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | # Sunflower
2 |
3 | antd 流程组件
4 |
5 | [![NPM version][npm-image]][npm-url]
6 | [![build status][circleci-image]][circleci-url]
7 |
8 | [circleci-image]: https://img.shields.io/circleci/build/github/ant-design/sunflower/master.svg?style=flat-square
9 | [circleci-url]: https://circleci.com/gh/ant-design/sunflower/tree/master
10 | [npm-image]: https://img.shields.io/npm/v/sunflower-antd.svg?style=flat
11 | [npm-url]: https://www.npmjs.com/package/sunflower-antd
12 |
13 | [English](./README.md) | 简体中文
14 |
15 | ## 特定
16 |
17 | - 支持 antd4, antd3
18 | - 将 antd 组件间关系使用 react-hooks 来表述,使用流程组件的方式来简化开发
19 | - 提炼于业务实际流程,拿来即用
20 |
21 | ## 为什么
22 |
23 | 通常,我们使用 antd 的多个组件来完成一个流程。比如想要完成一个 “使用 Form 搜索后 Table 来展示列表” 的功能,则需要去处理 “Form” 跟 “Table” 的关系,包括查询,分页等。
24 |
25 | 是否有个方式来简化组件间关系的维护?这就是 sunflower 的作用。能描述某个场景的 react-hooks,我们称之为 “流程组件”。sunflower 就是一系列基于 antd 的流程组件。
26 |
27 | 以下是一个 “Form & Table” 场景的示例,只需要以下的代码,就可完成包括查询,分页等功能。`useFormTable` 是一个 react-hooks,会返回 antd 组件的 props 等,将这些 props 给到 antd 组件即可完成组件间的联系。
28 |
29 | ```js
30 | import React from 'react';
31 | import { useFormTable } from 'sunflower-antd';
32 | import { Input, Button, Table, Form } from 'antd';
33 |
34 | export default props => {
35 | const { formProps, tableProps } = useFormTable({
36 | async search(values) {
37 | const res = await request(values);
38 | return res;
39 | },
40 | });
41 |
42 | return (
43 |
44 |
46 |
47 |
48 |
49 |
50 |
51 | 搜索
52 |
53 |
54 |
55 |
56 |
66 |
67 | );
68 | };
69 | ```
70 |
71 | ## 快速开始
72 |
73 | - [English](https://ant-design.github.io/sunflower/getting-started)
74 | - [简体中文](https://ant-design.github.io/sunflower/zh-CN/getting-started)
75 |
76 | ## 使用
77 |
78 | ```
79 | $ npm i sunflower-antd --save
80 | // or
81 | $ yarn add sunflower-antd
82 | ```
83 |
84 | ## 开发
85 |
86 | ```
87 | $ yarn
88 | $ yarn start
89 | ```
90 |
--------------------------------------------------------------------------------
/docs/getting-started/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: Getting Started
4 | order: 1
5 | title: Concept
6 | ---
7 |
8 | Usually, we use multiple components of antd to complete a process. For example, if you want to complete the function of "using Table to display the list after Form search", you need to deal with the relationship between "Form" and "Table", including query, pagination, etc.
9 |
10 | Is there a way to simplify the maintenance of relationships between components? This is what sunflower is for. React-hooks that describe a scene are called "Process Components". Sunflower is a series of antd-based "Process Components".
11 |
12 | The following is an example of a "Form & Table" scenario. You only need the following code to complete functions such as querying and paging. `useFormTable` is a react-hooks, which will return the props of the antd component, etc. You can give these props to the antd component to complete the connection between the components.
13 |
14 |
15 | ```js
16 | import React from 'react';
17 | import { useFormTable } from 'sunflower-antd';
18 | import { Input, Button, Table, Form } from 'antd';
19 |
20 | export default props => {
21 | const { formProps, tableProps } = useFormTable({
22 | async search(values) {
23 | const res = await request(values);
24 | return res;
25 | },
26 | });
27 |
28 | return
29 |
31 |
32 |
33 |
34 |
35 |
36 | Search
37 |
38 |
39 |
40 |
41 |
42 |
52 |
53 | };
54 | ```
--------------------------------------------------------------------------------
/docs/getting-started/index.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: 快速开始
4 | order: 1
5 | title: 概念
6 | ---
7 |
8 | 通常,我们使用 antd 的多个组件来完成一个流程。比如想要完成一个 “使用 Form 搜索后 Table 来展示列表” 的功能,则需要去处理 “Form” 跟 “Table” 的关系,包括查询,分页等。
9 |
10 | 是否有个方式来简化组件间关系的维护?这就是 sunflower 的作用。能描述某个场景的 react-hooks,我们称之为 “流程组件”。sunflower 就是一系列基于 antd 的流程组件。
11 |
12 | 以下是一个 “Form & Table” 场景的示例,只需要以下的代码,就可完成包括查询,分页等功能。`useFormTable` 是一个 react-hooks,会返回 antd 组件的 props 等,将这些 props 给到 antd 组件即可完成组件间的联系。
13 |
14 | ```js
15 | import React from 'react';
16 | import { useFormTable } from 'sunflower-antd';
17 | import { Input, Button, Table, Form } from 'antd';
18 |
19 | export default props => {
20 | const { formProps, tableProps } = useFormTable({
21 | async search(values) {
22 | const res = await request(values);
23 | return res;
24 | },
25 | });
26 |
27 | return (
28 |
29 |
31 |
32 |
33 |
34 |
35 |
36 | 搜索
37 |
38 |
39 |
40 |
41 |
51 |
52 | );
53 | };
54 | ```
55 |
--------------------------------------------------------------------------------
/docs/getting-started/installation.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: Getting Started
4 | title: Installation
5 | ---
6 |
7 | Make sure you have `antd` installed, then install:
8 |
9 | ```bash
10 | $ npm install sunflower-antd --save
11 | ```
12 |
13 | Sunflower includes different "Process Components", you can use different ones from "sunflower-antd".
14 |
15 | eg:
16 |
17 | ```js
18 | import { useFormTable } from 'sunflower-antd';
19 | ```
20 |
21 | Visit ["Process Components"](process-components)
22 |
--------------------------------------------------------------------------------
/docs/getting-started/installation.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: 快速开始
4 | title: 安装
5 | ---
6 |
7 | 确保你已经安装了 `antd`,之后安装:
8 |
9 | ```bash
10 | $ npm install sunflower-antd --save
11 | ```
12 |
13 | Sunflower 包括了不同的流程组件,你可从 `sunflower-antd` 中使用需要的。
14 |
15 | 示例:
16 |
17 | ```js
18 | import { useFormTable } from 'sunflower-antd';
19 | ```
20 |
21 | 查看 ["流程组件"](process-components) 使用
22 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Sunflower - process component for antd
3 | hero:
4 | title: Sunflower
5 | desc: Process Components for antd
6 | actions:
7 | - text: Getting Started
8 | link: getting-started
9 | features:
10 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/813f5ed9-6bc4-43d4-9f74-ec81ecf35733/k7htg6n4_w144_h144.png
11 | title: Simple to use
12 | desc: The relationship between antd components is expressed using react-hooks, and process components are used to simplify development.
13 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/7659205c-6637-4fa2-8529-d32e5818304b/k7htflfb_w144_h144.png
14 | title: From real business
15 | desc: Process Components are extracted from the actual business processes and used immediately.
16 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/6319a122-e8b8-497f-9b45-37cfbe77edaa/k7htfx7t_w144_h144.png
17 | title: Production available
18 | desc: Used in various industries of Alipay.
19 | footer: Open-source MIT Licensed | Copyright © 2019-present Powered by [dumi](https://d.umijs.org)
20 | ---
21 |
--------------------------------------------------------------------------------
/docs/index.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Sunflower - process component for antd
3 | hero:
4 | title: Sunflower
5 | desc: 基于 antd 的流程组件
6 | actions:
7 | - text: 快速开始
8 | link: getting-started
9 | features:
10 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/813f5ed9-6bc4-43d4-9f74-ec81ecf35733/k7htg6n4_w144_h144.png
11 | title: 使用简单
12 | desc: 将 antd 组件间关系使用 react-hooks 来表述,使用流程组件的方式来简化开发。
13 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/7659205c-6637-4fa2-8529-d32e5818304b/k7htflfb_w144_h144.png
14 | title: 提炼于实际业务
15 | desc: Sunflower 的流程组件都是提炼于业务实际流程,拿来即用。
16 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/6319a122-e8b8-497f-9b45-37cfbe77edaa/k7htfx7t_w144_h144.png
17 | title: 生产可用
18 | desc: 在支付宝多个行业业务使用。
19 | footer: Open-source MIT Licensed | Copyright © 2019-present Powered by [dumi](https://d.umijs.org)
20 | ---
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sunflower-antd",
3 | "version": "1.0.0-beta.3",
4 | "description": "Process Components for antd",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/ant-design/sunflower"
8 | },
9 | "keywords": [
10 | "antd",
11 | "react-hook"
12 | ],
13 | "authors": [
14 | "JIACHENG9 (https://github.com/JIACHENG9)"
15 | ],
16 | "license": "MIT",
17 | "bugs": "http://github.com/ant-design/sunflower",
18 | "homepage": "https://ant-design.github.io/sunflower/",
19 | "publishConfig": {
20 | "access": "public"
21 | },
22 | "files": [
23 | "es",
24 | "lib"
25 | ],
26 | "scripts": {
27 | "start": "dumi dev",
28 | "docs:build": "dumi build",
29 | "build": "rm -rf es lib && node scripts/build.js",
30 | "lint": "eslint --ext .ts,.tsx \"./src\"",
31 | "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
32 | "test": "umi-test",
33 | "cov": "umi-test --coverage"
34 | },
35 | "main": "lib/index.js",
36 | "module": "es/index.js",
37 | "typings": "lib/index.d.ts",
38 | "gitHooks": {
39 | "pre-commit": "lint-staged"
40 | },
41 | "lint-staged": {
42 | "*.{js,jsx,less,md,json}": [
43 | "prettier --write"
44 | ],
45 | "*.ts?(x)": [
46 | "prettier --parser=typescript --write"
47 | ]
48 | },
49 | "devDependencies": {
50 | "@testing-library/react-hooks": "^3.2.1",
51 | "@umijs/fabric": "^2.0.4",
52 | "@umijs/test": "^3.0.5",
53 | "antd": "^4.0.3",
54 | "dumi": "^1.0.8",
55 | "gh-pages": "^2.2.0",
56 | "lint-staged": "^10.0.7",
57 | "mockjs": "^1.1.0",
58 | "prettier": "^1.19.1",
59 | "react": "^16.13.0",
60 | "react-test-renderer": "^16.13.0",
61 | "typescript": "^3.8.3",
62 | "yorkie": "^2.0.0"
63 | },
64 | "peerDependencies": {
65 | "react": ">=16.9.0",
66 | "react-dom": ">=16.9.0"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | const cp = require('child_process');
2 | const path = require('path');
3 | const fs = require('fs');
4 |
5 | cp.fork(
6 | path.join(__dirname, '../node_modules/typescript/bin/tsc'),
7 | ['--module', 'esnext', '--outDir', './es'],
8 | {
9 | cwd: __dirname,
10 | },
11 | ).on('exit', code => {
12 | if (code !== 0) {
13 | process.exit(code);
14 | }
15 | });
16 |
17 | cp.fork(
18 | path.join(__dirname, '../node_modules/typescript/bin/tsc'),
19 | ['--module', 'commonjs', '--outDir', './lib'],
20 | {
21 | cwd: __dirname,
22 | },
23 | ).on('exit', code => {
24 | if (code !== 0) {
25 | process.exit(code);
26 | }
27 |
28 | function replace(name) {
29 | const filename = path.join(__dirname, 'lib', name);
30 | const str = fs.readFileSync(filename, 'utf8');
31 | fs.writeFileSync(filename, str.replace('antd/es/form', 'antd/lib/form'));
32 | }
33 | replace('form.js');
34 | replace('form.d.ts');
35 | });
36 |
--------------------------------------------------------------------------------
/scripts/pubDoc.js:
--------------------------------------------------------------------------------
1 | const ghpages = require('gh-pages');
2 |
3 | ghpages.publish('dist', function(err) {
4 | if (err) {
5 | console.log(err);
6 | }
7 | });
8 |
--------------------------------------------------------------------------------
/src/form.ts:
--------------------------------------------------------------------------------
1 | import Form from 'antd/es/form';
2 |
3 | export default Form;
4 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './useFormTable';
2 | export * from './useCascadeSelect';
3 | export * from './useModal';
4 | export * from './useForm';
5 | export * from './useModalForm';
6 | export * from './useCascadeSearch';
7 | export * from './useStepsForm';
8 |
--------------------------------------------------------------------------------
/src/useCascadeSearch/index.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | export interface UseCascadeSearchConfig {
4 | list: ((...args: any) => T | Promise)[];
5 | }
6 |
7 | export const useCascadeSearch = ({
8 | list = [],
9 | }: UseCascadeSearchConfig) => {
10 | const [responseDataList, setResponseDataList] = useState([] as T[]);
11 | const [loadingList, setLoadingList] = useState(
12 | list.map(() => false) as boolean[],
13 | );
14 | const search = (index: number, ...args: any) => {
15 | if (index >= list.length || index < 0) {
16 | return;
17 | }
18 | if (index > 0 && !responseDataList[index - 1]) {
19 | return;
20 | }
21 | const array = [...responseDataList.slice(0, index)];
22 | setResponseDataList(array);
23 | const loading = [...loadingList];
24 | loading[index] = true;
25 | setLoadingList(loading);
26 | Promise.resolve(list[index](responseDataList[index - 1], ...args)).then(
27 | value => {
28 | const nextArray = [...responseDataList.slice(0, index + 1)];
29 | nextArray[index] = value;
30 | const nextLoading = [...loadingList];
31 | nextLoading[index] = false;
32 | setLoadingList(nextLoading);
33 | setResponseDataList(nextArray);
34 | },
35 | );
36 | };
37 | return {
38 | search,
39 | responseDataList,
40 | loadingList,
41 | setResponseDataList,
42 | };
43 | };
44 |
--------------------------------------------------------------------------------
/src/useCascadeSelect/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: Process Components
4 | path: /process-components
5 | group:
6 | title: Process Components
7 | path: /process-components
8 | title: useCascadeSelect
9 | order: 2
10 | ---
11 |
12 | ## Overview
13 |
14 | `useCascadeSelect` is a react-hooks. When you want to use cascading select, you can use it.
15 |
16 | ## Examples
17 |
18 | ### Basic
19 |
20 | Change the "Like" select option, the "Type" select values will change.
21 |
22 | ```jsx
23 | import React from 'react';
24 | import { Input, Select, Form, Button, Table } from 'antd';
25 | import { useCascadeSelect, useFormTable } from 'sunflower-antd';
26 | import Mock from 'mockjs'; // mock request
27 |
28 | const layout = {
29 | labelCol: { span: 8 },
30 | wrapperCol: { span: 16 },
31 | };
32 | const tailLayout = {
33 | wrapperCol: { offset: 8, span: 16 },
34 | };
35 |
36 | export default props => {
37 | const { formProps, tableProps, form } = useFormTable({
38 | async search(values) {
39 | const res = await request(values);
40 | return {
41 | dataSource: res.list,
42 | total: res.total,
43 | };
44 | },
45 | });
46 |
47 | const { selects } = useCascadeSelect({
48 | form,
49 | list: [
50 | {
51 | name: 'like',
52 | async options() {
53 | // mock
54 | await new Promise(r => setTimeout(r, 1000));
55 | return [
56 | {
57 | label: 'This is a Apple',
58 | value: 'apple',
59 | },
60 | {
61 | label: 'This is a Bananar',
62 | value: 'bananar',
63 | },
64 | ];
65 | },
66 | },
67 | {
68 | name: 'type',
69 | async options(value) {
70 | // mock
71 | await new Promise(r => setTimeout(r, 1000));
72 |
73 | if (value === 'apple') {
74 | return [
75 | {
76 | label: 'Red Apple',
77 | value: '1',
78 | },
79 | {
80 | label: 'Green Apple',
81 | value: '2',
82 | },
83 | ];
84 | }
85 | return [
86 | {
87 | label: 'Yellow Bananar',
88 | value: '1',
89 | },
90 | {
91 | label: 'Green Bananar',
92 | value: '2',
93 | },
94 | ];
95 | },
96 | },
97 | ],
98 | });
99 | const [like, type] = selects;
100 |
101 | return (
102 |
103 |
105 |
106 |
107 |
108 |
109 |
110 | {like.options.map(item => (
111 | {item.label}
112 | ))}
113 |
114 |
115 |
116 |
117 |
118 | {type.options.map(item => (
119 | {item.label}
120 | ))}
121 |
122 |
123 |
124 |
125 | form.resetFields()} style={{ marginRight: 8 }}>
126 | Reset
127 |
128 |
129 |
130 | Search
131 |
132 |
133 |
134 |
135 |
158 |
159 | );
160 | };
161 |
162 | // mock request
163 | const total = 200;
164 | const db = Mock.mock({
165 | [`list|${total}`]: [
166 | {
167 | username: '@name',
168 | 'like|1': ['apple', 'bananar'],
169 | 'type|1': ['1', '2'],
170 | },
171 | ],
172 | });
173 | function filter(list, dataIndex, keyword) {
174 | if (!keyword) {
175 | return list;
176 | }
177 | return list.filter(
178 | item =>
179 | item[dataIndex].toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) >
180 | -1,
181 | );
182 | }
183 | function request({ current, pageSize, like, type, username }) {
184 | const start = pageSize * (current - 1);
185 | const end = start + pageSize;
186 | let totalList = db.list;
187 | totalList = filter(totalList, 'username', username);
188 | totalList = filter(totalList, 'like', like);
189 | totalList = filter(totalList, 'type', type);
190 | const list = totalList.slice(start, end);
191 | return new Promise(r =>
192 | setTimeout(() => {
193 | r({
194 | list,
195 | total: totalList.length,
196 | });
197 | }, 300),
198 | );
199 | }
200 | ```
201 |
202 | ### DefaultFormValues
203 |
204 | ## API
205 |
206 | ```js
207 | const Result = useCascadeSelect(Config);
208 | ```
209 |
210 | ### Config
211 |
212 |
213 |
214 |
215 | Key
216 | Description
217 | Type
218 | Default
219 |
220 |
221 |
222 |
223 | list
224 | Cascading method array. Each method returns a select list of options. When the previous select value change, the subsequent method will be triggered with previous select value.
225 | name, options
226 | []
227 |
228 |
229 | autoFirstSearch
230 | Whether the first method in the list will be executed automatically.
231 | boolean
232 | true
233 |
234 |
235 |
236 |
237 | ### Result
238 |
239 |
240 |
241 |
242 | Key
243 | Description
244 | Type
245 |
246 |
247 |
248 |
249 | selects
250 | Cascade array, according to the length of the config list
251 |
252 |
253 |
254 | search
255 | Execute the method of specifying the index in the config list,usually do not need to use.
256 | (index: number, value: string) => void
257 |
258 |
259 |
260 |
--------------------------------------------------------------------------------
/src/useCascadeSelect/index.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useCascadeSearch } from '../useCascadeSearch';
3 |
4 | export interface OptionData {
5 | value: string;
6 | label: string;
7 | }
8 |
9 | export interface ListItem {
10 | name: string;
11 | options: (...args: any) => OptionData[] | Promise;
12 | }
13 |
14 | export interface Select {
15 | props: {
16 | [key: string]: any;
17 | };
18 | options: OptionData[];
19 | }
20 |
21 | export interface UseCascadeSelectConfig {
22 | list: ListItem[];
23 | autoFirstSearch?: boolean;
24 | form: any;
25 | }
26 |
27 | export const useCascadeSelect = ({
28 | list = [],
29 | autoFirstSearch = true,
30 | form,
31 | }: UseCascadeSelectConfig) => {
32 | const {
33 | search,
34 | responseDataList,
35 | loadingList,
36 | setResponseDataList,
37 | } = useCascadeSearch({
38 | list: list.map(item => (lastValue, ...args) => item.options(...args)),
39 | });
40 |
41 | const selects: Select[] = list.map((item, index) => {
42 | const options = responseDataList[index] || [];
43 | return {
44 | props: {
45 | loading: loadingList[index],
46 | onChange(val) {
47 | if (val) {
48 | search(index + 1, val);
49 | }
50 | if (form) {
51 | const values = {};
52 | for (let i = index + 1; i < list.length; i += 1) {
53 | values[list[i].name] = undefined;
54 | }
55 | const nextResponseDataList = responseDataList.slice(0, index + 1);
56 | form.setFieldsValue(values);
57 | setResponseDataList(nextResponseDataList);
58 | }
59 | },
60 | },
61 | options,
62 | };
63 | });
64 |
65 | useEffect(() => {
66 | if (autoFirstSearch && !responseDataList[0]) {
67 | search(0);
68 | }
69 | }, []);
70 | return {
71 | search,
72 | selects,
73 | };
74 | };
75 |
--------------------------------------------------------------------------------
/src/useCascadeSelect/index.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: 流程组件
4 | path: /zh-CN/process-components
5 | group:
6 | title: 流程组件
7 | path: /zh-CN/process-components
8 | title: useCascadeSelect
9 | order: 2
10 | ---
11 |
12 | ## 说明
13 |
14 | 当需要进行多个 Select 组件的级联选择,可使用这个流程组件。
15 |
16 | ## 示例
17 |
18 | ### 基础
19 |
20 | 修改 “喜欢” 的选项,会去请求所选 “喜欢” 对应的 “类型” 列表。
21 |
22 | ```jsx
23 | import React, { useEffect } from 'react';
24 | import { Input, Select, Form, Button, Table } from 'antd';
25 | import { useCascadeSelect, useFormTable } from 'sunflower-antd';
26 | import Mock from 'mockjs'; // mock request
27 |
28 | const layout = {
29 | labelCol: { span: 8 },
30 | wrapperCol: { span: 16 },
31 | };
32 | const tailLayout = {
33 | wrapperCol: { offset: 8, span: 16 },
34 | };
35 |
36 | export default props => {
37 | const { selects, search } = useCascadeSelect({
38 | list: [
39 | {
40 | name: 'like',
41 | async options() {
42 | // mock
43 | await new Promise(r => setTimeout(r, 1000));
44 | return [
45 | {
46 | label: 'This is a Apple',
47 | value: 'apple',
48 | },
49 | {
50 | label: 'This is a Bananar',
51 | value: 'bananar',
52 | },
53 | ];
54 | },
55 | },
56 | {
57 | name: 'type',
58 | async options(value) {
59 | // mock
60 | await new Promise(r => setTimeout(r, 1000));
61 |
62 | if (value === 'apple') {
63 | return [
64 | {
65 | label: 'Red Apple',
66 | value: '1',
67 | },
68 | {
69 | label: 'Green Apple',
70 | value: '2',
71 | },
72 | ];
73 | }
74 | return [
75 | {
76 | label: 'Yellow Bananar',
77 | value: '1',
78 | },
79 | {
80 | label: 'Green Bananar',
81 | value: '2',
82 | },
83 | ];
84 | },
85 | },
86 | ],
87 | });
88 | const [like, type] = selects;
89 |
90 | const { formProps, tableProps, form } = useFormTable({
91 | async search(values) {
92 | const res = await request(values);
93 | return {
94 | dataSource: res.list,
95 | total: res.total,
96 | };
97 | },
98 | });
99 |
100 | return (
101 |
102 |
104 |
105 |
106 |
107 |
108 |
109 | {like.options.map(item => (
110 | {item.label}
111 | ))}
112 |
113 |
114 |
115 |
116 |
117 | {type.options.map(item => (
118 | {item.label}
119 | ))}
120 |
121 |
122 |
123 |
124 | form.resetFields()} style={{ marginRight: 8 }}>
125 | 重置
126 |
127 |
128 |
129 | 搜索
130 |
131 |
132 |
133 |
134 |
157 |
158 | );
159 | };
160 |
161 | // mock request
162 | const total = 200;
163 | const db = Mock.mock({
164 | [`list|${total}`]: [
165 | {
166 | username: '@name',
167 | 'like|1': ['apple', 'bananar'],
168 | 'type|1': ['1', '2'],
169 | },
170 | ],
171 | });
172 | function filter(list, dataIndex, keyword) {
173 | if (!keyword) {
174 | return list;
175 | }
176 | return list.filter(
177 | item =>
178 | item[dataIndex].toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) >
179 | -1,
180 | );
181 | }
182 | function request({ current, pageSize, like, type, username }) {
183 | const start = pageSize * (current - 1);
184 | const end = start + pageSize;
185 | let totalList = db.list;
186 | totalList = filter(totalList, 'username', username);
187 | totalList = filter(totalList, 'like', like);
188 | totalList = filter(totalList, 'type', type);
189 | const list = totalList.slice(start, end);
190 | return new Promise(r =>
191 | setTimeout(() => {
192 | r({
193 | list,
194 | total: totalList.length,
195 | });
196 | }, 300),
197 | );
198 | }
199 | ```
200 |
201 | ### DefaultFormValues
202 |
203 | ## API
204 |
205 | ```js
206 | const Result = useCascadeSelect(Config);
207 | ```
208 |
209 | ### Config
210 |
211 |
212 |
213 |
214 | 名称
215 | 说明
216 | 类型
217 | 默认值
218 |
219 |
220 |
221 |
222 | list
223 | 级联方法数组,之前的值返回后会作为之后的参数。
224 | name, options
225 | []
226 |
227 |
228 | autoFirstSearch
229 | 是否组件渲染就进行调用表单查询
230 | boolean
231 | true
232 |
233 |
234 |
235 |
236 | ### Result
237 |
238 |
239 |
240 |
241 | 名称
242 | 说明
243 | 类型
244 |
245 |
246 |
247 |
248 | selects
249 | 级联对象,跟 list 长度相同,每一个对象有 props 及 options 属性
250 |
251 |
252 |
253 | search
254 | 对指定的级联 Select 进行搜索
255 | (index: number, value: string) => void
256 |
257 |
258 |
259 |
--------------------------------------------------------------------------------
/src/useForm/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: Process Components
4 | path: /process-components
5 | group:
6 | title: Process Components
7 | path: /process-components
8 | title: useForm
9 | order: 5
10 | ---
11 |
12 | ## Overview
13 |
14 | When you want to use `antd Form`, you can use it.
15 |
16 | ## Examples
17 |
18 | ### Basic
19 |
20 | ```jsx
21 | import React from 'react';
22 | import { useForm } from 'sunflower-antd';
23 | import { Input, Button, Form, Spin } from 'antd';
24 |
25 | export default props => {
26 | const [form] = Form.useForm();
27 | const { formProps, formValues, formResult, formLoading } = useForm({
28 | form,
29 | async submit({ username, email }) {
30 | console.log('submit');
31 | await new Promise(r => setTimeout(r, 1000));
32 | console.log('fewfew');
33 | return 'ok';
34 | },
35 | });
36 | return (
37 |
38 |
39 | submit: username {formValues.username} email {formValues.email}
40 |
41 |
result: {formResult}
42 |
formLoading: {formLoading && }
43 |
49 |
50 |
51 |
52 |
63 |
64 |
65 |
66 |
67 | form.resetFields()}>Reset
68 |
69 |
70 |
71 |
72 | Search
73 |
74 |
75 |
76 |
77 | );
78 | };
79 | ```
80 |
81 | ### defaultFormValues
82 |
83 | ```jsx
84 | import React from 'react';
85 | import { useForm } from 'sunflower-antd';
86 | import { Input, Button, Form, Spin } from 'antd';
87 |
88 | export default props => {
89 | const [form] = Form.useForm();
90 | const { formProps, formValues, formResult, formLoading } = useForm({
91 | form,
92 | async submit({ username, email }) {
93 | console.log('submit');
94 | await new Promise(r => setTimeout(r, 1000));
95 | console.log('fewfew');
96 | return 'ok';
97 | },
98 | async defaultFormValues() {
99 | await new Promise(r => setTimeout(r, 3000));
100 | return {
101 | username: 'lily',
102 | };
103 | },
104 | });
105 | return (
106 |
107 |
108 | submit: username {formValues.username} email {formValues.email}
109 |
110 |
result: {formResult}
111 |
formLoading: {formLoading && }
112 |
118 |
119 |
120 |
121 |
132 |
133 |
134 |
135 |
136 | form.resetFields()}>Reset
137 |
138 |
139 |
140 |
141 | Search
142 |
143 |
144 |
145 |
146 | );
147 | };
148 | ```
149 |
150 | ### submit method
151 |
152 | ```jsx
153 | import React from 'react';
154 | import { useForm } from 'sunflower-antd';
155 | import { Input, Button, Form, Spin } from 'antd';
156 |
157 | export default props => {
158 | const [form] = Form.useForm();
159 | const { formProps, formValues, formResult, formLoading, submit } = useForm({
160 | form,
161 | async submit({ username, email }) {
162 | console.log('submit', username, email);
163 | await new Promise(r => setTimeout(r, 1000));
164 | return 'ok';
165 | },
166 | });
167 | return (
168 |
169 |
170 | submit: username {formValues.username} email {formValues.email}
171 |
172 |
result: {formResult}
173 |
formLoading: {formLoading && }
174 |
180 |
181 |
182 |
183 |
194 |
195 |
196 |
197 |
198 | form.resetFields()}>Reset
199 |
200 |
201 |
202 |
203 | Search
204 |
205 |
206 |
207 |
208 |
submit({ username: 'lily' })}
210 | style={{ marginTop: 20 }}
211 | type="primary"
212 | >
213 | call submit method
214 |
215 |
216 | );
217 | };
218 | ```
219 |
220 | ## API
221 |
222 | ```js
223 | const result = useForm(config);
224 | ```
225 |
226 | ### Config
227 |
228 |
229 |
230 |
231 | Key
232 | Description
233 | Type
234 | Default
235 |
236 |
237 |
238 |
239 | submit
240 | submit method, the parameter is the value of the form fields
241 | (formValues) => Promise<formResult> | formResult
242 |
243 |
244 |
245 | form
246 | Decorated by Form.create() will be automatically set this.props.form property(antd3); useForm react hooks(antd4)
247 |
248 |
249 |
250 |
251 | defaultFormValues
252 | Default form values.If the form has data that needs to be backfilled, use it to get the data.
253 | object | () => Promise<object>
254 |
255 |
256 |
257 |
258 |
259 | ### Result
260 |
261 |
262 |
263 |
264 | Key
265 | Description
266 | Type
267 |
268 |
269 |
270 |
271 | formProps
272 | antd Form props
273 |
274 |
275 |
276 | form
277 | Form instance
278 |
279 |
280 |
281 | formLoading
282 | form request loading.
283 | boolean
284 |
285 |
286 | formValues
287 | form values
288 | object
289 |
290 |
291 | initialValues
292 | initial form values
293 | object
294 |
295 |
296 | formResult
297 | submit return value
298 |
299 |
300 |
301 | defaultFormValuesLoading
302 | When use 'defaultFormValues', the value will be true during the request.
303 | boolean
304 |
305 |
306 | submit
307 | will call the 'submit' method
308 |
309 |
310 |
311 |
312 |
--------------------------------------------------------------------------------
/src/useForm/index.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import Form from '../form';
3 |
4 | export declare type StoreBaseValue = string | number | boolean;
5 | export declare type StoreValue = StoreBaseValue | Store | StoreBaseValue[];
6 | export interface Store {
7 | [name: string]: StoreValue;
8 | }
9 |
10 | export interface UseFormConfig {
11 | defaultFormValues?: Store | (() => Promise | Store);
12 | form?: any;
13 | submit?: (formValues: Store) => any;
14 | }
15 |
16 | export const useForm = (config: UseFormConfig) => {
17 | const [defaultFormValuesLoading, setDefaultFormValuesLoading] = useState(
18 | false,
19 | );
20 | const [initialValues, setInitialValues] = useState({});
21 | const { defaultFormValues, form, submit } = config;
22 | const [formValues, setFormValues] = useState({});
23 | const [formLoading, setFormLoading] = useState(false);
24 | const [formResult, setFormResult] = useState();
25 |
26 | let version = 3;
27 | // antd4
28 | if (Form['useForm']) {
29 | version = 4;
30 | }
31 |
32 | let formInstance = form;
33 | if (!form) {
34 | if (version === 4) {
35 | [formInstance] = Form['useForm']();
36 | } else {
37 | throw new Error('"form" need in antd@3');
38 | }
39 | }
40 |
41 | const onFinish = (formValue: Store) => {
42 | setFormValues(formValue);
43 | setFormLoading(true);
44 | return new Promise((resolve, reject) => {
45 | if (version === 4) {
46 | formInstance
47 | .validateFields()
48 | .then(() => {
49 | resolve(
50 | Promise.resolve(submit(formValue))
51 | .then(data => {
52 | setFormLoading(false);
53 | setFormResult(data);
54 | return data;
55 | })
56 | .catch(err => {
57 | setFormLoading(false);
58 | throw err;
59 | }),
60 | );
61 | })
62 | .catch(validateErr => {
63 | setFormLoading(false);
64 | reject(validateErr);
65 | });
66 | } else {
67 | formInstance.validateFields(validateErr => {
68 | if (validateErr) {
69 | setFormLoading(false);
70 | reject(validateErr);
71 | } else {
72 | resolve(
73 | Promise.resolve(submit(formValue))
74 | .then(data => {
75 | setFormLoading(false);
76 | setFormResult(data);
77 | return data;
78 | })
79 | .catch(err => {
80 | setFormLoading(false);
81 | throw err;
82 | }),
83 | );
84 | }
85 | });
86 | }
87 | });
88 | };
89 |
90 | useEffect(() => {
91 | let isUnMounted = false;
92 | if (!defaultFormValues) {
93 | return;
94 | }
95 | let value: Store | Promise;
96 | if (typeof defaultFormValues === 'function') {
97 | setDefaultFormValuesLoading(true);
98 | value = defaultFormValues();
99 | } else {
100 | value = defaultFormValues;
101 | }
102 | Promise.resolve(value)
103 | .then(data => {
104 | if (!isUnMounted) {
105 | const obj = { ...data };
106 | Object.keys(data).forEach(name => {
107 | obj[name] = formInstance.isFieldTouched(name)
108 | ? formInstance.getFieldValue(name)
109 | : data[name];
110 | });
111 | setDefaultFormValuesLoading(false);
112 | setInitialValues(data);
113 | formInstance.setFieldsValue(obj);
114 | }
115 | })
116 | .catch(() => {
117 | if (!isUnMounted) {
118 | setDefaultFormValuesLoading(false);
119 | }
120 | });
121 | return () => {
122 | isUnMounted = true;
123 | };
124 | }, []);
125 |
126 | const formProps =
127 | version === 4
128 | ? {
129 | form: formInstance,
130 | onFinish,
131 | initialValues,
132 | }
133 | : {
134 | onSubmit(e) {
135 | e.preventDefault();
136 | onFinish(
137 | formInstance.getFieldsValue(version === 4 ? true : undefined),
138 | );
139 | },
140 | };
141 |
142 | return {
143 | form: formInstance,
144 | formProps,
145 | defaultFormValuesLoading,
146 | formValues,
147 | initialValues,
148 | formResult,
149 | formLoading,
150 | submit: (values?: Store) => {
151 | formInstance.setFieldsValue(values);
152 | return onFinish(
153 | formInstance.getFieldsValue(version === 4 ? true : undefined),
154 | );
155 | },
156 | };
157 | };
158 |
--------------------------------------------------------------------------------
/src/useForm/index.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: 流程组件
4 | path: /zh-CN/process-components
5 | group:
6 | title: 流程组件
7 | path: /zh-CN/process-components
8 | title: useForm
9 | order: 5
10 | ---
11 |
12 | ## 说明
13 |
14 | 当你需要使用`antd Form`,可使用 `useForm`。
15 |
16 | ## 示例
17 |
18 | ### 基础
19 |
20 | ```jsx
21 | import React from 'react';
22 | import { useForm } from 'sunflower-antd';
23 | import { Input, Button, Form, Spin } from 'antd';
24 |
25 | export default props => {
26 | const [form] = Form.useForm();
27 | const { formProps, formValues, formResult, formLoading } = useForm({
28 | form,
29 | async submit({ username, email }) {
30 | console.log('submit');
31 | await new Promise(r => setTimeout(r, 1000));
32 | console.log('fewfew');
33 | return 'ok';
34 | },
35 | });
36 | return (
37 |
38 |
39 | submit: username {formValues.username} email {formValues.email}
40 |
41 |
result: {formResult}
42 |
formLoading: {formLoading && }
43 |
49 |
50 |
51 |
52 |
63 |
64 |
65 |
66 |
67 | form.resetFields()}>Reset
68 |
69 |
70 |
71 |
72 | Search
73 |
74 |
75 |
76 |
77 | );
78 | };
79 | ```
80 |
81 | ### defaultFormValues
82 |
83 | ```jsx
84 | import React from 'react';
85 | import { useForm } from 'sunflower-antd';
86 | import { Input, Button, Form, Spin } from 'antd';
87 |
88 | export default props => {
89 | const [form] = Form.useForm();
90 | const { formProps, formValues, formResult, formLoading } = useForm({
91 | form,
92 | async submit({ username, email }) {
93 | console.log('submit');
94 | await new Promise(r => setTimeout(r, 1000));
95 | console.log('fewfew');
96 | return 'ok';
97 | },
98 | async defaultFormValues() {
99 | await new Promise(r => setTimeout(r, 3000));
100 | return {
101 | username: 'lily',
102 | };
103 | },
104 | });
105 | return (
106 |
107 |
108 | submit: username {formValues.username} email {formValues.email}
109 |
110 |
result: {formResult}
111 |
formLoading: {formLoading && }
112 |
118 |
119 |
120 |
121 |
132 |
133 |
134 |
135 |
136 | form.resetFields()}>Reset
137 |
138 |
139 |
140 |
141 | Search
142 |
143 |
144 |
145 |
146 | );
147 | };
148 | ```
149 |
150 | ### submit method
151 |
152 | ```jsx
153 | import React from 'react';
154 | import { useForm } from 'sunflower-antd';
155 | import { Input, Button, Form, Spin } from 'antd';
156 |
157 | export default props => {
158 | const [form] = Form.useForm();
159 | const { formProps, formValues, formResult, formLoading, submit } = useForm({
160 | form,
161 | async submit({ username, email }) {
162 | console.log('submit', username, email);
163 | await new Promise(r => setTimeout(r, 1000));
164 | return 'ok';
165 | },
166 | });
167 | return (
168 |
169 |
170 | submit: username {formValues.username} email {formValues.email}
171 |
172 |
result: {formResult}
173 |
formLoading: {formLoading && }
174 |
180 |
181 |
182 |
183 |
194 |
195 |
196 |
197 |
198 | form.resetFields()}>Reset
199 |
200 |
201 |
202 |
203 | Search
204 |
205 |
206 |
207 |
208 |
submit({ username: 'lily' })}
210 | style={{ marginTop: 20 }}
211 | type="primary"
212 | >
213 | call submit method
214 |
215 |
216 | );
217 | };
218 | ```
219 |
220 | ## API
221 |
222 | ```js
223 | const result = useForm(config);
224 | ```
225 |
226 | ### Config
227 |
228 |
229 |
230 |
231 | 名称
232 | 说明
233 | 类型
234 | 默认值
235 |
236 |
237 |
238 |
239 | submit
240 | 请求方法, 其参数为 form fields 的值
241 | (formValues) => Promise<formResult> | formResult
242 |
243 |
244 |
245 | form
246 | 在 antd4 中为 useForm 返回的 form 实例。在 antd3 中 Form.create() props.form
247 |
248 |
249 |
250 |
251 | defaultFormValues
252 | 默认的表单回填值,可为一个对象,也可为一个异步方法
253 | object | () => Promise<object>
254 |
255 |
256 |
257 |
258 |
259 | ### Result
260 |
261 |
262 |
263 |
264 | 名称
265 | 说明
266 | 类型
267 |
268 |
269 |
270 |
271 | formProps
272 | antd Form 组件的 props,作为 Form 组件的 props 即可
273 |
274 |
275 |
276 | form
277 | antd Form 的实例,可访问查看 form 实例的方法
278 |
279 |
280 |
281 | formLoading
282 | 正在提交表单.
283 | boolean
284 |
285 |
286 | formValues
287 | 对应 form fileds 的值
288 | object
289 |
290 |
291 | initialValues
292 | 表单默认值. 对应defaultFormValues
293 | object
294 |
295 |
296 | formResult
297 | submit返回值
298 |
299 |
300 |
301 | defaultFormValuesLoading
302 | 当使用 defaultFormValues 在请求表单回填值的 loading.
303 | boolean
304 |
305 |
306 | submit
307 | submit 方法. 可主动触发, 提交表单, 其参数为 form fields 的值
308 | (formValues) => Promise<formResult>
309 |
310 |
311 |
312 |
--------------------------------------------------------------------------------
/src/useFormTable/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: Process Components
4 | path: /process-components
5 | group:
6 | title: Process Components
7 | path: /process-components
8 | title: useFormTable
9 | order: 1
10 | ---
11 |
12 | ## Overview
13 |
14 | When you want to use "Form Search Table", you can use it.
15 |
16 | ## Examples
17 |
18 | ### Basic
19 |
20 | ```jsx
21 | import React from 'react';
22 | import { useFormTable } from 'sunflower-antd';
23 | import { Input, Button, Table, Form } from 'antd';
24 | import Mock from 'mockjs'; // mock request
25 |
26 | export default props => {
27 | const { formProps, tableProps, form } = useFormTable({
28 | async search(values) {
29 | const res = await request(values);
30 | return {
31 | dataSource: res.list,
32 | total: res.total,
33 | };
34 | },
35 | defaultPageSize: 5,
36 | });
37 |
38 | return (
39 |
40 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | form.resetFields()}>Reset
51 |
52 |
53 |
54 |
55 | Search
56 |
57 |
58 |
59 |
60 |
77 |
78 | );
79 | };
80 |
81 | // mock request
82 | const total = 200;
83 | const db = Mock.mock({
84 | [`list|${total}`]: [
85 | {
86 | username: '@name',
87 | email: '@email',
88 | id: '@guid',
89 | 'gender|1': ['male', 'female'],
90 | },
91 | ],
92 | });
93 | function filter(list, dataIndex, keyword) {
94 | if (!keyword) {
95 | return list;
96 | }
97 | return list.filter(
98 | item =>
99 | item[dataIndex].toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) >
100 | -1,
101 | );
102 | }
103 | function request({ current, pageSize, filters, sorter, username, email }) {
104 | console.log(
105 | '-------> request: username: %s, pageSize: %s, current: %s, filters: %s, sorter: %s',
106 | username,
107 | pageSize,
108 | current,
109 | JSON.stringify(filters),
110 | JSON.stringify(sorter),
111 | );
112 | const start = pageSize * (current - 1);
113 | const end = start + pageSize;
114 | let totalList = db.list;
115 | totalList = filter(totalList, 'username', username);
116 | totalList = filter(totalList, 'email', email);
117 | if (filters) {
118 | Object.keys(filters).forEach(key => {
119 | if (!filters[key]) {
120 | return true;
121 | }
122 | if (filters[key].length === 0) {
123 | return true;
124 | }
125 | totalList = totalList.filter(item => filters[key].includes(item[key]));
126 | });
127 | }
128 | if (sorter && sorter.column) {
129 | const { dataIndex } = sorter.column;
130 | if (sorter.order === 'descend') {
131 | totalList = [...totalList].sort(
132 | (a, b) => b[dataIndex].charCodeAt(0) - a[dataIndex].charCodeAt(0),
133 | );
134 | } else {
135 | totalList = [...totalList].sort(
136 | (a, b) => a[dataIndex].charCodeAt(0) - b[dataIndex].charCodeAt(0),
137 | );
138 | }
139 | }
140 | const list = totalList.slice(start, end);
141 | return new Promise(r =>
142 | setTimeout(() => {
143 | r({
144 | list,
145 | total: totalList.length,
146 | });
147 | }, 300),
148 | );
149 | }
150 | ```
151 |
152 | ### DefaultFormValues
153 |
154 | ```jsx
155 | import React from 'react';
156 | import { useFormTable } from 'sunflower-antd';
157 | import { Input, Button, Table, Form } from 'antd';
158 | import Mock from 'mockjs'; // mock request
159 |
160 | export default props => {
161 | const { formProps, tableProps, form } = useFormTable({
162 | async search(values) {
163 | const res = await request(values);
164 | return {
165 | dataSource: res.list,
166 | total: res.total,
167 | };
168 | },
169 | async defaultFormValues() {
170 | await new Promise(r => setTimeout(r, 200));
171 | return {
172 | username: 'j',
173 | };
174 | },
175 | });
176 |
177 | return (
178 |
179 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 | form.resetFields()}>Reset
190 |
191 |
192 |
193 |
194 | Search
195 |
196 |
197 |
198 |
199 |
216 |
217 | );
218 | };
219 |
220 | // mock request
221 | const total = 200;
222 | const db = Mock.mock({
223 | [`list|${total}`]: [
224 | {
225 | username: '@name',
226 | email: '@email',
227 | id: '@guid',
228 | 'gender|1': ['male', 'female'],
229 | },
230 | ],
231 | });
232 | function filter(list, dataIndex, keyword) {
233 | if (!keyword) {
234 | return list;
235 | }
236 | return list.filter(
237 | item =>
238 | item[dataIndex].toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) >
239 | -1,
240 | );
241 | }
242 | function request({ current, pageSize, filters, sorter, username, email }) {
243 | console.log(
244 | '-------> request: username: %s, pageSize: %s, current: %s, filters: %s, sorter: %s',
245 | username,
246 | pageSize,
247 | current,
248 | JSON.stringify(filters),
249 | JSON.stringify(sorter),
250 | );
251 | const start = pageSize * (current - 1);
252 | const end = start + pageSize;
253 | let totalList = db.list;
254 | totalList = filter(totalList, 'username', username);
255 | totalList = filter(totalList, 'email', email);
256 | if (filters) {
257 | Object.keys(filters).forEach(key => {
258 | if (!filters[key]) {
259 | return true;
260 | }
261 | if (filters[key].length === 0) {
262 | return true;
263 | }
264 | totalList = totalList.filter(item => filters[key].includes(item[key]));
265 | });
266 | }
267 | if (sorter && sorter.column) {
268 | const { dataIndex } = sorter.column;
269 | if (sorter.order === 'descend') {
270 | totalList = [...totalList].sort(
271 | (a, b) => b[dataIndex].charCodeAt(0) - a[dataIndex].charCodeAt(0),
272 | );
273 | } else {
274 | totalList = [...totalList].sort(
275 | (a, b) => a[dataIndex].charCodeAt(0) - b[dataIndex].charCodeAt(0),
276 | );
277 | }
278 | }
279 | const list = totalList.slice(start, end);
280 | return new Promise(r =>
281 | setTimeout(() => {
282 | r({
283 | list,
284 | total: totalList.length,
285 | });
286 | }, 300),
287 | );
288 | }
289 | ```
290 |
291 | ### Filters & Sorter
292 |
293 | ```jsx
294 | import React from 'react';
295 | import { useFormTable } from 'sunflower-antd';
296 | import { Input, Button, Form, Table } from 'antd';
297 | import Mock from 'mockjs'; // mock request
298 |
299 | export default props => {
300 | const { formProps, tableProps, sorter, filters, form } = useFormTable({
301 | async search(values) {
302 | const res = await request(values);
303 | return {
304 | dataSource: res.list,
305 | total: res.total,
306 | };
307 | },
308 | });
309 |
310 | return (
311 |
312 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 | form.resetFields()}>Reset
323 |
324 |
325 |
326 |
327 | Search
328 |
329 |
330 |
331 |
332 |
374 |
375 | );
376 | };
377 |
378 | // mock request
379 | const total = 200;
380 | const db = Mock.mock({
381 | [`list|${total}`]: [
382 | {
383 | username: '@name',
384 | email: '@email',
385 | id: '@guid',
386 | 'gender|1': ['male', 'female'],
387 | },
388 | ],
389 | });
390 | function filter(list, dataIndex, keyword) {
391 | if (!keyword) {
392 | return list;
393 | }
394 | return list.filter(
395 | item =>
396 | item[dataIndex].toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) >
397 | -1,
398 | );
399 | }
400 | function request({ current, pageSize, filters, sorter, username, email }) {
401 | console.log(
402 | '-------> request: username: %s, pageSize: %s, current: %s, filters: %s, sorter: %s',
403 | username,
404 | pageSize,
405 | current,
406 | JSON.stringify(filters),
407 | JSON.stringify(sorter),
408 | );
409 | const start = pageSize * (current - 1);
410 | const end = start + pageSize;
411 | let totalList = db.list;
412 | totalList = filter(totalList, 'username', username);
413 | totalList = filter(totalList, 'email', email);
414 | if (filters) {
415 | Object.keys(filters).forEach(key => {
416 | if (!filters[key]) {
417 | return true;
418 | }
419 | if (filters[key].length === 0) {
420 | return true;
421 | }
422 | totalList = totalList.filter(item => filters[key].includes(item[key]));
423 | });
424 | }
425 | if (sorter && sorter.column) {
426 | const { dataIndex } = sorter.column;
427 | if (sorter.order === 'descend') {
428 | totalList = [...totalList].sort(
429 | (a, b) => b[dataIndex].charCodeAt(0) - a[dataIndex].charCodeAt(0),
430 | );
431 | } else {
432 | totalList = [...totalList].sort(
433 | (a, b) => a[dataIndex].charCodeAt(0) - b[dataIndex].charCodeAt(0),
434 | );
435 | }
436 | }
437 | const list = totalList.slice(start, end);
438 | return new Promise(r =>
439 | setTimeout(() => {
440 | r({
441 | list,
442 | total: totalList.length,
443 | });
444 | }, 300),
445 | );
446 | }
447 | ```
448 |
449 | ## API
450 |
451 | ```js
452 | const result = useFormTable(config);
453 | ```
454 |
455 | ### Config
456 |
457 |
458 |
459 |
460 | Key
461 | Description
462 | Type
463 | Default
464 |
465 |
466 |
467 |
468 | search
469 | Request method, the parameter is the value of the form fields.The method needs to return an array or Promise of SearchResponseData.
470 | (requestData) => Promise<responseData> | responseData
471 |
472 |
473 |
474 | form
475 | Optional, decorated by useForm in antd4(Form.create() in antd3)
476 |
477 |
478 |
479 |
480 | autoFirstSearch
481 | Whether the search method will be executed automatically.
482 | boolean
483 | true
484 |
485 |
486 | defaultPageSize
487 | Default page size
488 | number
489 | 10
490 |
491 |
492 | defaultCurrent
493 | Default current page
494 | number
495 | 1
496 |
497 |
498 | defaultFormValues
499 | Default form values.If the form has data that needs to be backfilled, use it to get the data.
500 | object | () => Promise<object>
501 |
502 |
503 |
504 |
505 |
506 | - responseData
507 |
508 | ```js
509 | {
510 | list: [{
511 | name: 'jack',
512 | }, {
513 | name: 'lily',
514 | }],
515 | total: 10,
516 | }
517 | ```
518 |
519 | - requestData
520 |
521 | ```js
522 | search({ current, pageSize, filters, sorter, ...formValues }) {
523 |
524 | }
525 | ```
526 |
527 | ### Result
528 |
529 |
530 |
531 |
532 | Key
533 | Description
534 | Type
535 |
536 |
537 |
538 |
539 | formProps
540 | antd Form props
541 |
542 |
543 |
544 | tableProps
545 | antd Table props
546 |
547 |
548 |
549 | form
550 | Form instance
551 |
552 |
553 |
554 | loading
555 | Request loading.
556 | boolean
557 |
558 |
559 | current
560 | Current page.
561 | number
562 |
563 |
564 | pageSize
565 | Page size.
566 | number
567 |
568 |
569 | formValues
570 | Form values.
571 | object
572 |
573 |
574 | dataSource
575 | The value's 'dataSource prop' returned by the search method.
576 | array
577 |
578 |
579 | total
580 | The value's 'total prop' returned by the search method.
581 | number
582 |
583 |
584 | defaultFormValuesLoading
585 | When use 'defaultFormValues', the value will be true during the request.
586 | boolean
587 |
588 |
589 | filters
590 | antd Table filters
591 | object
592 |
593 |
594 | sorter
595 | antd Table sorter
596 | object
597 |
598 |
599 | search
600 | will call the 'search' method with custom request data
601 | (customRequestData) => void
602 |
603 |
604 |
605 |
--------------------------------------------------------------------------------
/src/useFormTable/index.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import Form from '../form';
3 | import {
4 | useSearchResult as useSearchResultHooks,
5 | UseSearchResultConfig,
6 | } from '../useSearchResult';
7 |
8 | declare type StoreBaseValue = string | number | boolean;
9 | declare type StoreValue = StoreBaseValue | Store | StoreBaseValue[];
10 | interface Store {
11 | [name: string]: StoreValue;
12 | }
13 | export interface SearchResponseData {
14 | dataSource: Store[];
15 | total?: number;
16 | }
17 |
18 | export interface UseSearchResultAntdConfig
19 | extends UseSearchResultConfig {
20 | defaultPageSize?: number;
21 | defaultCurrent?: number;
22 | defaultFormValues?: Store | (() => Promise | Store);
23 | form?: any;
24 | }
25 |
26 | export const useFormTable = (config: UseSearchResultAntdConfig) => {
27 | const formTableConfig = config || ({} as UseSearchResultAntdConfig);
28 | const {
29 | search,
30 | autoFirstSearch = true,
31 | defaultPageSize = 10,
32 | defaultCurrent = 1,
33 | defaultFormValues = {},
34 | form,
35 | } = formTableConfig;
36 |
37 | let version = 3;
38 | // antd4
39 | if (Form['useForm']) {
40 | version = 4;
41 | }
42 |
43 | let formInstance = form;
44 | if (!form) {
45 | if (version === 4) {
46 | [formInstance] = Form['useForm']();
47 | } else {
48 | throw new Error('"form" need in antd@3');
49 | }
50 | }
51 |
52 | const [initialValues, setInitialValues] = useState();
53 | const {
54 | loading,
55 | requestData = {} as Store,
56 | setRequestData,
57 | responseData = {} as SearchResponseData,
58 | defaultRequestDataLoading,
59 | search: searchFunc,
60 | } = useSearchResultHooks({
61 | search,
62 | autoFirstSearch,
63 | defaultRequestData: () => {
64 | let value: Store | Promise;
65 | if (typeof defaultFormValues === 'function') {
66 | value = defaultFormValues();
67 | } else {
68 | value = defaultFormValues;
69 | }
70 | return Promise.resolve(value).then(data => {
71 | const touched = formInstance.isFieldsTouched();
72 | const obj = { ...data };
73 | Object.keys(data).forEach(name => {
74 | obj[name] = formInstance.isFieldTouched(name)
75 | ? formInstance.getFieldValue(name)
76 | : data[name];
77 | });
78 | setInitialValues(data);
79 | formInstance.setFieldsValue(obj);
80 | if (touched) {
81 | setRequestData({
82 | pageSize: defaultPageSize,
83 | current: defaultCurrent,
84 | ...obj,
85 | });
86 | throw new Error('will not autoFirstSearch');
87 | }
88 | return {
89 | pageSize: defaultPageSize,
90 | current: defaultCurrent,
91 | ...obj,
92 | };
93 | });
94 | },
95 | });
96 |
97 | const onFinish = (values: Store) => {
98 | searchFunc({
99 | current: 1,
100 | pageSize: requestData.pageSize,
101 | ...values,
102 | });
103 | };
104 |
105 | const onChange = (pagination, filters, sorter) => {
106 | searchFunc({
107 | ...requestData,
108 | current:
109 | pagination.current === requestData.current ? 1 : pagination.current,
110 | pageSize: pagination.pageSize,
111 | filters,
112 | sorter,
113 | });
114 | };
115 |
116 | const formProps =
117 | version === 4
118 | ? {
119 | form: formInstance,
120 | onFinish,
121 | initialValues,
122 | }
123 | : {
124 | onSubmit(e) {
125 | e.preventDefault();
126 | formInstance.validateFields((err, values) => {
127 | if (!err) {
128 | searchFunc({
129 | current: 1,
130 | pageSize: requestData.pageSize,
131 | ...values,
132 | });
133 | }
134 | });
135 | },
136 | };
137 |
138 | const tableProps = {
139 | pagination: {
140 | pageSize: (requestData.pageSize || defaultPageSize) as number,
141 | current: requestData.current as number,
142 | defaultPageSize,
143 | defaultCurrent,
144 | total: responseData.total,
145 | },
146 | loading,
147 | dataSource: responseData.dataSource,
148 | onChange,
149 | };
150 |
151 | const formValues = { ...requestData };
152 | delete formValues.current;
153 | delete formValues.pageSize;
154 | delete formValues.filter;
155 | delete formValues.sorter;
156 |
157 | return {
158 | form: formInstance,
159 | formProps,
160 | tableProps,
161 | loading,
162 | defaultFormValuesLoading: defaultRequestDataLoading,
163 | formValues,
164 | filters: requestData.filters,
165 | sorter: requestData.sorter,
166 | current: requestData.current as number,
167 | pageSize: (requestData.pageSize || defaultPageSize) as number,
168 | dataSource: responseData.dataSource,
169 | total: responseData.total,
170 | search: data => {
171 | searchFunc({
172 | ...requestData,
173 | ...data,
174 | });
175 | },
176 | };
177 | };
178 |
--------------------------------------------------------------------------------
/src/useFormTable/index.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: 流程组件
4 | path: /zh-CN/process-components
5 | group:
6 | title: 流程组件
7 | path: /zh-CN/process-components
8 | title: useFormTable
9 | order: 1
10 | ---
11 |
12 | ## 说明
13 |
14 | 当你需要使用搜索列表这个场景,可使用 `useFormTable`。
15 |
16 | ## 示例
17 |
18 | ### 基础
19 |
20 | ```jsx
21 | import React from 'react';
22 | import { useFormTable } from 'sunflower-antd';
23 | import { Input, Button, Table, Form } from 'antd';
24 | import Mock from 'mockjs'; // mock request
25 |
26 | export default props => {
27 | const { formProps, tableProps, form } = useFormTable({
28 | async search(values) {
29 | const res = await request(values);
30 | return {
31 | dataSource: res.list,
32 | total: res.total,
33 | };
34 | },
35 | defaultPageSize: 5,
36 | });
37 |
38 | return (
39 |
40 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | form.resetFields()}>重置
51 |
52 |
53 |
54 |
55 | 搜索
56 |
57 |
58 |
59 |
60 |
77 |
78 | );
79 | };
80 |
81 | // mock request
82 | const total = 200;
83 | const db = Mock.mock({
84 | [`list|${total}`]: [
85 | {
86 | username: '@name',
87 | email: '@email',
88 | id: '@guid',
89 | 'gender|1': ['male', 'female'],
90 | },
91 | ],
92 | });
93 | function filter(list, dataIndex, keyword) {
94 | if (!keyword) {
95 | return list;
96 | }
97 | return list.filter(
98 | item =>
99 | item[dataIndex].toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) >
100 | -1,
101 | );
102 | }
103 | function request({ current, pageSize, filters, sorter, username, email }) {
104 | console.log(
105 | '-------> request: username: %s, pageSize: %s, current: %s, filters: %s, sorter: %s',
106 | username,
107 | pageSize,
108 | current,
109 | JSON.stringify(filters),
110 | JSON.stringify(sorter),
111 | );
112 | const start = pageSize * (current - 1);
113 | const end = start + pageSize;
114 | let totalList = db.list;
115 | totalList = filter(totalList, 'username', username);
116 | totalList = filter(totalList, 'email', email);
117 | if (filters) {
118 | Object.keys(filters).forEach(key => {
119 | if (!filters[key]) {
120 | return true;
121 | }
122 | if (filters[key].length === 0) {
123 | return true;
124 | }
125 | totalList = totalList.filter(item => filters[key].includes(item[key]));
126 | });
127 | }
128 | if (sorter && sorter.column) {
129 | const { dataIndex } = sorter.column;
130 | if (sorter.order === 'descend') {
131 | totalList = [...totalList].sort(
132 | (a, b) => b[dataIndex].charCodeAt(0) - a[dataIndex].charCodeAt(0),
133 | );
134 | } else {
135 | totalList = [...totalList].sort(
136 | (a, b) => a[dataIndex].charCodeAt(0) - b[dataIndex].charCodeAt(0),
137 | );
138 | }
139 | }
140 | const list = totalList.slice(start, end);
141 | return new Promise(r =>
142 | setTimeout(() => {
143 | r({
144 | list,
145 | total: totalList.length,
146 | });
147 | }, 300),
148 | );
149 | }
150 | ```
151 |
152 | ### 默认表单回填值
153 |
154 | ```jsx
155 | import React from 'react';
156 | import { useFormTable } from 'sunflower-antd';
157 | import { Input, Button, Table, Form } from 'antd';
158 | import Mock from 'mockjs'; // mock request
159 |
160 | export default props => {
161 | const { formProps, tableProps, form } = useFormTable({
162 | async search(values) {
163 | const res = await request(values);
164 | return {
165 | dataSource: res.list,
166 | total: res.total,
167 | };
168 | },
169 | async defaultFormValues() {
170 | await new Promise(r => setTimeout(r, 200));
171 | return {
172 | username: 'j',
173 | };
174 | },
175 | });
176 |
177 | return (
178 |
179 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 | form.resetFields()}>重置
190 |
191 |
192 |
193 |
194 | 搜索
195 |
196 |
197 |
198 |
199 |
216 |
217 | );
218 | };
219 |
220 | // mock request
221 | const total = 200;
222 | const db = Mock.mock({
223 | [`list|${total}`]: [
224 | {
225 | username: '@name',
226 | email: '@email',
227 | id: '@guid',
228 | 'gender|1': ['male', 'female'],
229 | },
230 | ],
231 | });
232 | function filter(list, dataIndex, keyword) {
233 | if (!keyword) {
234 | return list;
235 | }
236 | return list.filter(
237 | item =>
238 | item[dataIndex].toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) >
239 | -1,
240 | );
241 | }
242 | function request({ current, pageSize, filters, sorter, username, email }) {
243 | console.log(
244 | '-------> request: username: %s, pageSize: %s, current: %s, filters: %s, sorter: %s',
245 | username,
246 | pageSize,
247 | current,
248 | JSON.stringify(filters),
249 | JSON.stringify(sorter),
250 | );
251 | const start = pageSize * (current - 1);
252 | const end = start + pageSize;
253 | let totalList = db.list;
254 | totalList = filter(totalList, 'username', username);
255 | totalList = filter(totalList, 'email', email);
256 | if (filters) {
257 | Object.keys(filters).forEach(key => {
258 | if (!filters[key]) {
259 | return true;
260 | }
261 | if (filters[key].length === 0) {
262 | return true;
263 | }
264 | totalList = totalList.filter(item => filters[key].includes(item[key]));
265 | });
266 | }
267 | if (sorter && sorter.column) {
268 | const { dataIndex } = sorter.column;
269 | if (sorter.order === 'descend') {
270 | totalList = [...totalList].sort(
271 | (a, b) => b[dataIndex].charCodeAt(0) - a[dataIndex].charCodeAt(0),
272 | );
273 | } else {
274 | totalList = [...totalList].sort(
275 | (a, b) => a[dataIndex].charCodeAt(0) - b[dataIndex].charCodeAt(0),
276 | );
277 | }
278 | }
279 | const list = totalList.slice(start, end);
280 | return new Promise(r =>
281 | setTimeout(() => {
282 | r({
283 | list,
284 | total: totalList.length,
285 | });
286 | }, 300),
287 | );
288 | }
289 | ```
290 |
291 | ### 过滤、排序
292 |
293 | ```jsx
294 | import React from 'react';
295 | import { useFormTable } from 'sunflower-antd';
296 | import { Input, Button, Form, Table } from 'antd';
297 | import Mock from 'mockjs'; // mock request
298 |
299 | export default props => {
300 | const { formProps, tableProps, sorter, filters, form } = useFormTable({
301 | async search(values) {
302 | const res = await request(values);
303 | return {
304 | dataSource: res.list,
305 | total: res.total,
306 | };
307 | },
308 | });
309 |
310 | return (
311 |
312 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 | form.resetFields()}>重置
323 |
324 |
325 |
326 |
327 | 搜索
328 |
329 |
330 |
331 |
332 |
374 |
375 | );
376 | };
377 |
378 | // mock request
379 | const total = 200;
380 | const db = Mock.mock({
381 | [`list|${total}`]: [
382 | {
383 | username: '@name',
384 | email: '@email',
385 | id: '@guid',
386 | 'gender|1': ['male', 'female'],
387 | },
388 | ],
389 | });
390 | function filter(list, dataIndex, keyword) {
391 | if (!keyword) {
392 | return list;
393 | }
394 | return list.filter(
395 | item =>
396 | item[dataIndex].toLocaleLowerCase().indexOf(keyword.toLocaleLowerCase()) >
397 | -1,
398 | );
399 | }
400 | function request({ current, pageSize, filters, sorter, username, email }) {
401 | console.log(
402 | '-------> request: username: %s, pageSize: %s, current: %s, filters: %s, sorter: %s',
403 | username,
404 | pageSize,
405 | current,
406 | JSON.stringify(filters),
407 | JSON.stringify(sorter),
408 | );
409 | const start = pageSize * (current - 1);
410 | const end = start + pageSize;
411 | let totalList = db.list;
412 | totalList = filter(totalList, 'username', username);
413 | totalList = filter(totalList, 'email', email);
414 | if (filters) {
415 | Object.keys(filters).forEach(key => {
416 | if (!filters[key]) {
417 | return true;
418 | }
419 | if (filters[key].length === 0) {
420 | return true;
421 | }
422 | totalList = totalList.filter(item => filters[key].includes(item[key]));
423 | });
424 | }
425 | if (sorter && sorter.column) {
426 | const { dataIndex } = sorter.column;
427 | if (sorter.order === 'descend') {
428 | totalList = [...totalList].sort(
429 | (a, b) => b[dataIndex].charCodeAt(0) - a[dataIndex].charCodeAt(0),
430 | );
431 | } else {
432 | totalList = [...totalList].sort(
433 | (a, b) => a[dataIndex].charCodeAt(0) - b[dataIndex].charCodeAt(0),
434 | );
435 | }
436 | }
437 | const list = totalList.slice(start, end);
438 | return new Promise(r =>
439 | setTimeout(() => {
440 | r({
441 | list,
442 | total: totalList.length,
443 | });
444 | }, 300),
445 | );
446 | }
447 | ```
448 |
449 | ## API
450 |
451 | ```js
452 | const result = useFormTable(config);
453 | ```
454 |
455 | ### Config
456 |
457 |
458 |
459 |
460 | 名称
461 | 说明
462 | 类型
463 | 默认值
464 |
465 |
466 |
467 |
468 | search
469 | 请求方法,其参数为 form fields 的值
470 | (requestData) => Promise<responseData> | responseData
471 |
472 |
473 |
474 | form
475 | 可选。在 antd4 中为 useForm 返回的 form 实例。在 antd3 中 Form.create() props.form.
476 |
477 |
478 |
479 |
480 | autoFirstSearch
481 | 是否组件渲染就进行调用表单查询
482 | boolean
483 | true
484 |
485 |
486 | defaultPageSize
487 | 默认的分页大小
488 | number
489 | 10
490 |
491 |
492 | defaultCurrent
493 | 默认的当前页
494 | number
495 | 1
496 |
497 |
498 | defaultFormValues
499 | 默认的表单回填值,可为一个对象,也可为一个异步方法。
500 | object | () => Promise<object>
501 |
502 |
503 |
504 |
505 |
506 | - responseData
507 |
508 | ```js
509 | {
510 | list: [{
511 | name: 'jack',
512 | }, {
513 | name: 'lily',
514 | }],
515 | total: 10,
516 | }
517 | ```
518 |
519 | - requestData
520 |
521 | ```js
522 | search({ current, pageSize, filters, sorter, ...formValues }) {
523 |
524 | }
525 | ```
526 |
527 | ### Result
528 |
529 |
530 |
531 |
532 | 名称
533 | 说明
534 | 类型
535 |
536 |
537 |
538 |
539 | formProps
540 | antd Form 组件的 props,作为 Form 组件的 props 即可
541 |
542 |
543 |
544 | tableProps
545 | antd Table 组件的 props,作为 Table 组件的 props 即可
546 |
547 |
548 |
549 | form
550 | antd Form 的实例,可访问查看 form 实例的方法
551 |
552 |
553 |
554 | loading
555 | 正在搜索
556 | boolean
557 |
558 |
559 | current
560 | 当前第几页
561 | number
562 |
563 |
564 | pageSize
565 | 分页大小
566 | number
567 |
568 |
569 | formValues
570 | 对应 form fileds 的值
571 | object
572 |
573 |
574 | dataSource
575 | search 方法返回的 dataSource
576 | array
577 |
578 |
579 | total
580 | search 方法返回的 total
581 | number
582 |
583 |
584 | defaultFormValuesLoading
585 | 当使用 defaultFormValues 在请求表单回填值的 loading
586 | boolean
587 |
588 |
589 | filters
590 | antd Table 组件的 filters
591 | object
592 |
593 |
594 | sorter
595 | antd Table 组件的 sorter
596 | object
597 |
598 |
599 | search
600 | search 方法,可主动触发,可传入自定义请求参数
601 | (customrequestData) => void
602 |
603 |
604 |
605 |
--------------------------------------------------------------------------------
/src/useModal/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: Process Components
4 | path: /process-components
5 | group:
6 | title: Process Components
7 | path: /process-components
8 | title: useModal
9 | order: 6
10 | ---
11 |
12 | ## Overview
13 |
14 | When you want to use `antd Modal`, you can use it
15 |
16 | ## Examples
17 |
18 | ### Basic
19 |
20 | ```jsx
21 | import React from 'react';
22 | import { useModal } from 'sunflower-antd';
23 | import { Modal, Input, Button, Form, message } from 'antd';
24 |
25 | export default props => {
26 | const [form] = Form.useForm();
27 | const { modalProps, show, close } = useModal({
28 | defaultVisible: false,
29 | });
30 | const onSubmit = () => {
31 | form
32 | .validateFields()
33 | .then(async value => {
34 | await new Promise(r => setTimeout(r, 1000));
35 | form.resetFields();
36 | message.success('提交成功');
37 | close();
38 | })
39 | .catch(err => {
40 | console.log(err);
41 | });
42 | };
43 | return (
44 |
45 |
52 |
58 |
59 |
60 |
61 |
72 |
73 |
74 |
75 |
76 | show
77 |
78 | );
79 | };
80 | ```
81 |
82 | ## API
83 |
84 | ```js
85 | const result = useModal(config);
86 | ```
87 |
88 | ### Config
89 |
90 |
91 |
92 |
93 | Key
94 | Description
95 | Type
96 | Default
97 |
98 |
99 |
100 |
101 | defaultVisible
102 | Whether the modal dialog is visible or not
103 | boolean
104 | false
105 |
106 |
107 |
108 |
109 | ### Result
110 |
111 |
112 |
113 |
114 | Key
115 | Description
116 | Type
117 |
118 |
119 |
120 |
121 | modalProps
122 | antd Modal props
123 |
124 |
125 |
126 | show
127 | Specify a function that can open the modal
128 | () => void
129 |
130 |
131 | close
132 | Specify a function that can close the modal
133 | () => void
134 |
135 |
136 | visible
137 | Whether the modal dialog is visible or not
138 | boolean
139 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/src/useModal/index.ts:
--------------------------------------------------------------------------------
1 | import { useState, useCallback } from 'react';
2 |
3 | export interface UseModalConfig {
4 | defaultVisible: boolean;
5 | }
6 |
7 | export const useModal = (config: UseModalConfig) => {
8 | const modalConfig = config || ({} as UseModalConfig);
9 | const { defaultVisible = false } = modalConfig;
10 |
11 | const [visible, setVisible] = useState(defaultVisible);
12 | const show = useCallback(() => setVisible(true), [visible]);
13 | const close = useCallback(() => setVisible(false), [visible]);
14 |
15 | const modalProps = {
16 | visible,
17 | onCancel: close,
18 | };
19 | return {
20 | visible,
21 | show,
22 | close,
23 | modalProps,
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/src/useModal/index.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: 流程组件
4 | path: /zh-CN/process-components
5 | group:
6 | title: 流程组件
7 | path: /zh-CN/process-components
8 | title: useModal
9 | order: 6
10 | ---
11 |
12 | ## 说明
13 |
14 | 当你需要使用`antd Modal`,可使用 `useModal`。
15 |
16 | ## 示例
17 |
18 | ### 基础
19 |
20 | ```jsx
21 | import React from 'react';
22 | import { useModal } from 'sunflower-antd';
23 | import { Modal, Input, Button, Form, message } from 'antd';
24 |
25 | export default props => {
26 | const [form] = Form.useForm();
27 | const { modalProps, show, close } = useModal({
28 | defaultVisible: false,
29 | });
30 | const onSubmit = () => {
31 | form
32 | .validateFields()
33 | .then(async value => {
34 | await new Promise(r => setTimeout(r, 1000));
35 | form.resetFields();
36 | message.success('提交成功');
37 | close();
38 | })
39 | .catch(err => {
40 | console.log(err);
41 | });
42 | };
43 | return (
44 |
45 |
52 |
58 |
59 |
60 |
61 |
72 |
73 |
74 |
75 |
76 | show
77 |
78 | );
79 | };
80 | ```
81 |
82 | ## API
83 |
84 | ```js
85 | const result = useModal(config);
86 | ```
87 |
88 | ### Config
89 |
90 |
91 |
92 |
93 | 名称
94 | 说明
95 | 类型
96 | 默认值
97 |
98 |
99 |
100 |
101 | defaultVisible
102 | 弹窗是否可见
103 | boolean
104 | false
105 |
106 |
107 |
108 |
109 | ### Result
110 |
111 |
112 |
113 |
114 | 名称
115 | 说明
116 | 类型
117 |
118 |
119 |
120 |
121 | modalProps
122 | antd Modal 组件的 props,作为 Modal 组件的 props 即可
123 |
124 |
125 |
126 | show
127 | 打开弹窗
128 | () => void
129 |
130 |
131 | close
132 | 关闭弹窗
133 | () => void
134 |
135 |
136 | visible
137 | 弹窗当前显隐状态
138 | boolean
139 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/src/useModalForm/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: Process Components
4 | path: /process-components
5 | group:
6 | title: Process Components
7 | path: /process-components
8 | title: useModalForm
9 | order: 3
10 | ---
11 |
12 | ## Overview
13 |
14 | When you want to use "Modal Form", you can use it.
15 |
16 | ## Examples
17 |
18 | ### Basic
19 |
20 | ```jsx
21 | import React from 'react';
22 | import { useModalForm } from 'sunflower-antd';
23 | import { Modal, Input, Button, Form, Spin } from 'antd';
24 |
25 | export default props => {
26 | const [form] = Form.useForm();
27 | const {
28 | modalProps,
29 | formProps,
30 | show,
31 | formLoading,
32 | formValues,
33 | formResult,
34 | } = useModalForm({
35 | defaultVisible: false,
36 | autoSubmitClose: true,
37 | autoResetForm: true,
38 | async submit({ username, email }) {
39 | console.log('beforeSubmit');
40 | await new Promise(r => setTimeout(r, 1000));
41 | console.log('afterSubmit', username, email);
42 | return 'ok';
43 | },
44 | form,
45 | });
46 | return (
47 |
83 | );
84 | };
85 | ```
86 |
87 | ### DefaultFormValues
88 |
89 | ```jsx
90 | import React from 'react';
91 | import { useModalForm } from 'sunflower-antd';
92 | import { Modal, Input, Button, Form, Spin } from 'antd';
93 |
94 | export default props => {
95 | const [form] = Form.useForm();
96 | const {
97 | modalProps,
98 | formProps,
99 | show,
100 | formLoading,
101 | defaultFormValuesLoading,
102 | } = useModalForm({
103 | defaultVisible: false,
104 | autoSubmitClose: true,
105 | form,
106 | async submit({ username, email }) {
107 | console.log('beforeSubmit');
108 | await new Promise(r => setTimeout(r, 1000));
109 | console.log('afterSubmit', `username:${username}, email:${email}`);
110 | return 'ok';
111 | },
112 | async defaultFormValues() {
113 | await new Promise(r => setTimeout(r, 3000));
114 | return {
115 | username: 'lily',
116 | };
117 | },
118 | });
119 | return (
120 |
121 |
122 |
123 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | show
135 |
136 | );
137 | };
138 | ```
139 |
140 | ### Submit method
141 |
142 | ```jsx
143 | import React from 'react';
144 | import { useModalForm } from 'sunflower-antd';
145 | import { Modal, Input, Button, Form, Spin } from 'antd';
146 |
147 | export default props => {
148 | const [form] = Form.useForm();
149 | const {
150 | modalProps,
151 | formProps,
152 | show,
153 | formLoading,
154 | submit: formSubmit,
155 | } = useModalForm({
156 | defaultVisible: false,
157 | autoSubmitClose: true,
158 | async submit({ username, email }) {
159 | console.log('beforeSubmit');
160 | await new Promise(r => setTimeout(r, 1000));
161 | console.log('afterSubmit', username, email);
162 | return 'ok';
163 | },
164 | form,
165 | });
166 | return (
167 |
168 |
169 |
170 |
172 |
173 |
174 |
175 |
186 |
187 |
188 |
189 |
190 |
192 | formSubmit({ username: 'lily' }).then(data =>
193 | console.log(data),
194 | )
195 | }
196 | type="primary"
197 | style={{ marginTop: 20 }}
198 | >
199 | call submit method
200 |
201 |
202 |
203 |
204 |
205 | show
206 |
207 | );
208 | };
209 | ```
210 |
211 | ### Default form submit
212 |
213 | ```jsx
214 | import React from 'react';
215 | import { useModalForm } from 'sunflower-antd';
216 | import { Modal, Input, Button, Form, Spin } from 'antd';
217 |
218 | export default props => {
219 | const [form] = Form.useForm();
220 | const {
221 | modalProps,
222 | formProps,
223 | show,
224 | formLoading,
225 | form: formInstance,
226 | } = useModalForm({
227 | defaultVisible: false,
228 | autoSubmitClose: true,
229 | async submit({ username, email }) {
230 | console.log('beforeSubmit');
231 | await new Promise(r => setTimeout(r, 1000));
232 | console.log('afterSubmit', username, email);
233 | return 'ok';
234 | },
235 | form,
236 | });
237 | return (
238 |
239 |
240 |
241 |
243 |
244 |
245 |
246 |
257 |
258 |
259 |
260 |
261 | formInstance.resetFields()}>Reset
262 |
263 |
264 |
265 |
266 | submit
267 |
268 |
269 |
270 |
271 |
272 | show
273 |
274 | );
275 | };
276 | ```
277 |
278 | ## API
279 |
280 | ```js
281 | const result = useModalForm(config);
282 | ```
283 |
284 | ### Config
285 |
286 |
287 |
288 |
289 | Key
290 | Description
291 | Type
292 | Default
293 |
294 |
295 |
296 |
297 | defaultVisible
298 | Whether the modal dialog is visible or not
299 | boolean
300 | false
301 |
302 |
303 | autoSubmitClose
304 | Click Modal "ok", will trigger submit, then close modal
305 | boolean
306 | true
307 |
308 |
309 | autoResetForm
310 | Reset the specified fields' value(to initialValue) and status
311 | boolean
312 | true
313 |
314 |
315 | submit
316 | submit method, the parameter is the value of the form fields
317 | (formValues) => Promise<formResult> | formResult
318 |
319 |
320 |
321 | form
322 | Decorated by Form.create() will be automatically set this.props.form property(antd3); useForm react hooks(antd4)
323 |
324 |
325 |
326 |
327 | defaultFormValues
328 | Default form values.If the form has data that needs to be backfilled, use it to get the data.
329 | object | () => Promise<object>
330 |
331 |
332 |
333 |
334 |
335 | ### Result
336 |
337 |
338 |
339 |
340 | Key
341 | Description
342 | Type
343 |
344 |
345 |
346 |
347 | modalProps
348 | antd Modal props
349 |
350 |
351 |
352 | show
353 | Specify a function that can open the modal
354 | () => void
355 |
356 |
357 | close
358 | Specify a function that can close the modal
359 | () => void
360 |
361 |
362 | visible
363 | Whether the modal dialog is visible or not
364 | boolean
365 |
366 |
367 | formProps
368 | antd Form props
369 |
370 |
371 |
372 | form
373 | Form instance
374 |
375 |
376 |
377 | formLoading
378 | form request loading.
379 | boolean
380 |
381 |
382 | formValues
383 | form values
384 | object
385 |
386 |
387 | initialValues
388 | initial form values
389 | object
390 |
391 |
392 | formResult
393 | submit return value
394 |
395 |
396 |
397 | defaultFormValuesLoading
398 | When use 'defaultFormValues', the value will be true during the request.
399 | boolean
400 |
401 |
402 | submit
403 | will call the 'submit' method
404 |
405 |
406 |
407 |
408 |
--------------------------------------------------------------------------------
/src/useModalForm/index.ts:
--------------------------------------------------------------------------------
1 | import { useModal } from '../useModal';
2 | import { useForm, UseFormConfig } from '../useForm';
3 |
4 | export interface UseModalFormConfig extends UseFormConfig {
5 | defaultVisible?: boolean;
6 | autoSubmitClose?: boolean;
7 | autoResetForm?: boolean;
8 | }
9 |
10 | export const useModalForm = (config: UseModalFormConfig) => {
11 | const modalFormConfig = config || ({} as UseModalFormConfig);
12 | const {
13 | defaultVisible = false,
14 | autoSubmitClose = true,
15 | autoResetForm = true,
16 | submit,
17 | form,
18 | defaultFormValues,
19 | } = modalFormConfig;
20 |
21 | const { visible, show, close, modalProps } = useModal({
22 | defaultVisible,
23 | });
24 |
25 | const {
26 | form: formInstance,
27 | formProps,
28 | formLoading,
29 | defaultFormValuesLoading,
30 | formValues,
31 | initialValues,
32 | formResult,
33 | submit: formSubmit,
34 | } = useForm({
35 | form,
36 | submit,
37 | defaultFormValues,
38 | });
39 |
40 | const modalFormProps = {
41 | ...modalProps,
42 | onOk: () => {
43 | formSubmit().then(() => {
44 | if (autoSubmitClose) {
45 | close();
46 | }
47 |
48 | if (autoResetForm) {
49 | formInstance.resetFields();
50 | }
51 | });
52 | },
53 | };
54 |
55 | return {
56 | form: formInstance,
57 | visible,
58 | show,
59 | close,
60 | modalProps: modalFormProps,
61 | formProps,
62 | formLoading,
63 | defaultFormValuesLoading,
64 | formValues,
65 | initialValues,
66 | formResult,
67 | submit: formSubmit,
68 | };
69 | };
70 |
--------------------------------------------------------------------------------
/src/useModalForm/index.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: 流程组件
4 | path: /zh-CN/process-components
5 | group:
6 | title: 流程组件
7 | path: /zh-CN/process-components
8 | title: useModalForm
9 | order: 3
10 | ---
11 |
12 | ## 说明
13 |
14 | 当你需要在关闭弹窗时自动提交表单,可使用 `useModalForm`。
15 |
16 | ## 示例
17 |
18 | ### 基础
19 |
20 | ```jsx
21 | import React from 'react';
22 | import { useModalForm } from 'sunflower-antd';
23 | import { Modal, Input, Button, Form, Spin } from 'antd';
24 |
25 | export default props => {
26 | const [form] = Form.useForm();
27 | const {
28 | modalProps,
29 | formProps,
30 | show,
31 | formLoading,
32 | formValues,
33 | formResult,
34 | } = useModalForm({
35 | defaultVisible: false,
36 | autoSubmitClose: true,
37 | autoResetForm: true,
38 | async submit({ username, email }) {
39 | console.log('beforeSubmit');
40 | await new Promise(r => setTimeout(r, 1000));
41 | console.log('afterSubmit', username, email);
42 | return 'ok';
43 | },
44 | form,
45 | });
46 | return (
47 |
83 | );
84 | };
85 | ```
86 |
87 | ### DefaultFormValues
88 |
89 | ```jsx
90 | import React from 'react';
91 | import { useModalForm } from 'sunflower-antd';
92 | import { Modal, Input, Button, Form, Spin } from 'antd';
93 |
94 | export default props => {
95 | const [form] = Form.useForm();
96 | const {
97 | modalProps,
98 | formProps,
99 | show,
100 | formLoading,
101 | defaultFormValuesLoading,
102 | } = useModalForm({
103 | defaultVisible: false,
104 | autoSubmitClose: true,
105 | form,
106 | async submit({ username, email }) {
107 | console.log('beforeSubmit');
108 | await new Promise(r => setTimeout(r, 1000));
109 | console.log('afterSubmit', `username:${username}, email:${email}`);
110 | return 'ok';
111 | },
112 | async defaultFormValues() {
113 | await new Promise(r => setTimeout(r, 3000));
114 | return {
115 | username: 'lily',
116 | };
117 | },
118 | });
119 | return (
120 |
121 |
122 |
123 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | show
135 |
136 | );
137 | };
138 | ```
139 |
140 | ### Submit method
141 |
142 | ```jsx
143 | import React from 'react';
144 | import { useModalForm } from 'sunflower-antd';
145 | import { Modal, Input, Button, Form, Spin } from 'antd';
146 |
147 | export default props => {
148 | const [form] = Form.useForm();
149 | const {
150 | modalProps,
151 | formProps,
152 | show,
153 | formLoading,
154 | submit: formSubmit,
155 | } = useModalForm({
156 | defaultVisible: false,
157 | autoSubmitClose: true,
158 | async submit({ username, email }) {
159 | console.log('beforeSubmit');
160 | await new Promise(r => setTimeout(r, 1000));
161 | console.log('afterSubmit', username, email);
162 | return 'ok';
163 | },
164 | form,
165 | });
166 | return (
167 |
168 |
169 |
170 |
172 |
173 |
174 |
175 |
186 |
187 |
188 |
189 |
190 |
192 | formSubmit({ username: 'lily' }).then(data =>
193 | console.log(data),
194 | )
195 | }
196 | type="primary"
197 | style={{ marginTop: 20 }}
198 | >
199 | call submit method
200 |
201 |
202 |
203 |
204 |
205 | show
206 |
207 | );
208 | };
209 | ```
210 |
211 | ### Default form submit
212 |
213 | ```jsx
214 | import React from 'react';
215 | import { useModalForm } from 'sunflower-antd';
216 | import { Modal, Input, Button, Form, Spin } from 'antd';
217 |
218 | export default props => {
219 | const [form] = Form.useForm();
220 | const {
221 | modalProps,
222 | formProps,
223 | show,
224 | formLoading,
225 | form: formInstance,
226 | } = useModalForm({
227 | defaultVisible: false,
228 | autoSubmitClose: true,
229 | async submit({ username, email }) {
230 | console.log('beforeSubmit');
231 | await new Promise(r => setTimeout(r, 1000));
232 | console.log('afterSubmit', username, email);
233 | return 'ok';
234 | },
235 | form,
236 | });
237 | return (
238 |
239 |
240 |
241 |
243 |
244 |
245 |
246 |
257 |
258 |
259 |
260 |
261 | formInstance.resetFields()}>Reset
262 |
263 |
264 |
265 |
266 | submit
267 |
268 |
269 |
270 |
271 |
272 | show
273 |
274 | );
275 | };
276 | ```
277 |
278 | ## API
279 |
280 | ```js
281 | const result = useModalForm(config);
282 | ```
283 |
284 | ### Config
285 |
286 |
287 |
288 |
289 | 名称
290 | 说明
291 | 类型
292 | 默认值
293 |
294 |
295 |
296 |
297 | defaultVisible
298 | 弹窗默认是否可见
299 | boolean
300 | false
301 |
302 |
303 | autoSubmitClose
304 | 点击确认, 提交表单后是否自动关闭弹窗
305 | boolean
306 | true
307 |
308 |
309 | autoResetForm
310 | 点击确认, 提交表单后是否重置initialValues
311 | boolean
312 | true
313 |
314 |
315 | submit
316 | 请求方法, 其参数为 form fields 的值
317 | (formValues) => Promise<formResult> | formResult
318 |
319 |
320 |
321 | form
322 | 在 antd4 中为 useForm 返回的 form 实例。在 antd3 中 Form.create() props.form
323 |
324 |
325 |
326 |
327 | defaultFormValues
328 | 默认的表单回填值,可为一个对象,也可为一个异步方法
329 | object | () => Promise<object>
330 |
331 |
332 |
333 |
334 |
335 | ### Result
336 |
337 |
338 |
339 |
340 | 名称
341 | 说明
342 | 类型
343 |
344 |
345 |
346 |
347 | modalProps
348 | antd Modal 组件的 props,作为 Modal 组件的 props 即可
349 |
350 |
351 |
352 | show
353 | 打开弹窗
354 | () => void
355 |
356 |
357 | close
358 | 关闭弹窗
359 | () => void
360 |
361 |
362 | visible
363 | 弹窗当前显隐状态
364 | boolean
365 |
366 |
367 | formProps
368 | antd Form 组件的 props,作为 Form 组件的 props 即可
369 |
370 |
371 |
372 | form
373 | antd Form 的实例,可访问查看 form 实例的方法
374 |
375 |
376 |
377 | formLoading
378 | 正在提交表单
379 | boolean
380 |
381 |
382 | formValues
383 | 对应 form fileds 的值
384 | object
385 |
386 |
387 | initialValues
388 | 表单默认值. 对应defaultFormValues
389 | object
390 |
391 |
392 | formResult
393 | submit返回值
394 | any
395 |
396 |
397 | defaultFormValuesLoading
398 | 当使用 defaultFormValues 在请求表单回填值的 loading.
399 | boolean
400 |
401 |
402 | submit
403 | submit 方法. 可主动触发, 提交表单, 其参数为 form fields 的值
404 | (formValues) => Promise<formResult>
405 |
406 |
407 |
408 |
--------------------------------------------------------------------------------
/src/useSearchResult/index.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 |
3 | export interface UseSearchResultConfig {
4 | search?: (requestData: S) => Promise | T;
5 | autoFirstSearch?: boolean;
6 | defaultRequestData?: S | (() => Promise | S);
7 | }
8 |
9 | export const useSearchResult = ({
10 | search,
11 | autoFirstSearch = true,
12 | defaultRequestData,
13 | }: UseSearchResultConfig) => {
14 | const [requestData, setRequestData] = useState();
15 | const [responseData, setResponseData] = useState();
16 | const [loading, setLoading] = useState(false);
17 | const [defaultRequestDataLoading, setDefaultRequestDataLoading] = useState(
18 | false,
19 | );
20 |
21 | const searchFunc = (data: S) => {
22 | setRequestData(data);
23 | setLoading(true);
24 | return Promise.resolve(search && search(data)).then(response => {
25 | setResponseData(response);
26 | setLoading(false);
27 | });
28 | };
29 |
30 | useEffect(() => {
31 | setDefaultRequestDataLoading(true);
32 | let value;
33 | if (typeof defaultRequestData === 'function') {
34 | value = (defaultRequestData as any)();
35 | } else {
36 | value = defaultRequestData;
37 | }
38 | Promise.resolve(value).then(data => {
39 | setRequestData(data);
40 | setDefaultRequestDataLoading(false);
41 | if (autoFirstSearch) {
42 | searchFunc(data);
43 | }
44 | }).catch(() => {
45 | setDefaultRequestDataLoading(false);
46 | });
47 | }, []);
48 |
49 | return {
50 | loading,
51 | requestData,
52 | setRequestData,
53 | responseData,
54 | defaultRequestDataLoading,
55 | search: searchFunc,
56 | };
57 | };
58 |
--------------------------------------------------------------------------------
/src/useStepsForm/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: Process Components
4 | path: /process-components
5 | group:
6 | title: Process Components
7 | path: /process-components
8 | title: useStepsForm
9 | order: 4
10 | ---
11 |
12 | ## Overview
13 |
14 | `useStepsForm` is a react-hook. When you want to use "Steps Form", you can use it.
15 |
16 | ## Examples
17 |
18 | ### Basic
19 |
20 | ```jsx
21 | import React from 'react';
22 | import { useStepsForm } from 'sunflower-antd';
23 | import { Steps, Input, Button, Form, Result } from 'antd';
24 |
25 | const { Step } = Steps;
26 |
27 | const layout = {
28 | labelCol: { span: 8 },
29 | wrapperCol: { span: 16 },
30 | };
31 | const tailLayout = {
32 | wrapperCol: { offset: 8, span: 16 },
33 | };
34 |
35 | export default props => {
36 | const {
37 | form,
38 | current,
39 | gotoStep,
40 | stepsProps,
41 | formProps,
42 | submit,
43 | formLoading,
44 | } = useStepsForm({
45 | async submit(values) {
46 | const { username, email, address } = values;
47 | console.log(username, email, address);
48 | await new Promise(r => setTimeout(r, 1000));
49 | return 'ok';
50 | },
51 | total: 3,
52 | });
53 |
54 | const formList = [
55 | <>
56 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | gotoStep(current + 1)}>Next
73 |
74 | >,
75 |
76 | <>
77 |
87 |
88 |
89 |
90 | {
95 | submit().then(result => {
96 | if (result === 'ok') {
97 | gotoStep(current + 1);
98 | }
99 | });
100 | }}
101 | >
102 | Submit
103 |
104 | gotoStep(current - 1)}>Prev
105 |
106 | >,
107 | ];
108 |
109 | return (
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
121 |
122 | {current === 2 && (
123 |
128 | {
131 | form.resetFields();
132 | gotoStep(0);
133 | }}
134 | >
135 | Buy it again
136 |
137 | Check detail
138 | >
139 | }
140 | />
141 | )}
142 |
143 |
144 | );
145 | };
146 | ```
147 |
148 | ## API
149 |
150 | ```js
151 | const Result = useStepsForm(Config);
152 | ```
153 |
154 | ### Config
155 |
156 |
157 |
158 |
159 | Key
160 | Description
161 | Type
162 | Default
163 |
164 |
165 |
166 |
167 | submit
168 | submit method, the parameter is the value of the form fields
169 | (formValues) => Promise<formResult> | formResult
170 |
171 |
172 |
173 | form
174 | Decorated by Form.create() will be automatically set this.props.form property(antd3); useForm react hooks(antd4)
175 |
176 |
177 |
178 |
179 | defaultFormValues
180 | Default form values.If the form has data that needs to be backfilled, use it to get the data.
181 | object
182 |
183 |
184 |
185 | defaultCurrent
186 | Default step, counting from 0.
187 | number
188 |
189 |
190 |
191 | total
192 | total counting for steps.
193 | number
194 |
195 |
196 |
197 | isBackValidate
198 | should validate if go to prev step
199 | boolean
200 | true
201 |
202 |
203 |
204 |
205 | ### Result
206 |
207 |
208 |
209 |
210 | Key
211 | Description
212 | Type
213 |
214 |
215 |
216 |
217 | current
218 | current step, counting from 0.
219 | number
220 |
221 |
222 | gotoStep
223 | goto the target step. When use it, the hook will validate current form at first.
224 | (step: number) => void
225 |
226 |
227 | stepsProps
228 | antd Steps props
229 | object
230 |
231 |
232 | formProps
233 | antd Form props
234 | object
235 |
236 |
237 | form
238 | Form instance
239 |
240 |
241 |
242 | formLoading
243 | form request loading.
244 | boolean
245 |
246 |
247 | formValues
248 | form values
249 | object
250 |
251 |
252 | initialValues
253 | initial form values
254 | object
255 |
256 |
257 | formResult
258 | submit return value
259 |
260 |
261 |
262 | defaultFormValuesLoading
263 | When use 'defaultFormValues', the value will be true during the request.
264 | boolean
265 |
266 |
267 | submit
268 | will call the 'submit' method
269 |
270 |
271 |
272 |
273 |
--------------------------------------------------------------------------------
/src/useStepsForm/index.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { useForm, UseFormConfig } from '../useForm';
3 |
4 | export interface UseStepsFormConfig extends UseFormConfig {
5 | defaultCurrent?: number;
6 | total?: number;
7 | isBackValidate?: boolean;
8 | }
9 |
10 | export const useStepsForm = (config: UseStepsFormConfig) => {
11 | const {
12 | form,
13 | defaultFormValues,
14 | defaultCurrent = 0,
15 | submit,
16 | total,
17 | isBackValidate = true,
18 | } = config || ({} as UseStepsFormConfig);
19 | const [current, setCurrent] = useState(defaultCurrent);
20 |
21 | const {
22 | form: formInstance,
23 | formProps,
24 | formLoading,
25 | defaultFormValuesLoading,
26 | formValues,
27 | initialValues,
28 | formResult,
29 | submit: formSubmit,
30 | } = useForm({
31 | form,
32 | submit,
33 | defaultFormValues,
34 | });
35 |
36 | const go = step => {
37 | let targetStep = step;
38 |
39 | if (step > total - 1) {
40 | targetStep = total - 1;
41 | }
42 |
43 | if (step < 0) {
44 | targetStep = 0;
45 | }
46 |
47 | setCurrent(targetStep);
48 | };
49 |
50 | const gotoStep = step => {
51 | if (step === current) {
52 | return true;
53 | }
54 |
55 | if (step < current && !isBackValidate) {
56 | go(step);
57 | return true;
58 | }
59 |
60 | // goto the target step after passing validate
61 | return formInstance.validateFields().then(values => {
62 | go(step);
63 | return values;
64 | });
65 | };
66 |
67 | const handleStepChange = currentStep => gotoStep(currentStep);
68 |
69 | return {
70 | current,
71 | gotoStep,
72 | stepsProps: {
73 | current,
74 | onChange: handleStepChange,
75 | },
76 | formProps,
77 | formLoading,
78 | defaultFormValuesLoading,
79 | formValues,
80 | initialValues,
81 | formResult,
82 | form: formInstance,
83 | submit: formSubmit,
84 | };
85 | };
86 |
--------------------------------------------------------------------------------
/src/useStepsForm/index.zh-CN.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav:
3 | title: 流程组件
4 | path: /zh-CN/process-components
5 | group:
6 | title: 流程组件
7 | path: /zh-CN/process-components
8 | title: useStepsForm
9 | order: 4
10 | ---
11 |
12 | ## 说明
13 |
14 | 当你需要使用分步表单时,可使用 `useStepsForm`
15 |
16 | ## 示例
17 |
18 | ### 基础
19 |
20 | ```jsx
21 | import React from 'react';
22 | import { useStepsForm } from 'sunflower-antd';
23 | import { Steps, Input, Button, Form, Result } from 'antd';
24 |
25 | const { Step } = Steps;
26 |
27 | const layout = {
28 | labelCol: { span: 8 },
29 | wrapperCol: { span: 16 },
30 | };
31 | const tailLayout = {
32 | wrapperCol: { offset: 8, span: 16 },
33 | };
34 |
35 | export default props => {
36 | const {
37 | form,
38 | current,
39 | gotoStep,
40 | stepsProps,
41 | formProps,
42 | submit,
43 | formLoading,
44 | } = useStepsForm({
45 | async submit(values) {
46 | const { username, email, address } = values;
47 | console.log(username, email, address);
48 | await new Promise(r => setTimeout(r, 1000));
49 | return 'ok';
50 | },
51 | total: 3,
52 | });
53 |
54 | const formList = [
55 | <>
56 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | gotoStep(current + 1)}>下一步
73 |
74 | >,
75 |
76 | <>
77 |
87 |
88 |
89 |
90 | {
95 | submit().then(result => {
96 | if (result === 'ok') {
97 | gotoStep(current + 1);
98 | }
99 | });
100 | }}
101 | >
102 | 提交
103 |
104 | gotoStep(current - 1)}>上一步
105 |
106 | >,
107 | ];
108 |
109 | return (
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
121 |
122 | {current === 2 && (
123 |
128 | {
131 | form.resetFields();
132 | gotoStep(0);
133 | }}
134 | >
135 | 再次购买
136 |
137 | 查看详情
138 | >
139 | }
140 | />
141 | )}
142 |
143 |
144 | );
145 | };
146 | ```
147 |
148 | ## API
149 |
150 | ```js
151 | const Result = useStepsForm(Config);
152 | ```
153 |
154 | ### Config
155 |
156 |
157 |
158 |
159 | 名称
160 | 说明
161 | 类型
162 | 默认值
163 |
164 |
165 |
166 |
167 | submit
168 | 请求方法, 其参数为 form fields 的值
169 | (formValues) => Promise<formResult> | formResult
170 |
171 |
172 |
173 | form
174 | 在 antd4 中为 `useForm` 返回的 `form` 实例。在 antd3 中 `Form.create()` 会自动注入 `props.form`
175 |
176 |
177 |
178 |
179 | defaultFormValues
180 | 默认的表单回填值,可为一个对象,也可为一个异步方法
181 | object
182 |
183 |
184 |
185 | defaultCurrent
186 | 默认开始的步骤,从 0 开始
187 | number
188 | 0
189 |
190 |
191 | total
192 | 分步表单的总步骤
193 | number
194 |
195 |
196 |
197 | isBackValidate
198 | 返回上一步时是否校验表单
199 | boolean
200 | true
201 |
202 |
203 |
204 |
205 | ### Result
206 |
207 |
208 |
209 |
210 | 名称
211 | 说明
212 | 类型
213 |
214 |
215 |
216 |
217 | current
218 | 当前步骤,从 0 开始
219 | number
220 |
221 |
222 | gotoStep
223 | 切换到目标步骤,使用该方法时,将首先校验当前步骤的表单
224 | void'}}>
225 |
226 |
227 | stepsProps
228 | antd Steps 组件的 props,作为 Steps 组件的 props 即可
229 | object
230 |
231 |
232 | formProps
233 | antd Form 组件的 props,作为 Form 组件的 props 即可
234 | object
235 |
236 |
237 | form
238 | Form 实例
239 |
240 |
241 |
242 | formLoading
243 | 正在提交表单
244 | boolean
245 |
246 |
247 | formValues
248 | 对应 form fileds 的值
249 | object
250 |
251 |
252 | initialValues
253 | 表单默认值. 对应 defaultFormValues
254 | object
255 |
256 |
257 | formResult
258 | submit 方法返回值
259 |
260 |
261 |
262 | defaultFormValuesLoading
263 | 当使用 defaultFormValues 在请求表单回填值的 loading
264 | boolean
265 |
266 |
267 | submit
268 | submit 方法. 可主动触发, 提交表单, 其参数为 form fields 的值
269 |
270 |
271 |
272 |
273 |
--------------------------------------------------------------------------------
/tests/useCascadeSearch.test.js:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import { useCascadeSearch } from '../src/useCascadeSearch';
3 |
4 | test('useCascadeSearch', async () => {
5 | const config = {
6 | list: [
7 | () => {
8 | return [
9 | {
10 | label: '1',
11 | value: '1',
12 | },
13 | ];
14 | },
15 | async (lastValue, value) => {
16 | await new Promise(r => setTimeout(r, 200));
17 | return value === '1'
18 | ? [
19 | {
20 | label: '2',
21 | value: '2',
22 | },
23 | ]
24 | : [];
25 | },
26 | ],
27 | autoFirstSearch: false,
28 | };
29 | const { result, waitForNextUpdate } = renderHook(() =>
30 | useCascadeSearch(config),
31 | );
32 | result.current.search(0);
33 | expect(result.current.loadingList).toEqual([true, false]);
34 | await waitForNextUpdate();
35 | expect(result.current.loadingList).toEqual([false, false]);
36 | expect(result.current.responseDataList).toEqual([
37 | [{ label: '1', value: '1' }],
38 | ]);
39 | result.current.search(1, '1');
40 | expect(result.current.loadingList).toEqual([false, true]);
41 | await waitForNextUpdate();
42 | expect(result.current.loadingList).toEqual([false, false]);
43 | expect(result.current.responseDataList).toEqual([
44 | [{ label: '1', value: '1' }],
45 | [{ label: '2', value: '2' }],
46 | ]);
47 | });
48 |
--------------------------------------------------------------------------------
/tests/useCascadeSelect.test.js:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import { useCascadeSelect } from '../src/useCascadeSelect';
3 |
4 | test('useCascadeSelect', async () => {
5 | const setFieldsValue = jest.fn();
6 | const config = {
7 | list: [
8 | {
9 | name: '1',
10 | options: async () => {
11 | await new Promise(r => setTimeout(r, 200));
12 | return [
13 | {
14 | label: '1',
15 | value: '1',
16 | },
17 | ];
18 | },
19 | },
20 | {
21 | name: '2',
22 | options: async value => {
23 | await new Promise(r => setTimeout(r, 200));
24 | return value === '1'
25 | ? [
26 | {
27 | label: '2',
28 | value: '2',
29 | },
30 | ]
31 | : [];
32 | },
33 | },
34 | ],
35 | autoFirstSearch: true,
36 | form: {
37 | setFieldsValue,
38 | },
39 | };
40 | const { result, waitForNextUpdate } = renderHook(() =>
41 | useCascadeSelect(config),
42 | );
43 | expect(result.current.selects.length).toBe(2);
44 | expect(result.current.selects[0].options).toEqual([]);
45 | expect(result.current.selects[1].options).toEqual([]);
46 | await waitForNextUpdate();
47 | expect(result.current.selects[0].options).toEqual([
48 | {
49 | label: '1',
50 | value: '1',
51 | },
52 | ]);
53 | result.current.selects[0].props.onChange('1');
54 | expect(setFieldsValue).toBeCalled();
55 | await waitForNextUpdate();
56 | expect(result.current.selects[1].options).toEqual([
57 | {
58 | label: '2',
59 | value: '2',
60 | },
61 | ]);
62 | });
63 |
--------------------------------------------------------------------------------
/tests/useForm.test.js:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import { useForm } from '../src/useForm';
3 |
4 | test('useForm', async () => {
5 | const submit = jest.fn();
6 | const mockForm = {};
7 | const config = {
8 | form: mockForm,
9 | submit: values => {
10 | submit({
11 | ...values,
12 | });
13 | return {
14 | list: [
15 | {
16 | name: 'lily',
17 | },
18 | {
19 | name: 'jack',
20 | },
21 | ],
22 | total: 10,
23 | };
24 | },
25 | defaultFormValues: {
26 | username: 'lily',
27 | },
28 | };
29 | const { result } = renderHook(() => useForm(config));
30 | const { formProps, form } = result.current;
31 | expect(typeof formProps.onFinish).toBe('function');
32 | expect(form).toBe(mockForm);
33 | });
34 |
--------------------------------------------------------------------------------
/tests/useFormTable.test.js:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import { useFormTable } from '../src/useFormTable';
3 |
4 | test('useFormTable', async () => {
5 | const search = jest.fn();
6 | const mockForm = {};
7 | const config = {
8 | form: mockForm,
9 | search: values => {
10 | search({
11 | ...values,
12 | });
13 | return {
14 | list: [
15 | {
16 | name: 'lily',
17 | },
18 | {
19 | name: 'jack',
20 | },
21 | ],
22 | total: 10,
23 | };
24 | },
25 | defaultFormValues: {
26 | username: 'lily',
27 | },
28 | autoFirstSearch: true,
29 | };
30 | const { result } = renderHook(() => useFormTable(config));
31 | const { formProps, tableProps, form } = result.current;
32 | expect(typeof formProps.onFinish).toBe('function');
33 | const props = { ...tableProps };
34 | delete props.onChange;
35 | expect(props).toEqual({
36 | pagination: {
37 | pageSize: 10,
38 | current: undefined,
39 | defaultPageSize: 10,
40 | defaultCurrent: 1,
41 | total: undefined,
42 | },
43 | loading: false,
44 | dataSource: undefined,
45 | });
46 | expect(form).toBe(mockForm);
47 | });
48 |
--------------------------------------------------------------------------------
/tests/useModal.test.js:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import { useModal } from '../src/useModal';
3 |
4 | test('useModal', async () => {
5 | const config = {
6 | defaultVisible: false,
7 | };
8 | const { result } = renderHook(() => useModal(config));
9 | const { modalProps, show, close, visible } = result.current;
10 | expect(typeof modalProps.onCancel).toBe('function');
11 | expect(typeof show).toBe('function');
12 | expect(typeof close).toBe('function');
13 | expect(typeof visible).toBe('boolean');
14 | const props = { ...modalProps };
15 | delete props.onCancel;
16 | expect(props).toEqual({
17 | visible: false,
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/tests/useModalForm.test.js:
--------------------------------------------------------------------------------
1 | import { renderHook } from '@testing-library/react-hooks';
2 | import { useModalForm } from '../src/useModalForm';
3 |
4 | test('useModalForm', async () => {
5 | const submit = jest.fn();
6 | const mockForm = {};
7 | const config = {
8 | form: mockForm,
9 | submit: values => {
10 | submit({
11 | ...values,
12 | });
13 | return {
14 | list: [
15 | {
16 | name: 'lily',
17 | },
18 | {
19 | name: 'jack',
20 | },
21 | ],
22 | total: 10,
23 | };
24 | },
25 | defaultFormValues: {
26 | username: 'lily',
27 | },
28 | defaultVisible: false,
29 | autoSubmitClose: true,
30 | };
31 | const { result } = renderHook(() => useModalForm(config));
32 | const { formProps, modalProps, form } = result.current;
33 | expect(typeof formProps.onFinish).toBe('function');
34 | expect(typeof modalProps.onOk).toBe('function');
35 | expect(form).toBe(mockForm);
36 | });
37 |
--------------------------------------------------------------------------------
/tests/useSearchResult.test.js:
--------------------------------------------------------------------------------
1 | import { renderHook, act } from '@testing-library/react-hooks';
2 | import { useSearchResult } from '../src/useSearchResult';
3 |
4 | test('useSearchResult', async () => {
5 | const config = {
6 | search: () =>
7 | new Promise(r =>
8 | setTimeout(
9 | () =>
10 | r({
11 | list: [
12 | {
13 | name: 'lily',
14 | },
15 | {
16 | name: 'jack',
17 | },
18 | ],
19 | total: 10,
20 | }),
21 | 200,
22 | ),
23 | ),
24 | defaultRequestData: new Promise(r =>
25 | setTimeout(() => {
26 | r({
27 | a: 1,
28 | b: 2,
29 | });
30 | }, 20),
31 | ),
32 | autoFirstSearch: false,
33 | };
34 | const { result, waitForNextUpdate } = renderHook(() =>
35 | useSearchResult(config),
36 | );
37 |
38 | expect(result.current.requestData).toEqual(undefined);
39 | await waitForNextUpdate();
40 | expect(result.current.requestData).toEqual({
41 | a: 1,
42 | b: 2,
43 | });
44 |
45 | act(() => {
46 | result.current.search({
47 | ...result.current.requestData,
48 | currentPage: 2,
49 | pageSize: 2,
50 | });
51 | });
52 |
53 | expect(result.current.requestData).toEqual({
54 | a: 1,
55 | b: 2,
56 | currentPage: 2,
57 | pageSize: 2,
58 | });
59 |
60 | await waitForNextUpdate();
61 |
62 | expect(result.current.responseData).toEqual({
63 | list: [
64 | {
65 | name: 'lily',
66 | },
67 | {
68 | name: 'jack',
69 | },
70 | ],
71 | total: 10,
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/tests/useStepsForm.test.js:
--------------------------------------------------------------------------------
1 | import { renderHook, act } from '@testing-library/react-hooks';
2 | import { useStepsForm } from '../src';
3 |
4 | function excuteHook(defaultCurrent = 0) {
5 | const submit = jest.fn();
6 | const mockForm = {
7 | validateFields: () =>
8 | new Promise(resolve => {
9 | resolve();
10 | }),
11 | };
12 | const config = {
13 | form: mockForm,
14 | submit: values => {
15 | submit({
16 | ...values,
17 | });
18 | },
19 | defaultFormValues: {
20 | username: 'test',
21 | },
22 | defaultCurrent,
23 | total: 2,
24 | };
25 | const { result } = renderHook(() => useStepsForm(config));
26 |
27 | return result;
28 | }
29 |
30 | describe('useStepsForm', () => {
31 | test('base use', async () => {
32 | const result = excuteHook();
33 | const {
34 | submit: formSubmit,
35 | current,
36 | gotoStep,
37 | stepsProps,
38 | formProps,
39 | } = result.current;
40 | expect(typeof formSubmit).toBe('function');
41 | expect(typeof stepsProps).toBe('object');
42 | expect(typeof formProps).toBe('object');
43 | expect(typeof stepsProps.onChange).toBe('function');
44 | expect(typeof gotoStep).toBe('function');
45 | expect(typeof current).toBe('number');
46 | expect(current).toBe(0);
47 |
48 | await act(() => gotoStep(1));
49 | expect(result.current.current).toBe(1);
50 | });
51 |
52 | test('goto prev step', async () => {
53 | const result = excuteHook(1);
54 | const { current, gotoStep } = result.current;
55 | expect(current).toBe(1);
56 |
57 | await act(() => gotoStep(0));
58 | expect(result.current.current).toBe(0);
59 | });
60 |
61 | test('goto a step more than total', async () => {
62 | const result = excuteHook();
63 | const { gotoStep } = result.current;
64 |
65 | await act(() => gotoStep(3));
66 | expect(result.current.current).toBe(1);
67 | });
68 |
69 | test('goto current step', () => {
70 | const result = excuteHook(1);
71 | const { gotoStep } = result.current;
72 |
73 | act(() => gotoStep(1));
74 | expect(result.current.current).toBe(1);
75 | });
76 |
77 | test('goto a negative step', async () => {
78 | const result = excuteHook();
79 | const { gotoStep } = result.current;
80 |
81 | await act(() => gotoStep(-2));
82 | expect(result.current.current).toBe(0);
83 | });
84 |
85 | test('stepsProps onChange', async () => {
86 | const result = excuteHook();
87 | const { stepsProps } = result.current;
88 |
89 | await act(() => stepsProps.onChange(1));
90 | expect(result.current.current).toBe(1);
91 | });
92 | });
93 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "declaration": true,
6 | "moduleResolution": "node",
7 | "importHelpers": true,
8 | "jsx": "react",
9 | "esModuleInterop": true,
10 | "baseUrl": "./",
11 | "paths": {
12 | "@/*": ["src/*"],
13 | "@@/*": ["src/.umi/*"]
14 | },
15 | "allowSyntheticDefaultImports": true
16 | },
17 | "exclude": [
18 | "node_modules",
19 | "lib",
20 | "es",
21 | "dist",
22 | "typings",
23 | "**/__test__",
24 | "test"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.css';
2 | declare module '*.less';
3 |
--------------------------------------------------------------------------------