├── .editorconfig
├── .env
├── .eslintignore
├── .eslintrc
├── .fatherrc.js
├── .gitignore
├── .prettierrc.js
├── .travis.yml
├── README.md
├── build.sh
├── components
├── basic-form-items
│ ├── BasicFormItems.tsx
│ ├── __tests__
│ │ ├── custom.test.tsx
│ │ ├── input.test.tsx
│ │ └── select.test.tsx
│ ├── demo
│ │ ├── custom.md
│ │ ├── input.md
│ │ └── select.md
│ ├── index.md
│ ├── index.tsx
│ └── style
│ │ ├── index.less
│ │ └── index.tsx
├── basic-table
│ ├── BasicTable.tsx
│ ├── __tests__
│ │ └── basic.test.tsx
│ ├── config.tsx
│ ├── demo
│ │ ├── basic.md
│ │ ├── basicFetch.md
│ │ ├── basicHalfHeader.md
│ │ ├── query-compact.md
│ │ ├── query.md
│ │ └── select.md
│ ├── index.md
│ ├── index.tsx
│ └── style
│ │ ├── index.less
│ │ └── index.tsx
├── card
│ ├── Card.tsx
│ ├── __tests__
│ │ └── basic.test.tsx
│ ├── demo
│ │ ├── basic.md
│ │ ├── extra.md
│ │ ├── footer.md
│ │ ├── icon.md
│ │ ├── percent.md
│ │ ├── sider.md
│ │ └── theme.md
│ ├── index.md
│ ├── index.tsx
│ └── style
│ │ ├── index.less
│ │ └── index.tsx
├── code
│ ├── code.tsx
│ ├── demo
│ │ ├── basic.md
│ │ └── toggle.md
│ ├── index.md
│ ├── index.tsx
│ └── style
│ │ ├── index.less
│ │ └── index.tsx
├── color-select
│ ├── ColorSelect.tsx
│ ├── demo
│ │ ├── basic.md
│ │ ├── disabled.md
│ │ ├── form.md
│ │ └── value.md
│ ├── index.md
│ ├── index.tsx
│ └── style
│ │ ├── index.less
│ │ └── index.ts
├── create-global-state
│ ├── index.md
│ ├── index.ts
│ ├── useEffectOnce.ts
│ └── useIsomorphicLayoutEffect.ts
├── create-reducer-context
│ ├── index.md
│ └── index.ts
├── create-state-context
│ ├── index.md
│ └── index.ts
├── data-table
│ ├── DataTable.tsx
│ ├── __tests__
│ │ └── index.test.tsx
│ ├── config.tsx
│ ├── demo
│ │ ├── basic.md
│ │ ├── query.md
│ │ ├── queryCompact.md
│ │ └── select.md
│ ├── index.md
│ ├── index.tsx
│ └── style
│ │ ├── index.less
│ │ └── index.tsx
├── descriptions
│ ├── Descriptions.tsx
│ ├── __tests__
│ │ └── index.test.tsx
│ ├── demo
│ │ ├── basic.md
│ │ ├── changeMode.md
│ │ ├── list.md
│ │ └── mainTitle.md
│ ├── index.md
│ ├── index.tsx
│ └── style
│ │ ├── index.less
│ │ └── index.tsx
├── empty-line
│ ├── __tests__
│ │ └── index.test.tsx
│ ├── demo
│ │ ├── basic.md
│ │ └── withHeight.md
│ ├── empty-line.tsx
│ ├── index.md
│ ├── index.tsx
│ └── style
│ │ ├── index.less
│ │ └── index.tsx
├── index.tsx
├── locale-provider
│ ├── context.tsx
│ ├── index.tsx
│ ├── provider.tsx
│ ├── useIntl.tsx
│ └── withIntl.tsx
├── locale
│ ├── en_US.tsx
│ ├── index.tsx
│ └── zh_CN.tsx
├── query-form
│ ├── QueryForm.tsx
│ ├── demo
│ │ ├── basic.md
│ │ ├── changeMode.md
│ │ └── customCols.md
│ ├── index.md
│ ├── index.tsx
│ ├── style
│ │ ├── index.less
│ │ └── index.tsx
│ ├── use-media-antd-query.ts
│ └── use-media.ts
├── style
│ ├── antd-extension.less
│ ├── index.less
│ ├── index.tsx
│ ├── mixins.less
│ ├── themes.less
│ └── variable.less
├── switch
│ ├── __tests__
│ │ └── index.test.tsx
│ ├── demo
│ │ ├── disabled.md
│ │ ├── small.md
│ │ └── text.md
│ ├── index.md
│ ├── index.tsx
│ ├── style
│ │ ├── index.less
│ │ └── index.tsx
│ └── switch.tsx
├── use-async-fn
│ ├── index.md
│ └── index.ts
├── use-async-retry
│ ├── index.md
│ └── index.ts
├── use-async
│ ├── index.md
│ └── index.ts
├── use-debounce
│ ├── demo
│ │ └── useDebounce.md
│ ├── index.md
│ └── index.tsx
├── use-deep-compare-effect
│ ├── index.md
│ ├── index.ts
│ └── useCustomCompareEffect.ts
├── use-dynamic-list
│ ├── demo
│ │ └── basic.md
│ ├── index.md
│ └── index.tsx
├── use-interval
│ ├── index.md
│ └── index.ts
├── use-mounted-state
│ ├── index.md
│ └── index.ts
├── use-timeout
│ ├── index.md
│ ├── index.ts
│ ├── useTimeoutFn.ts
│ └── useUpdate.ts
├── utils
│ └── index.tsx
└── virtual-select
│ ├── VirtualSelect.tsx
│ ├── demo
│ ├── basic.md
│ ├── bigdata.md
│ ├── multiple.md
│ ├── search.md
│ └── theme.md
│ ├── index.md
│ ├── index.tsx
│ └── style
│ ├── index.less
│ ├── index.tsx
│ ├── multiple.less
│ ├── rtl.less
│ └── single.less
├── docs
├── changelog.md
├── develop.md
├── i18n.md
├── introduce.md
└── theme.md
├── jest.config.js
├── jest.setup.js
├── jest_html_reporters.html
├── package-lock.json
├── package.json
├── scripts
├── changeLess2Css.js
├── copyStatic.js
├── deploy.js
├── moveDeclare.js
├── optimizeDist.js
├── publish.js
├── renameEnd.js
├── renameStart.js
├── sync.js
└── theme
│ ├── antd-theme-generator.js
│ └── antd-theme-webpack-plugin.js
├── site
├── bisheng.config.js
└── theme
│ ├── en-US.js
│ ├── index.js
│ ├── static
│ ├── colors.less
│ ├── common.less
│ ├── customer
│ │ ├── antd-variables.less
│ │ └── colors.less
│ ├── demo.less
│ ├── docsearch.less
│ ├── footer.less
│ ├── header.less
│ ├── highlight.less
│ ├── home.less
│ ├── icons.less
│ ├── index.less
│ ├── markdown.less
│ ├── mock-browser.less
│ ├── motion.less
│ ├── new-version-info-modal.less
│ ├── not-found.less
│ ├── nprogress.less
│ ├── page-nav.less
│ ├── preview-img.less
│ ├── resource.less
│ ├── responsive.less
│ ├── santa.less
│ ├── style.js
│ ├── template.html
│ ├── theme.less
│ └── toc.less
│ ├── template
│ ├── Content
│ │ ├── Article.jsx
│ │ ├── ComponentDoc.jsx
│ │ ├── Demo.jsx
│ │ ├── MainContent.jsx
│ │ ├── PrevAndNext.jsx
│ │ └── index.jsx
│ ├── Home
│ │ ├── index.jsx
│ │ ├── landing.png
│ │ ├── personal.jpg
│ │ └── qun.jpg
│ ├── Layout
│ │ ├── Footer.jsx
│ │ ├── Header.jsx
│ │ ├── github.png
│ │ └── index.jsx
│ ├── NotFound.jsx
│ └── utils.js
│ └── zh-CN.js
├── static
├── landing.png
└── logo.png
├── tsconfig.father.json
├── tsconfig.json
└── tsconfig.test.json
/.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 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | NODE_PATH=./src
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | config/*
3 | webpack.*
4 | examples/*
5 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "react-app",
3 | "plugins": ["prettier"],
4 | "rules": {
5 | "prettier/prettier": "error",
6 | "no-unused-vars": "off",
7 | "@typescript-eslint/no-unused-vars": ["error", {
8 | "vars": "all",
9 | "args": "after-used",
10 | "ignoreRestSiblings": false
11 | }]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.fatherrc.js:
--------------------------------------------------------------------------------
1 | import react from 'react';
2 | import reactDom from 'react-dom';
3 | import reactIs from 'react-is';
4 | import propTypes from 'prop-types';
5 |
6 | export default
7 | {
8 | // cssModules: true, // 默认是 .module.css 走 css modules,.css 不走 css modules。配置 cssModules 为 true 后,全部 css 文件都走 css modules。(less 文件同理)
9 | extractCSS: true,
10 | esm: 'babel',
11 | cjs: 'babel',
12 | umd: {
13 | name: 'dantd',
14 | sourcemap: true,
15 | globals: {
16 | react: 'React',
17 | antd: 'antd'
18 | },
19 | },
20 | extraBabelPlugins: [
21 | ['import', { libraryName: 'antd', libraryDirectory: 'es', style: true }],
22 | ],
23 | entry: 'src/index.tsx',
24 | namedExports: {
25 | react: Object.keys(react),
26 | 'react-dom': Object.keys(reactDom),
27 | 'react-is': Object.keys(reactIs),
28 | 'prop-types': Object.keys(propTypes),
29 | },
30 | doc: {
31 | title: 'Dantd基础UI组件库',
32 | base: '/dantd/',
33 | menu: [
34 | '首页',
35 | '更新日志',
36 | '组件'
37 | ]
38 | },
39 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 | .DS_Store
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 | /output
14 | /dist
15 | /lib
16 | /es
17 | /_site
18 |
19 | # misc
20 | .DS_Store
21 | .env.local
22 | .env.development.local
23 | .env.test.local
24 | .env.production.local
25 |
26 | .docz
27 |
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 |
32 | test-report.html
33 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | // prettier.config.js or .prettierrc.js
2 | module.exports = {
3 | singleQuote: true,
4 | trailingComma: 'all',
5 | printWidth: 100,
6 | proseWrap: 'never',
7 | endOfLine: 'auto',
8 | overrides: [
9 | {
10 | files: '.prettierrc',
11 | options: {
12 | parser: 'json',
13 | },
14 | },
15 | ],
16 | };
17 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 9
4 | - 8
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # dantd
2 |
3 | [预览地址](https://thedicode.github.io/dantd)
4 |
5 | 一个基于 [Antd-v3](https://github.com/ant-design/ant-design/) 所封装的业务组件库
6 |
7 | # Features
8 |
9 | - 支持 TypeScript
10 | - 支持 单元测试 Jest + @testing-library/react
11 | - 支持 less
12 | - 支持 eslint & prettier
13 | - 支持 react-app-rewired
14 | - 基于 [umijs/father](https://github.com/umijs/father) 完成打包,可使用 cjs、esm 和 umd 三种格式的引用
15 |
16 | # Installation
17 |
18 | ```
19 | $ npm install antd-advanced
20 | ```
21 |
22 | # Usage
23 |
24 | ```
25 | # 开发文档
26 | $ npm start
27 |
28 | # 组件测试
29 | $ npm test
30 |
31 | # 文档构建
32 | $ npm run site
33 |
34 | # 文档发布
35 | $ npm run deploy
36 |
37 | # 打包
38 | $ npm run build
39 | ```
40 |
41 | # 目录结构
42 |
43 | ```
44 | ── README.md
45 | ├── build.sh
46 | ├── config-overrides.js
47 | ├── docs // 文档
48 | | ├── changeLog.md
49 | | └── introduce.md
50 | ├── elevate
51 | | └── pipeline.doc.yml
52 | ├── es
53 | ├── jest.config.js
54 | ├── lib
55 | ├── package-lock.json
56 | ├── package.json
57 | ├── public // 公共文件
58 | ├── scripts // 构建用到的脚本
59 | ├── server.conf // 用于部署的 Nginx 配置
60 | ├── src
61 | | ├── App.tsx
62 | | ├── config.tsx // 路由等配置
63 | | ├── index.tsx
64 | | ├── pages // 开发组件的地方
65 | | ├── demos // 写Demo的地方
66 | | |—— components // 写组件的地方
67 | | | ├── empty-line
68 | | | | ├── EmptyLine.tsx // 组件
69 | | | | ├── __tests__ // 测试目录
70 | | | | | └── index.test.tsx
71 | | | | ├── index.tsx // 组件入口
72 | | | | ├── style // 组件样式
73 | | | | | ├── index.less
74 | | | | | └── index.tsx
75 | | | | └── demo // 文档
76 | | | | └── basic.md
77 | | | └── index.tsx
78 | ├── test-report.html 测试报告
79 | └── tsconfig.json
80 | ```
81 |
82 | # 组件
83 |
84 | ### 开发
85 |
86 | 在 `src` 目录下,新增一个组件的目录,类似上面的 `empty-line` 组件。目录名需要保持**小写**。如果是自定义组件,需要取一个 `antd` 中所不包含的组件名称。添加完文件之后,在 `entry/config.tsx` 中增加 `demo` 的配置。此时应该可以看到组件,并继续开发了。
87 |
88 | 更多请参考:[手摸手,打造属于自己的 React 组件库 —— 基础篇](https://juejin.im/post/6844904054347268103)
89 |
90 | ### 测试
91 |
92 | 测试文件需要保持 `.test.tsx` 的后缀。相关技术栈以及文档:
93 |
94 | - [Jest](https://jestjs.io/):JavaScript 测试框架。
95 | - [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro):将 React 组件转化成 Dom 节点来测试,而不是渲染的 React 组件的实例,可以当做是 [Enzyme](http://airbnb.io/enzyme/) 的替代。编写测试脚本,并保证希望测试到的地方已经覆盖。
96 |
97 | 更多请参考:[手摸手,打造属于自己的 React 组件库 —— 测试篇](https://juejin.im/post/6844904054351462408)
98 |
99 | ### 打包&发布
100 |
101 | ```
102 | $ npm run build
103 | $ npm login
104 | $ npm version patch
105 | $ git push
106 | $ npm publish
107 | ```
108 |
109 | > npm version
110 | - major:主版本号
111 | - minor:次版本号
112 | - patch:补丁号
113 | - premajor:预备主版本
114 | - prepatch:预备次版本
115 | - prerelease:预发布版本
116 |
117 | # 文档
118 |
119 | ### 开发文档
120 |
121 | 在组件的 `demo` 文件夹中添加 `.md` 文件开发文档,并运行命令查看效果
122 |
123 | ```
124 | npm start
125 | ```
126 |
127 | ### 构建文档
128 |
129 | ```
130 | npm run site
131 | ```
132 |
133 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | export NODE_PATH=/home/xiaoju/node-v8.9.1-linux-x64:/home/xiaoju/node-v8.9.1-linux-x64/lib/node_modules
3 | export PATH=/home/xiaoju/node-v8.9.1-linux-x64/bin:$PATH
4 |
5 | echo $PATH
6 |
7 | rm -rf output
8 | mkdir -p ./output
9 |
10 | mv _site/* output/
11 | cp ./server.conf ./output/
12 |
13 | ret=$?
14 | if [ $ret -ne 0 ];then
15 | echo "===== npm build failure ====="
16 | exit $ret
17 | else
18 | echo -n "===== npm build successfully! ====="
19 | fi
20 |
21 |
--------------------------------------------------------------------------------
/components/basic-form-items/__tests__/input.test.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Form, Button } from 'antd';
3 | import { render, fireEvent, cleanup } from '@testing-library/react';
4 | import BasicFormItems from '../BasicFormItems';
5 | import { processBasicFormItemsData } from '../../utils';
6 |
7 | describe('Basic Forms Testing Input', () => {
8 | afterEach(cleanup);
9 | it('输入,提交校验', async () => {
10 | const Demo = (props) => {
11 | const { form } = props;
12 | const [results, setResults] = useState('');
13 | const handleSubmit = () => {
14 | const {
15 | form: { validateFields },
16 | } = props;
17 | validateFields(async (errors, values) => {
18 | if (errors) {
19 | return;
20 | }
21 |
22 | setResults(JSON.stringify(processBasicFormItemsData(values)));
23 | });
24 | };
25 | return (
26 |
31 |
42 |
45 | {results}
46 |
47 | );
48 | };
49 |
50 | const BasicDemoForm = Form.create({ name: 'basic-form' })(Demo);
51 | const { findByText, findAllByTestId, getByTestId } = render();
52 | const addBtn = getByTestId('btn-add');
53 | // 增加3次
54 | fireEvent.click(addBtn);
55 | fireEvent.click(addBtn);
56 | fireEvent.click(addBtn);
57 | const inputElements = await findAllByTestId('field-input');
58 | // 输入
59 | inputElements.forEach((inputEle, inputIdx) => {
60 | fireEvent.change(inputEle, { target: { value: `test${inputIdx}` } });
61 | });
62 | const submitBtn = getByTestId('btn-submit');
63 | fireEvent.click(submitBtn);
64 | await findByText('{"inputParams":[["test0"],["test1"],["test2"]]}');
65 | const removeBtns = await findAllByTestId('btn-remove');
66 | fireEvent.click(removeBtns[0]);
67 | fireEvent.click(submitBtn);
68 | await findByText('{"inputParams":[["test1"],["test2"]]}');
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/components/basic-form-items/demo/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 3
3 | title: Custom
4 | ---
5 |
6 | 当 `type=custom` 时,可以通过 `component` 属性传入组件
7 |
8 | ```jsx
9 | import React, {useState} from 'react';
10 | import { BasicFormItems, EmptyLine } from 'antd-advanced';
11 | import { processBasicFormItemsData } from 'antd-advanced/utils';
12 | import { Form, Button, DatePicker, InputNumber } from 'antd';
13 | import moment from 'moment';
14 |
15 | const Demo = props => {
16 | const { form } = props;
17 | const [results, setResults] = useState();
18 | const customDataSource = [['张三', 23, moment('1996-12-23'), '程序员']];
19 | const handleSubmit = () => {
20 | const {
21 | form: { validateFields },
22 | } = props;
23 |
24 | validateFields(async (errors, values) => {
25 | if (errors) {
26 | return;
27 | }
28 |
29 | setResults(JSON.stringify(processBasicFormItemsData(values)));
30 | });
31 | };
32 | return (
33 |
38 |
54 | ),
55 | },
56 | {
57 | type: 'custom',
58 | title: '生日',
59 | required: true,
60 | component: ,
61 | },
62 | {
63 | type: 'select',
64 | title: '职业',
65 | required: true,
66 | selectOptions: [
67 | {
68 | title: '程序员',
69 | value: '程序员',
70 | },
71 | {
72 | title: '产品经理',
73 | value: '产品经理',
74 | },
75 | {
76 | title: '设计师',
77 | value: '设计师',
78 | },
79 | ],
80 | },
81 | ]}
82 | />
83 |
84 |
90 |
91 | {results}
92 |
93 | );
94 | }
95 |
96 | const BasicDemoForm = Form.create({ name: 'basic-form' })(Demo);
97 |
98 | ReactDOM.render(, mountNode);
99 | ```
100 |
--------------------------------------------------------------------------------
/components/basic-form-items/demo/input.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 0
3 | title: Input
4 | ---
5 |
6 | 这是一个基础的动态表单,可以使用 `dataSource` 来设置初始值。
7 |
8 | ```jsx
9 | import React, {useState} from 'react';
10 | import { BasicFormItems, EmptyLine } from 'antd-advanced';
11 | import { processBasicFormItemsData } from 'antd-advanced/utils';
12 | import { Form, Button } from 'antd';
13 |
14 | const Demo = props => {
15 | const { form } = props;
16 | const [results, setResults] = useState();
17 | const handleSubmit = () => {
18 | const {
19 | form: { validateFields },
20 | } = props;
21 |
22 | validateFields(async (errors, values) => {
23 | if (errors) {
24 | return;
25 | }
26 |
27 | setResults(JSON.stringify(processBasicFormItemsData(values)));
28 | });
29 | };
30 | return (
31 |
36 |
47 |
48 |
54 |
55 | {results}
56 |
57 | );
58 | }
59 |
60 | const BasicDemoForm = Form.create({ name: 'basic-form' })(Demo);
61 |
62 | ReactDOM.render(, mountNode);
63 | ```
64 |
--------------------------------------------------------------------------------
/components/basic-form-items/demo/select.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 2
3 | title: Select
4 | ---
5 |
6 | 当 `type=select` 时,可以通过 `selectOptions` 属性传入下拉选项
7 |
8 |
9 | ```jsx
10 | import React, {useState} from 'react';
11 | import { BasicFormItems, EmptyLine } from 'antd-advanced';
12 | import { processBasicFormItemsData } from 'antd-advanced/utils';
13 | import { Form, Button } from 'antd';
14 |
15 |
16 |
17 | const Demo = props => {
18 | const { form } = props;
19 | const [results, setResults] = useState();
20 | const dataSource = [
21 | ['显示器', 6, 5000],
22 | ['主机', 2, 15000],
23 | ];
24 | const handleSubmit = () => {
25 | const {
26 | form: { validateFields },
27 | } = props;
28 |
29 | validateFields(async (errors, values) => {
30 | if (errors) {
31 | return;
32 | }
33 |
34 | setResults(JSON.stringify(processBasicFormItemsData(values)));
35 | });
36 | };
37 | return (
38 |
43 |
82 |
83 |
89 |
90 | {results}
91 |
92 | );
93 | }
94 |
95 | const BasicDemoForm = Form.create({ name: 'basic-form' })(Demo);
96 |
97 | ReactDOM.render(, mountNode);
98 | ```
99 |
--------------------------------------------------------------------------------
/components/basic-form-items/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 组件
3 | type: 动态表单
4 | title: BasicFormItems
5 | subtitle: 基础
6 | ---
7 |
8 | ## 何时使用
9 |
10 | 快捷的动态增加、减少表单项。
11 |
12 |
13 |
14 | ## API
15 |
16 | ### BasicFormItems
17 |
18 | | 参数 | 说明 | 类型 | 默认值 |
19 | | :--------- | :------------- | :------------------ | :----- |
20 | | form | 经 Form.create() 包装过的组件会自带 this.props.form 属性 | object | - |
21 | | dataSource | 初始参数 | [[string, string]...] | [] |
22 | | columns | 表单列的配置描述,具体项见下表 | ColumnProps | [] |
23 | | maxCount | 最大数量 | number | - |
24 | | fieldName | 在 `form` 中的唯一 `key`,在同一个页面中需要保持唯一 | string | - |
25 | | errorMessage | 错误提示,会出现在组件底端 | string \| React.ReactNode | - |
26 | | size | 组件的大小 | ['large', 'default', 'small'] | string | 'default' |
27 | | antConfig | 使用 `Antd ConfigProvider` 进行的全局配置,需要通过这个属性传进来 | [ConfigProviderProps](https://github.com/ant-design/ant-design/blob/master/components/config-provider/index.tsx) | - |
28 |
29 | ### Columns
30 |
31 | 表单列的配置描述,目前支持 `['input', 'select', 'custom']` 这三种。
32 |
33 | > 在 `.tsx` 文件中,请使用 `'input' as 'input'`,来避免报错
34 |
35 | | 参数 | 说明 | 类型 | 默认值 |
36 | | :--------- | :------------- | :------------------ | :----- |
37 | | type | 动态表单组件的类型,内置 `input`, `select`;也可以自定义 | `['input', 'select', 'custom']` | - |
38 | | title | 标题 | string | - |
39 | | colSpan | 默认会根据配置的长度,自动生成 `Col.span`;也可以手动设置 | number | - |
40 | | placeholder | 占位文案,默认会根据 `title` 自动生成 | string | - |
41 | | required | 是否对参数进行必填校验 | boolean | true |
42 | | rules | 自定义表单项的校验规则 | `object[]` | - |
43 | | showColon | 是否在改表单项后面添加冒号 `; ` | boolean | false |
44 | | isSelectUniq | `type="select"` 时,通过该属性设置下拉选择是否唯一 | boolean | false |
45 | | selectOptions | `type="select"` 时,通过该属性设置下拉选项 | {title: string;value: string;}[] | [] |
46 | | component | `type="custom"` 时,可以通过该属性传递 `React.ReactNode` | React.ReactNode | - |
47 |
48 |
49 | ### 处理表单数据
50 |
51 | 向后端提交数据时,由于使用`useDynamicList`时,删除数据会在 也可以使用组件库提供的工具函数来快速处理
52 |
53 | ```
54 | import { processBasicFormItemsData } from 'antd-advanced/utils';
55 | const values = form.getFieldsValue();
56 | processBasicFormItemsData(values);
57 | // [[xxx, xxx]...]
58 | ```
--------------------------------------------------------------------------------
/components/basic-form-items/index.tsx:
--------------------------------------------------------------------------------
1 | import './style/index.less';
2 | import BasicFormItems from './BasicFormItems';
3 |
4 | export default BasicFormItems;
5 |
--------------------------------------------------------------------------------
/components/basic-form-items/style/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/index.less';
2 | @import '~antd/es/style/themes/default.less';
3 |
4 | @dantd-basic-form-items-prefix-cls: ~'@{dantd-prefix}-basic-form-items';
5 |
6 | .@{dantd-basic-form-items-prefix-cls} {
7 | position: relative;
8 | margin-bottom: 10px;
9 |
10 | &-item-title {
11 | &:after {
12 | content: ":";
13 | position: absolute;
14 | margin: 0 8px 0 2px;
15 | top: 8px;
16 | right: -8px;
17 | }
18 | }
19 | &-item-del-btn {
20 | margin-top: 12px;
21 | }
22 |
23 | &-detail {
24 | &-header {
25 | display: flex;
26 | border-top: 1px solid #dbe3f2;
27 | border-bottom: 1px solid #dbe3f2;
28 | border-left: 1px solid #dbe3f2;
29 | border-right: 1px solid #dbe3f2;
30 | padding-left: 10px;
31 | padding-top: 5px;
32 | padding-bottom: 5px;
33 | margin-left: 0 !important;
34 | margin-right: 0 !important;
35 | overflow: hidden;
36 | word-break: break-word;
37 | }
38 | &-body {
39 | display: flex;
40 | border-bottom: 1px solid #dbe3f2;
41 | border-top: none;
42 | border-left: 1px solid #dbe3f2;
43 | border-right: 1px solid #dbe3f2;
44 | padding: 8px 10px;
45 | margin-left: 0 !important;
46 | margin-right: 0 !important;
47 | overflow: hidden;
48 | word-break: break-word;
49 | }
50 | &-empty {
51 | display: flex;
52 | justify-content: center;
53 | border-bottom: 1px solid #dbe3f2;
54 | border-top: none;
55 | border-left: 1px solid #dbe3f2;
56 | border-right: 1px solid #dbe3f2;
57 | padding: 8px 10px;
58 | margin-left: 0 !important;
59 | margin-right: 0 !important;
60 | }
61 | &-content {
62 | overflow: hidden;
63 | word-break: break-word;
64 | }
65 | }
66 | &-error-msg {
67 | color: @error-color;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/components/basic-form-items/style/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.less';
2 |
--------------------------------------------------------------------------------
/components/basic-table/config.tsx:
--------------------------------------------------------------------------------
1 | export const pageSizeOptions = ['10', '20', '50', '100'];
2 |
3 | export type TSorterNames = 'ascend' | 'descend';
4 |
5 | export const sorterNames = {
6 | ascend: '升序',
7 | descend: '降序',
8 | };
9 |
10 | export const locale = {
11 | filterTitle: '筛选',
12 | filterConfirm: '确定',
13 | filterReset: '重置',
14 | emptyText: '暂无数据',
15 | };
16 |
17 | export const showTotal = (total: number) => {
18 | return `共 ${total} 条`;
19 | };
20 |
--------------------------------------------------------------------------------
/components/basic-table/demo/basic.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 0
3 | title: 基本
4 | ---
5 |
6 | 只传递 `columns` 和 `dataSource` 这两个必要属性。会展示默认的分页信息,以及通用的 「排序、搜索、过滤」等功能。这个例子中,也包含了 `searchRender` 的使用,可以自定义 `render` 方法,也可以高亮输入的部分。
7 |
8 | ```jsx
9 | import { BasicTable as Table } from 'antd-advanced';
10 | import { Tag, Divider, Typography } from 'antd';
11 | const { Paragraph } = Typography;
12 |
13 | const columns = [
14 | {
15 | title: 'Name',
16 | dataIndex: 'name',
17 | key: 'name',
18 | commonFilter: true,
19 | render: text => {text},
20 | },
21 | {
22 | title: 'Age',
23 | dataIndex: 'age',
24 | commonSorter: true,
25 | key: 'age',
26 | render: text => {text},
27 | },
28 | {
29 | title: 'Address',
30 | dataIndex: 'address',
31 | key: 'address',
32 | commonSearch: true,
33 | searchRender(value: any, row: any, index: any, highlightNode: any) {
34 | const obj = {
35 | children: (
36 |
37 |
38 | {highlightNode}
39 |
40 |
41 | ),
42 | };
43 | return obj;
44 | },
45 | },
46 | {
47 | title: 'Tags',
48 | key: 'tags',
49 | dataIndex: 'tags',
50 | commonSearch: true,
51 | render: tags => (
52 |
53 | {tags.map(tag => {
54 | let color = tag.length > 5 ? 'geekblue' : 'green';
55 | if (tag === 'loser') {
56 | color = 'volcano';
57 | }
58 | return (
59 |
60 | {tag.toUpperCase()}
61 |
62 | );
63 | })}
64 |
65 | ),
66 | },
67 | {
68 | title: 'Action',
69 | key: 'action',
70 | render: (text, record) => (
71 |
72 | Invite {record.name}
73 |
74 | Delete
75 |
76 | ),
77 | },
78 | ];
79 |
80 | const data = [
81 | {
82 | key: '1',
83 | name: 'John Brown',
84 | age: 32,
85 | address: 'New York No. 1 Lake Park',
86 | tags: ['nice', 'developer'],
87 | },
88 | {
89 | key: '2',
90 | name: 'Jim Green',
91 | age: 42,
92 | address: 'London No. 1 Lake Park',
93 | tags: ['loser'],
94 | },
95 | {
96 | key: '3',
97 | name: 'Joe Black',
98 | age: 23,
99 | address: 'Sidney No. 1 Lake Park',
100 | tags: ['cool', 'teacher'],
101 | },
102 | ];
103 |
104 | const newDataSource = [];
105 | for (let i = 0; i < 200; i++) {
106 | const sItem = data[i%data.length];
107 | newDataSource.push(sItem);
108 | }
109 |
110 | ReactDOM.render(
111 | ,
114 | mountNode,
115 | );
116 | ```
117 |
--------------------------------------------------------------------------------
/components/basic-table/demo/basicFetch.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 4
3 | title: 从API获取数据
4 | ---
5 |
6 | 这是一个较为完整的,从 `API` 获取数据的示例,使用 `BasicTable` 完成
7 |
8 | ```jsx
9 | import { Button, Tabs, DatePicker, Form } from 'antd';
10 | import { BasicTable as Table } from 'antd-advanced';
11 | import moment from 'moment';
12 |
13 | const columns = [
14 | {
15 | title: '标题',
16 | dataIndex: 'title',
17 | commonSearch: true,
18 | },
19 | {
20 | title: '缩略图',
21 | dataIndex: 'image',
22 | render: (text, record, index) => (
23 |
24 | {record.thumbnail_pic_s ? (
25 |

26 | ) : (
27 | '暂无图片'
28 | )}
29 |
30 | ),
31 | },
32 | {
33 | title: '分类',
34 | dataIndex: 'category',
35 | commonFilter: true,
36 | },
37 | {
38 | title: '作者',
39 | dataIndex: 'author_name',
40 | commonFilter: true,
41 | },
42 | {
43 | title: '发布日期',
44 | dataIndex: 'date',
45 | commonSorter: true,
46 | },
47 | ];
48 |
49 | function getOnHourUnix(time) {
50 | if (!time) {
51 | return '';
52 | }
53 | const onHourDate = new Date(time.format('YYYY-MM-DD 00:00:00'));
54 | return moment(onHourDate)
55 | .unix()
56 | .toString();
57 | }
58 |
59 | const listUrl = 'https://service-dmgco1kc-1302187237.gz.apigw.tencentcs.com/release/table_api';
60 |
61 | const BasicExample: React.FC = () => {
62 |
63 | const [newsDate, setDate] = React.useState();
64 | const [newsData, setNews] = React.useState([]);
65 | const [loading, setLoading] = React.useState(false);
66 |
67 | React.useEffect(() => {
68 | fetchData();
69 | }, [newsDate]);
70 |
71 | async function fetchData() {
72 | const date = getOnHourUnix(newsDate);
73 | let url = new URL(listUrl) as any;
74 | url.search = new URLSearchParams({
75 | date,
76 | }) as any;
77 | setLoading(true);
78 | const res = await fetch(url);
79 | res
80 | .json()
81 | .then(res => {
82 | setNews(res.data);
83 | setLoading(false);
84 | })
85 | .catch(() => {
86 | setLoading(false);
87 | });
88 | }
89 |
90 | const onDateChange = newDate => {
91 | setDate(newDate);
92 | };
93 | return (
94 |
108 |
109 |
110 | }
111 | queryMode="compact"
112 | searchPos="right"
113 | searchPlaceholder="模糊匹配,输入标题/作者/分类,以空格分隔"
114 | reloadBtnPos="left"
115 | reloadBtnType="btn"
116 | filterType="none"
117 | loading={loading}
118 | onReload={() => {
119 | fetchData();
120 | }}
121 | tableTitle="头条新闻"
122 | showSearch={true}
123 | columns={columns}
124 | dataSource={newsData}
125 | />
126 | );
127 | }
128 |
129 | ReactDOM.render(
130 |
131 |
132 |
,
133 | mountNode,
134 | );
135 | ```
136 |
--------------------------------------------------------------------------------
/components/basic-table/demo/basicHalfHeader.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 1
3 | title: 常用表头
4 | ---
5 |
6 | 表头包含左侧的标题区,和右侧的筛选区。
7 |
8 | ```jsx
9 | import { BasicTable as Table } from 'antd-advanced';
10 | import { Tag, Divider, Typography } from 'antd';
11 | const { Paragraph } = Typography;
12 |
13 | const columns = [
14 | {
15 | title: 'Name',
16 | dataIndex: 'name',
17 | key: 'name',
18 | commonFilter: true,
19 | render: text => {text},
20 | },
21 | {
22 | title: 'Age',
23 | dataIndex: 'age',
24 | commonSorter: true,
25 | key: 'age',
26 | render: text => {text},
27 | },
28 | {
29 | title: 'Address',
30 | dataIndex: 'address',
31 | key: 'address',
32 | commonSearch: true,
33 | searchRender(value: any, row: any, index: any, highlightNode: any) {
34 | const obj = {
35 | children: (
36 |
37 |
38 | {highlightNode}
39 |
40 |
41 | ),
42 | };
43 | return obj;
44 | },
45 | },
46 | {
47 | title: 'Tags',
48 | key: 'tags',
49 | dataIndex: 'tags',
50 | render: tags => (
51 |
52 | {tags.map(tag => {
53 | let color = tag.length > 5 ? 'geekblue' : 'green';
54 | if (tag === 'loser') {
55 | color = 'volcano';
56 | }
57 | return (
58 |
59 | {tag.toUpperCase()}
60 |
61 | );
62 | })}
63 |
64 | ),
65 | },
66 | {
67 | title: 'Action',
68 | key: 'action',
69 | render: (text, record) => (
70 |
71 | Invite {record.name}
72 |
73 | Delete
74 |
75 | ),
76 | },
77 | ];
78 |
79 | const data = [
80 | {
81 | key: '1',
82 | name: 'John Brown',
83 | age: 32,
84 | address: 'New York No. 1 Lake Park',
85 | tags: ['nice', 'developer'],
86 | },
87 | {
88 | key: '2',
89 | name: 'Jim Green',
90 | age: 42,
91 | address: 'London No. 1 Lake Park',
92 | tags: ['loser'],
93 | },
94 | {
95 | key: '3',
96 | name: 'Joe Black',
97 | age: 23,
98 | address: 'Sidney No. 1 Lake Park',
99 | tags: ['cool', 'teacher'],
100 | },
101 | ];
102 |
103 | ReactDOM.render(
104 | ,
107 | mountNode,
108 | );
109 | ```
110 |
--------------------------------------------------------------------------------
/components/basic-table/demo/query.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 6
3 | title: query 模式
4 | ---
5 |
6 | 只传递 `columns` 和 `dataSource` 这两个必要属性。会展示默认的分页信息,以及通用的 「排序、搜索、过滤」等功能。这个例子中,也包含了 `searchRender` 的使用,可以自定义 `render` 方法,也可以高亮输入的部分。
7 |
8 | ```jsx
9 | import { useState } from 'react';
10 | import { BasicTable, useAsyncRetry } from 'antd-advanced';
11 | import { Button, Divider, Typography } from 'antd';
12 |
13 | const columns = [
14 | {
15 | title: '标题',
16 | dataIndex: 'title',
17 | },
18 | {
19 | title: '缩略图',
20 | dataIndex: 'image',
21 | render: (text, record, index) => (
22 |
23 | {record.thumbnail_pic_s ? (
24 |

25 | ) : (
26 | '暂无图片'
27 | )}
28 |
29 | ),
30 | },
31 | {
32 | title: '分类',
33 | dataIndex: 'category',
34 | },
35 | {
36 | title: '作者',
37 | dataIndex: 'author_name',
38 | },
39 | {
40 | title: '发布日期',
41 | dataIndex: 'date',
42 | commonSorter: true,
43 | },
44 | ];
45 |
46 | const queryFormColumns = [
47 | {
48 | type: 'input',
49 | title: '标题',
50 | dataIndex: 'title',
51 | },
52 | {
53 | type: 'select',
54 | title: '作者',
55 | dataIndex: 'author_name',
56 | options: [
57 | {
58 | title: '快科技',
59 | value: '快科技',
60 | },
61 | {
62 | title: '搜狐新闻',
63 | value: '搜狐新闻',
64 | },
65 | {
66 | title: '科技朝闻',
67 | value: '科技朝闻',
68 | },
69 | ],
70 | },
71 | {
72 | type: 'select',
73 | title: '分类',
74 | dataIndex: 'category',
75 | selectMode: 'multiple',
76 | options: [
77 | {
78 | title: '科技',
79 | value: '科技',
80 | },
81 | {
82 | title: '头条',
83 | value: '头条',
84 | },
85 | {
86 | title: '社会',
87 | value: '社会',
88 | },
89 | {
90 | title: '财经',
91 | value: '财经',
92 | },
93 | ],
94 | },
95 | ];
96 |
97 | const fetchData = () => {
98 | const listUrl = 'https://service-dmgco1kc-1302187237.gz.apigw.tencentcs.com/release/table_api';
99 | return new Promise(async (resolve, reject) => {
100 | const res = await fetch(listUrl);
101 | res
102 | .json()
103 | .then(res => {
104 | resolve(res.data);
105 | })
106 | .catch((err) => {
107 | reject(err)
108 | });
109 | })
110 | }
111 |
112 | const Demo: React.FC = () => {
113 | const tableState = useAsyncRetry(fetchData)
114 |
115 | return (
116 |
120 | 一个按钮}
125 | reloadBtnPos="left"
126 | reloadBtnType="btn"
127 | filterType="none"
128 | // hideContentBorder={true}
129 | showSearch={false}
130 | columns={columns}
131 | dataSource={tableState.value}
132 | />
133 |
134 | );
135 | }
136 |
137 | ReactDOM.render(
138 |
139 |
140 |
,
141 | mountNode,
142 | );
143 | ```
144 |
--------------------------------------------------------------------------------
/components/basic-table/demo/select.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 5
3 | title: 选择和操作
4 | ---
5 |
6 | 选择后进行操作,完成后清空选择,通过 `rowSelection.selectedRowKeys` 来控制选中项。
7 |
8 | ```jsx
9 | import { BasicTable as Table } from 'antd-advanced';
10 | import { Tag, Divider, Typography } from 'antd';
11 | const { Paragraph } = Typography;
12 |
13 | const columns = [
14 | {
15 | title: 'Name',
16 | dataIndex: 'name',
17 | key: 'name',
18 | commonFilter: true,
19 | render: text => {text},
20 | },
21 | {
22 | title: 'Age',
23 | dataIndex: 'age',
24 | commonSorter: true,
25 | key: 'age',
26 | render: text => {text},
27 | },
28 | {
29 | title: 'Address',
30 | dataIndex: 'address',
31 | key: 'address',
32 | commonSearch: true,
33 | searchRender(value: any, row: any, index: any, highlightNode: any) {
34 | const obj = {
35 | children: (
36 |
37 |
38 | {highlightNode}
39 |
40 |
41 | ),
42 | };
43 | return obj;
44 | },
45 | },
46 | {
47 | title: 'Tags',
48 | key: 'tags',
49 | dataIndex: 'tags',
50 | commonSearch: true,
51 | render: tags => (
52 |
53 | {tags.map(tag => {
54 | let color = tag.length > 5 ? 'geekblue' : 'green';
55 | if (tag === 'loser') {
56 | color = 'volcano';
57 | }
58 | return (
59 |
60 | {tag.toUpperCase()}
61 |
62 | );
63 | })}
64 |
65 | ),
66 | },
67 | {
68 | title: 'Action',
69 | key: 'action',
70 | render: (text, record) => (
71 |
72 | Invite {record.name}
73 |
74 | Delete
75 |
76 | ),
77 | },
78 | ];
79 |
80 | const data = [
81 | {
82 | key: '1',
83 | name: 'John Brown',
84 | age: 32,
85 | address: 'New York No. 1 Lake Park',
86 | tags: ['nice', 'developer'],
87 | },
88 | {
89 | key: '2',
90 | name: 'Jim Green',
91 | age: 42,
92 | address: 'London No. 1 Lake Park',
93 | tags: ['loser'],
94 | },
95 | {
96 | key: '3',
97 | name: 'Joe Black',
98 | age: 23,
99 | address: 'Sidney No. 1 Lake Park',
100 | tags: ['cool', 'teacher'],
101 | },
102 | ];
103 |
104 | const SelectDemo: React.FC = () => {
105 | const [selectedRowKeys, setSelectedRowKeys] = React.useState([]);
106 | const onSelectChange = newKeys => {
107 | setSelectedRowKeys(newKeys);
108 | };
109 | const rowSelection = {
110 | selectedRowKeys,
111 | onChange: onSelectChange,
112 | };
113 | return (
114 |
115 | )
116 | }
117 |
118 | ReactDOM.render(
119 |
120 |
121 |
,
122 | mountNode,
123 | );
124 | ```
125 |
--------------------------------------------------------------------------------
/components/basic-table/index.tsx:
--------------------------------------------------------------------------------
1 | import './style/index.less';
2 | import BasicTable from './BasicTable';
3 |
4 | export default BasicTable;
5 |
--------------------------------------------------------------------------------
/components/basic-table/style/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.less';
2 |
--------------------------------------------------------------------------------
/components/card/Card.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import _ from 'lodash';
3 | import classNames from 'classnames';
4 |
5 | interface ISiderData {
6 | label: string;
7 | value: string | number;
8 | }
9 |
10 | interface IBasicCard {
11 | prefixCls?: string;
12 | title: string;
13 | value: number | string;
14 | rightHeader?: React.ReactNode;
15 | style?: React.CSSProperties;
16 | icon?: React.ReactNode;
17 | subTitle?: string;
18 | siderData?: ISiderData[];
19 | showProgress?: boolean;
20 | progressLabel?: string;
21 | progressPercent?: number;
22 | footerDom?: React.ReactNode;
23 | theme?: 'default' | 'gray' | 'success' | 'error' | 'warning' | 'info';
24 | }
25 |
26 | function Card(props: IBasicCard) {
27 | const prefixCls = `${props.prefixCls || 'dantd'}-card`;
28 | const {
29 | style = {},
30 | title,
31 | rightHeader = ,
32 | icon,
33 | siderData = [],
34 | value,
35 | subTitle,
36 | showProgress = false,
37 | progressLabel,
38 | progressPercent,
39 | footerDom,
40 | theme = 'default',
41 | } = props;
42 |
43 | const cardCls = classNames({
44 | [prefixCls]: true,
45 | [`card-${theme}`]: true,
46 | [`${prefixCls}-has-battery`]: showProgress,
47 | });
48 |
49 | const bottomCls = classNames({
50 | [`${prefixCls}-bottom`]: true,
51 | [`${prefixCls}-bottom-battery`]: showProgress,
52 | [`${prefixCls}-bottom-custom`]: !!footerDom,
53 | });
54 |
55 | const batteryCls = classNames({
56 | 'bg-full-percent': progressPercent === 100,
57 | 'bg-percent': progressPercent !== 100,
58 | });
59 |
60 | return (
61 |
62 |
63 |
{title}
64 |
65 | {rightHeader}
66 |
67 |
68 |
69 |
70 | {icon &&
{icon}
}
71 |
72 |
{value}
73 | {subTitle &&
{subTitle}
}
74 |
75 |
76 |
77 | {!_.isEmpty(siderData) && (
78 |
79 | {_.map(siderData, (item, i) => (
80 |
81 | {item.label}
82 | {item.value}
83 |
84 | ))}
85 |
86 | )}
87 |
88 |
89 | {showProgress && (
90 |
91 |
{progressLabel}
92 |
98 |
99 | )}
100 | {footerDom &&
{footerDom}
}
101 |
102 |
103 | );
104 | }
105 |
106 | export default Card;
107 |
--------------------------------------------------------------------------------
/components/card/__tests__/basic.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Icon } from 'antd';
3 | import '@testing-library/jest-dom/extend-expect';
4 | import { render } from '@testing-library/react';
5 | import BasicCard from '../Card';
6 |
7 | describe('Card', () => {
8 | it('右侧rightHeader渲染正常', () => {
9 | const rightHeader = ;
10 | const { getByTestId } = render(
11 | }
15 | rightHeader={rightHeader}
16 | />,
17 | );
18 | const element = getByTestId('header-right');
19 | expect(element).toHaveClass('dantd-card-header-right');
20 | });
21 |
22 | it('检验siderData渲染正常', () => {
23 | const siederData = [
24 | {
25 | label: '指标1',
26 | value: '30%',
27 | },
28 | {
29 | label: '指标2',
30 | value: '20%',
31 | },
32 | {
33 | label: '指标3',
34 | value: '20%',
35 | },
36 | {
37 | label: '指标4',
38 | value: '20%',
39 | },
40 | {
41 | label: '指标5',
42 | value: '20%',
43 | },
44 | {
45 | label: '指标6',
46 | value: '20%',
47 | },
48 | ];
49 | const { getByTestId } = render(
50 | ,
51 | );
52 | const element = getByTestId('sider-data');
53 | expect(element.children.length).toBe(6);
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/components/card/demo/basic.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 0
3 | title: 基本卡片
4 | ---
5 |
6 | 卡片白底样式,基本属性包括标题和数值。
7 |
8 |
9 | ```jsx
10 | import { BasicCard } from 'antd-advanced';
11 |
12 | const BasicExample: React.FC = () => {
13 | return (
14 |
15 |
18 |
23 |
29 |
30 | );
31 | }
32 |
33 | ReactDOM.render(
34 |
35 |
36 |
,
37 | mountNode,
38 | );
39 | ```
40 |
--------------------------------------------------------------------------------
/components/card/demo/extra.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 1
3 | title: 带操作按钮
4 | ---
5 |
6 | 自定义右上角操作栏。
7 |
8 |
9 | ```jsx
10 | import { BasicCard } from 'antd-advanced';
11 | import { Icon } from 'antd';
12 |
13 | const BasicExample: React.FC = () => {
14 | return (
15 | }
19 | rightHeader={
20 |
21 |
22 |
23 |
24 | }
25 | />
26 | );
27 | }
28 |
29 | ReactDOM.render(
30 |
31 |
32 |
,
33 | mountNode,
34 | );
35 | ```
36 |
--------------------------------------------------------------------------------
/components/card/demo/footer.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 6
3 | title: 底部支持自定义
4 | ---
5 |
6 | 卡片底部支持传入图表组件。
7 |
8 |
9 | ```jsx
10 | import { BasicCard } from 'antd-advanced';
11 | import { Icon } from 'antd';
12 | import ReactEcharts from 'echarts-for-react';
13 |
14 | const BasicExample: React.FC = () => {
15 | function getLineChart(grahData = {xdata: [], ydata: []}) {
16 | const option = {
17 | color: ['rgba(255, 255, 255, 0.5)'],
18 | tooltip: {
19 | trigger: 'axis',
20 | },
21 | grid: {
22 | show: false,
23 | left: 0,
24 | right: 0,
25 | top: 0,
26 | bottom:0,
27 | },
28 | xAxis: {
29 | show: false,
30 | data: _.get(grahData, 'xdata') || [],
31 | boundaryGap: false,
32 | },
33 | yAxis: {
34 | type: 'value',
35 | show: false
36 | },
37 | series:[{
38 | data: _.get(grahData, 'ydata') || [],
39 | type: 'line',
40 | areaStyle: {}
41 | }],
42 | };
43 | return option;
44 | }
45 |
46 | const graphData = {
47 | xdata: ['0','1','2','3', '4'],
48 | ydata: ['1','1','2','3', '4']
49 | };
50 |
51 | return (
52 |
57 |
58 |
59 |
60 | }
61 | footerDom={
62 |
68 | }
69 | theme="success"
70 | />
71 | );
72 | }
73 |
74 | ReactDOM.render(
75 |
76 |
77 |
,
78 | mountNode,
79 | );
80 | ```
81 |
--------------------------------------------------------------------------------
/components/card/demo/icon.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 2
3 | title: 图标或图片
4 | ---
5 |
6 | 传参icon支持antd icon格式和自定义图片dom。
7 |
8 |
9 | ```jsx
10 | import { BasicCard } from 'antd-advanced';
11 | import { Icon } from 'antd';
12 |
13 | const BasicExample: React.FC = () => {
14 | return (
15 |
16 | }
20 | style={{marginTop: 20 }}
21 | />
22 |
23 | );
24 | }
25 |
26 | ReactDOM.render(
27 |
28 |
29 |
,
30 | mountNode,
31 | );
32 | ```
33 |
--------------------------------------------------------------------------------
/components/card/demo/percent.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 4
3 | title: 进度条
4 | ---
5 |
6 | 传参showProgress为true,progressLabel展示label,progressPercent展示进度比例。
7 |
8 | ```jsx
9 | import { BasicCard } from 'antd-advanced';
10 | import { Icon } from 'antd';
11 |
12 | const BasicExample: React.FC = () => {
13 | return (
14 |
42 | );
43 | }
44 |
45 | ReactDOM.render(
46 |
47 |
48 |
,
49 | mountNode,
50 | );
51 | ```
52 |
--------------------------------------------------------------------------------
/components/card/demo/sider.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 3
3 | title: 侧边指标数据
4 | ---
5 |
6 | 传参siderData,支持侧边指标数据展示。
7 |
8 |
9 | ```jsx
10 | import { BasicCard } from 'antd-advanced';
11 | import { Icon } from 'antd';
12 |
13 | const BasicExample: React.FC = () => {
14 | return (
15 | }
19 | siderData={
20 | [{
21 | label: '指标1',
22 | value: '30%'
23 | }, {
24 | label: '指标2',
25 | value: '20%'
26 | }, {
27 | label: '指标2',
28 | value: '20%'
29 | }, {
30 | label: '指标2',
31 | value: '20%'
32 | }, {
33 | label: '指标2',
34 | value: '20%'
35 | }, {
36 | label: '指标2',
37 | value: '20%'
38 | }]
39 | }
40 | />
41 | );
42 | }
43 |
44 | ReactDOM.render(
45 |
46 |
47 |
,
48 | mountNode,
49 | );
50 | ```
51 |
--------------------------------------------------------------------------------
/components/card/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 组件
3 | type: 数据展示
4 | title: Card
5 | subtitle: 卡片
6 | ---
7 |
8 | ## 何时使用
9 |
10 | 最基础的卡片容器,支持文字,数值,icon和进度条的展示,支持定制主题色。
11 |
12 | ## API
13 |
14 | | 参数 | 说明 | 类型 | 默认值
15 | | --- | --- | --- | --- | --- |
16 | | title | 卡片标题 | string\|ReactNode | - |
17 | | value | 数据 | number\|string | - |
18 | | rightHeader | 右上角操作栏 | ReactNode | - |
19 | | theme | 主题色,可选`default` \| `gray` \|`warning` \| `info` \| `error` \| `success`| string | default | - |
20 | | style |样式 | React.CSSProperties | - |
21 | | icon | 图标或图片,支持传入antd的icon或img | ReactNode | - |
22 | | subTitle | 数字标题 | string | - |
23 | | siderData | 指标数据 | {label: string, value: string\|number}[] | [] |
24 | | showProgress | 是否展示进度条 | boolean | false |
25 | | progressLabel | 指标标识文案 | string | - |
26 | | progressPercent | 进度比例(0-100) | number | - |
27 | | footerDom | 自定义底部 | ReactNode | - |
28 | | prefixCls | 样式前缀 | string | - |
29 |
--------------------------------------------------------------------------------
/components/card/index.tsx:
--------------------------------------------------------------------------------
1 | import './style/index.less';
2 | import Card from './Card';
3 |
4 | export default Card;
5 |
--------------------------------------------------------------------------------
/components/card/style/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.less';
2 |
--------------------------------------------------------------------------------
/components/code/demo/basic.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 0
3 | title: 基本
4 | ---
5 |
6 | 简单的使用`代码块展示`,以及`代码块编辑`。
7 |
8 | ```jsx
9 | import { Code } from 'antd-advanced';
10 | const codeStr = 'const twoSum = function(nums, target) {\n const newNumsMap = new Map(nums.map((item, idx) => [item, idx]));\n\n let result = [];\n nums.forEach((numItem, numIndex) => {\n const subItem = target - numItem;\n if (newNumsMap.has(subItem) \&\& numIndex !== newNumsMap.get(subItem)) {\n result = [newNumsMap.get(subItem), numIndex];\n }\n })\n return result;\n};\n\nconst nums = [2, 7, 11, 15];\nconst target = 9;\n\nconsole.info(twoSum(nums, target));';
11 | const Demo: React.FC = () => {
12 | return (
13 |
14 |
Code
15 |
16 | Code editor
17 |
18 |
19 | );
20 | }
21 |
22 | ReactDOM.render(
23 |
24 |
25 |
,
26 | mountNode,
27 | );
28 | ```
29 |
--------------------------------------------------------------------------------
/components/code/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 组件
3 | type: 数据展示
4 | cols: 1
5 | title: Code
6 | subtitle: 代码块
7 | ---
8 |
9 | ## 何时使用
10 |
11 | 更优雅的展示、编辑代码块。
12 |
13 | ## API
14 |
15 | > 注意:value 和 defaultValue 只能设置一个
16 |
17 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
18 | | --- | --- | --- | --- | --- |
19 | | value | 当前 `code` 的值,设置了会变成受控组件 | string | - | |
20 | | defaultValue | 非受控组件的初始值 | string | - | |
21 | | onChange | 变化时回调函数 | Function(value: string) | - | |
22 | | loading | 让代码块展示加载中的状态 | boolean | false | |
23 | | defaultChecked | 初始是否选中 | boolean | false | |
24 | | style | 代码块的容器样式 | React.CSSProperties | - | |
25 | | width | 代码块的容器的宽度 | number \| string | `100%` | |
26 | | height | 代码块的容器的高度 | number \| string | `100px` | |
27 | | editable | 是否可以编辑 | boolean | false | |
28 | | theme | 主题 | `light`\| `dark` | `light` | |
29 | | language | 语言 | `javascript` \|`css` \|`go` \|`markdown` \|`jsx` \|`java` \|`nginx` \|`php` \|`python` \|`shell` \|`sql` \|`xml` \|`html` | - | |
30 | | className | Code 类名 | string | - | |
31 |
--------------------------------------------------------------------------------
/components/code/index.tsx:
--------------------------------------------------------------------------------
1 | import './style/index.less';
2 | import Code from './code';
3 |
4 | export default Code;
5 |
--------------------------------------------------------------------------------
/components/code/style/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/index.less';
2 | @import '~antd/es/style/themes/default.less';
3 | @dantd-switch-prefix-cls: ~'@{dantd-prefix}-code';
4 |
5 | .@{dantd-switch-prefix-cls} {
6 | position: relative;
7 | &-editor-wrapper {
8 | height: 100px;
9 | }
10 |
11 | &-editor {
12 | position: relative;
13 | height: 100%;
14 | .CodeMirror {
15 | height: 100%;
16 | }
17 | .CodeMirror-scroll {
18 | padding: 10px;
19 | }
20 | .CodeMirror pre.CodeMirror-line {
21 | padding: 0;
22 | }
23 | &-light {
24 | .cm-s-neo.CodeMirror {
25 | background: rgb(245, 247, 255);
26 | }
27 | .CodeMirror {
28 | background: rgb(245, 247, 255);
29 | }
30 | }
31 | &-dark {
32 | .cm-s-mbo .CodeMirror-gutters {
33 | left: 0 !important;
34 | }
35 | }
36 | }
37 |
38 | &-text {
39 | pre > code {
40 | padding: 5px 0px !important;
41 | font-size: 12px;
42 | line-height: 1.5;
43 | }
44 | pre > code > code {
45 | padding: 0 12px 0 5px !important;
46 | font-size: 12px;
47 | line-height: 1.5;
48 | }
49 | &-light {
50 | pre code {
51 | background: rgb(245, 247, 255) !important;
52 | }
53 | }
54 | &-dark {
55 | pre code {
56 | background: rgb(29, 31, 33) !important;
57 | color: rgb(197, 200, 198);
58 | }
59 | }
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/components/code/style/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.less';
2 |
--------------------------------------------------------------------------------
/components/color-select/demo/basic.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 0
3 | title: 基本
4 | ---
5 |
6 | 拾色选择器的基本使用
7 |
8 | ```jsx
9 | import { ColorSelect } from 'antd-advanced';
10 |
11 |
12 | ReactDOM.render(
13 |
14 |
15 |
,
16 | mountNode,
17 | );
18 | ```
19 |
--------------------------------------------------------------------------------
/components/color-select/demo/disabled.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 2
3 | title: 禁用模式
4 | ---
5 |
6 | 使用 disabled 禁用组件
7 |
8 | ```jsx
9 | import { ColorSelect, EmptyLine } from 'antd-advanced';
10 |
11 |
12 | ReactDOM.render(
13 |
14 |
15 |
16 |
17 |
,
18 | mountNode,
19 | );
20 | ```
21 |
--------------------------------------------------------------------------------
/components/color-select/demo/form.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 3
3 | title: 结合Form.Item使用
4 | ---
5 |
6 | 结合 `Form.Item` 包裹组件
7 |
8 | ```jsx
9 | import { ColorSelect } from 'antd-advanced';
10 | import { Form } from 'antd';
11 |
12 | ReactDOM.render(
13 |
14 |
15 |
16 |
17 | ,
18 | mountNode,
19 | );
20 | ```
21 |
--------------------------------------------------------------------------------
/components/color-select/demo/value.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 1
3 | title: 受控模式
4 | ---
5 |
6 | 提供 value 和 onChange 属性,支持受控模式的组件
7 |
8 | ```jsx
9 | import { useState } from 'react';
10 | import { ColorSelect } from 'antd-advanced';
11 |
12 | const Demo: React.FC = () => {
13 | const [color, setColor] = useState('#746aa7');
14 | return (
15 | {
18 | setColor(newColor);
19 | }}
20 | />
21 | )
22 | }
23 |
24 | ReactDOM.render(
25 |
26 |
27 |
,
28 | mountNode,
29 | );
30 | ```
31 |
--------------------------------------------------------------------------------
/components/color-select/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 组件
3 | type: 数据录入
4 | title: ColorSelect
5 | subtitle: 颜色选择器
6 | ---
7 |
8 | ## 何时使用
9 |
10 | 需要使用 `hex` 模式,选择颜色时;可结合 form 表单使用。
11 |
12 | ### TODO
13 |
14 | 支持 `RGB` 模式等更多扩展
15 |
16 | ## API
17 |
18 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
19 | | --- | --- | --- | --- | --- |
20 | | className | 类名 | string | - | |
21 | | defaultValue | 初始默认颜色,非受控模式下使用 | string | | |
22 | | value | 颜色值 | string | | |
23 | | disabled | 是否禁用 | boolean | false | |
24 | | placeholder | 输入框占位 | string | 请选择颜色 | |
25 | | onChange | 变化时回调函数 | Function(color: string) | | |
26 | | style | 组件外层的 style 属性 | object | - | |
27 | | inputStyle | 输入框的 style 属性 | object | - | |
28 |
--------------------------------------------------------------------------------
/components/color-select/index.tsx:
--------------------------------------------------------------------------------
1 | import './style/index.less';
2 | import ColorSelect from './ColorSelect';
3 | export default ColorSelect;
4 |
--------------------------------------------------------------------------------
/components/color-select/style/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/index.less';
2 | @import '~antd/es/style/themes/default.less';
3 |
4 | @dantd-color-select-prefix-cls: ~'@{dantd-prefix}-color-select';
5 |
6 | .@{dantd-color-select-prefix-cls} {
7 | position: relative;
8 | display: flex;
9 |
10 | &-input-bg {
11 | position: absolute;
12 | top: 5px;
13 | left: 6px;
14 | right: 52px;
15 | bottom: 5px;
16 | border-radius: 2px;
17 | line-height: 22px;
18 | background-color: transparent;
19 | text-align: center;
20 | color: #fff;
21 | cursor: pointer;
22 | }
23 |
24 | input {
25 | text-align: center !important;
26 | background-color: transparent;
27 | color: transparent;
28 | }
29 | }
--------------------------------------------------------------------------------
/components/color-select/style/index.ts:
--------------------------------------------------------------------------------
1 | import './style.less';
2 |
--------------------------------------------------------------------------------
/components/create-global-state/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 自定义Hook
3 | cols: 1
4 | type: 工厂
5 | title: createGlobalState
6 | subtitle: 全局状态工厂
7 | ---
8 |
9 | ## 何时使用
10 |
11 | 一个创造全局共享状态的 React Hook,可以理解为一个全局的 `useState`。
12 |
13 | `createGlobalState` 工厂会返回一个自定义hook,这个自定义hook的使用方式和 `useState` 是一样的,
14 | 返回一个 `state`,以及更新 `state` 的函数
15 |
16 | > 与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。
17 |
18 | ```jsx
19 | setState(prevState => {
20 | // 也可以使用 Object.assign
21 | return {...prevState, ...updatedValues};
22 | });
23 | ```
24 |
25 | 唯一不同的地方,是初始值的设置。因为是全局共享,所以自定义hook不支持传参设置初始值,
26 | 只能调用返回值的第二个参数,来设置状态的改变。
27 |
28 | ### Demo
29 |
30 | ```jsx
31 | import { createGlobalState } from 'antd-advanced';
32 |
33 | const useGlobalValue = createGlobalState(0);
34 |
35 | const CompA: FC = () => {
36 | const [value, setValue] = useGlobalValue();
37 |
38 | return ;
39 | };
40 |
41 | const CompB: FC = () => {
42 | const [value, setValue] = useGlobalValue();
43 |
44 | return ;
45 | };
46 |
47 | const Demo: FC = () => {
48 | const [value] = useGlobalValue();
49 | return (
50 |
51 |
{value}
52 |
53 |
54 |
55 | );
56 | };
57 | ```
58 |
59 | copy 自 [react-use](https://github.com/streamich/react-use/blob/master/docs/createGlobalState.md)
60 |
--------------------------------------------------------------------------------
/components/create-global-state/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import { useState } from 'react';
3 | import useEffectOnce from './useEffectOnce';
4 | import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect';
5 |
6 | export function createGlobalState(initialState?: S) {
7 | const store: { state: S | undefined; setState: (state: S | ((preState: S) => S)) => void; setters: any[] } = {
8 | state: initialState,
9 | setState(state: S | ((preState: S) => S)) {
10 | if (Object.prototype.toString.call(state) === '[object Function]') {
11 | const stateFunc = state as Function;
12 | store.state = stateFunc(store.state);
13 | } else {
14 | store.state = state as S;
15 | }
16 | store.setters.forEach(setter => setter(store.state));
17 | },
18 | setters: [],
19 | };
20 |
21 | return (): [S | undefined, (state: S | ((preState: S) => S)) => void] => {
22 | const [globalState, stateSetter] = useState(store.state);
23 |
24 | useEffectOnce(() => () => {
25 | store.setters = store.setters.filter(setter => setter !== stateSetter);
26 | });
27 |
28 | useIsomorphicLayoutEffect(() => {
29 | if (!store.setters.includes(stateSetter)) {
30 | store.setters.push(stateSetter);
31 | }
32 | });
33 |
34 | return [globalState, store.setState];
35 | };
36 | }
37 |
38 | export default createGlobalState;
39 |
--------------------------------------------------------------------------------
/components/create-global-state/useEffectOnce.ts:
--------------------------------------------------------------------------------
1 | import { EffectCallback, useEffect } from 'react';
2 |
3 | const useEffectOnce = (effect: EffectCallback) => {
4 | useEffect(effect, []);
5 | };
6 |
7 | export default useEffectOnce;
8 |
--------------------------------------------------------------------------------
/components/create-global-state/useIsomorphicLayoutEffect.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useLayoutEffect } from 'react';
2 |
3 | const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
4 |
5 | export default useIsomorphicLayoutEffect;
6 |
--------------------------------------------------------------------------------
/components/create-reducer-context/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 自定义Hook
3 | cols: 1
4 | type: 工厂
5 | title: createReducerContext
6 | subtitle: 全局Reducer工厂
7 | ---
8 |
9 | ## 何时使用
10 |
11 | react上下文 hook 的工厂,除了状态将在提供者中的所有组件之间共享之外,它的行为与react的useReducer一样。
12 |
13 | 这允许您拥有任何组件都可以轻松更新的共享状态。
14 |
15 |
16 | ```jsx
17 | import { createReducerContext } from 'antd-advanced';
18 |
19 | type Action = 'increment' | 'decrement';
20 |
21 | const reducer = (state: number, action: Action) => {
22 | switch (action) {
23 | case 'increment':
24 | return state + 1;
25 | case 'decrement':
26 | return state - 1;
27 | default:
28 | throw new Error();
29 | }
30 | };
31 |
32 | const [useSharedCounter, SharedCounterProvider] = createReducerContext(reducer, 0);
33 |
34 | const ComponentA = () => {
35 | const [count, dispatch] = useSharedCounter();
36 | return (
37 |
38 | Component A
39 |
42 | {count}
43 |
46 |
47 | );
48 | };
49 |
50 | const ComponentB = () => {
51 | const [count, dispatch] = useSharedCounter();
52 | return (
53 |
54 | Component B
55 |
58 | {count}
59 |
62 |
63 | );
64 | };
65 |
66 | const Demo = () => {
67 | return (
68 |
69 | Those two counters share the same value.
70 |
71 |
72 |
73 | );
74 | };
75 | ```
76 |
77 | ```jsx
78 |
79 | const [useSharedState, SharedStateProvider] = createReducerContext(reducer, initialState);
80 |
81 | // In wrapper
82 | const Wrapper = ({ children }) => (
83 | // You can override the initial state for each Provider
84 |
85 | { children }
86 |
87 | )
88 |
89 | // In a component
90 | const Component = () => {
91 | const [sharedState, dispatch] = useSharedState();
92 |
93 | // ...
94 | }
95 | ```
96 |
97 | copy 自 [react-use](https://github.com/streamich/react-use/blob/master/docs/createReducerContext.md)
98 |
--------------------------------------------------------------------------------
/components/create-reducer-context/index.ts:
--------------------------------------------------------------------------------
1 | import { createElement, createContext, useContext, useReducer } from 'react';
2 |
3 | const createReducerContext = >(
4 | reducer: R,
5 | defaultInitialState: React.ReducerState,
6 | ) => {
7 | const context = createContext<
8 | [React.ReducerState, React.Dispatch>] | undefined
9 | >(undefined);
10 | const providerFactory = (props, children) => createElement(context.Provider, props, children);
11 |
12 | const ReducerProvider: React.FC<{ initialState?: React.ReducerState }> = ({
13 | children,
14 | initialState,
15 | }) => {
16 | const state = useReducer(
17 | reducer,
18 | initialState !== undefined ? initialState : defaultInitialState,
19 | );
20 | return providerFactory({ value: state }, children);
21 | };
22 |
23 | const useReducerContext = () => {
24 | const state = useContext(context);
25 | if (state == null) {
26 | throw new Error(`useReducerContext must be used inside a ReducerProvider.`);
27 | }
28 | return state;
29 | };
30 |
31 | return [useReducerContext, ReducerProvider, context] as const;
32 | };
33 |
34 | export default createReducerContext;
35 |
--------------------------------------------------------------------------------
/components/create-state-context/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 自定义Hook
3 | cols: 1
4 | type: 工厂
5 | title: createStateContext
6 | subtitle: 全局State工厂
7 | ---
8 |
9 | ## 何时使用
10 |
11 | react上下文 hook 的工厂,除了状态将在提供者中的所有组件之间共享之外,它的行为就像react的useState。
12 |
13 | 这允许您拥有任何组件都可以轻松更新的共享状态。
14 |
15 |
16 | ```jsx
17 | import { createStateContext } from 'antd-advanced';
18 |
19 | const [useSharedText, SharedTextProvider] = createStateContext('');
20 |
21 | const ComponentA = () => {
22 | const [text, setText] = useSharedText();
23 | return (
24 |
25 | Component A:
26 |
27 | setText(ev.target.value)} />
28 |
29 | );
30 | };
31 |
32 | const ComponentB = () => {
33 | const [text, setText] = useSharedText();
34 | return (
35 |
36 | Component B:
37 |
38 | setText(ev.target.value)} />
39 |
40 | );
41 | };
42 |
43 | const Demo = () => {
44 | return (
45 |
46 | Those two fields share the same value.
47 |
48 |
49 |
50 | );
51 | };
52 | ```
53 |
54 |
55 | copy 自 [react-use](https://github.com/streamich/react-use/blob/master/docs/createStateContext.md)
56 |
--------------------------------------------------------------------------------
/components/create-state-context/index.ts:
--------------------------------------------------------------------------------
1 | import { createElement, createContext, useContext, useState } from 'react';
2 |
3 | const createStateContext = (defaultInitialValue: T) => {
4 | const context = createContext<[T, React.Dispatch>] | undefined>(
5 | undefined,
6 | );
7 | const providerFactory = (props, children) => createElement(context.Provider, props, children);
8 |
9 | const StateProvider: React.FC<{ initialValue?: T }> = ({ children, initialValue }) => {
10 | const state = useState(initialValue !== undefined ? initialValue : defaultInitialValue);
11 | return providerFactory({ value: state }, children);
12 | };
13 |
14 | const useStateContext = () => {
15 | const state = useContext(context);
16 | if (state == null) {
17 | throw new Error(`useStateContext must be used inside a StateProvider.`);
18 | }
19 | return state;
20 | };
21 |
22 | return [useStateContext, StateProvider, context] as const;
23 | };
24 |
25 | export default createStateContext;
26 |
--------------------------------------------------------------------------------
/components/data-table/config.tsx:
--------------------------------------------------------------------------------
1 | export const pageSizeOptions = ['10', '20', '50', '100'];
2 |
3 | export const paginationLocale = {
4 | items_per_page: '/ 页',
5 | jump_to: '跳至',
6 | jump_to_confirm: '确定',
7 | page: '页',
8 | prev_page: '上一页',
9 | next_page: '下一页',
10 | prev_5: '向前 5 页',
11 | next_5: '向后 5 页',
12 | prev_3: '向前 3 页',
13 | next_3: '向后 3 页',
14 | };
15 |
16 | export const sorterNames = {
17 | ascend: '升序',
18 | descend: '降序',
19 | };
20 |
21 | export type TSorterNames = 'ascend' | 'descend';
22 |
23 | export const locale = {
24 | filterTitle: '筛选',
25 | filterConfirm: '确定',
26 | filterReset: '重置',
27 | emptyText: '暂无数据',
28 | };
29 |
30 | export const showTotal = (total: number) => {
31 | return `共 ${total} 条`;
32 | };
33 |
--------------------------------------------------------------------------------
/components/data-table/demo/basic.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 0
3 | title: 基本
4 | ---
5 |
6 | 只传递 `columns` 和 `url` 这两个必要属性,以及一些必要的后端API请求参数。会展示默认的分页信息,以及通用的 「排序、搜索、过滤」等功能。
7 |
8 |
9 | ```jsx
10 | import { DataTable as Table } from 'antd-advanced';
11 | import { Button, Tabs, DatePicker, Form } from 'antd';
12 | import moment from 'moment';
13 |
14 |
15 |
16 | const listUrl = 'https://service-dmgco1kc-1302187237.gz.apigw.tencentcs.com/release/table_api';
17 |
18 | const BasicExample: React.FC = () => {
19 | const columns = [
20 | {
21 | title: 标题
,
22 | title: '标题',
23 | dataIndex: 'title',
24 | apiSearch: {
25 | name: 'title',
26 | },
27 | },
28 | {
29 | title: '缩略图',
30 | dataIndex: 'image',
31 | render: (text, record, index) => (
32 |
33 | {record.thumbnail_pic_s ? (
34 |

35 | ) : (
36 | '暂无图片'
37 | )}
38 |
39 | ),
40 | },
41 | {
42 | title: '分类',
43 | dataIndex: 'category',
44 | filters: [
45 | { text: '科技', value: '科技' },
46 | { text: '头条', value: '头条' },
47 | { text: '社会', value: '社会' },
48 | { text: '财经', value: '财经' },
49 | ],
50 | apiFilter: {
51 | name: 'category',
52 | callback: arr => {
53 | return arr.join(',');
54 | },
55 | },
56 | },
57 | {
58 | title: '作者',
59 | dataIndex: 'author_name',
60 | },
61 | {
62 | title: '发布日期',
63 | dataIndex: 'date',
64 | sorter: true,
65 | apiSorter: {
66 | name: 'date_order',
67 | ascend: 'asc',
68 | descend: 'desc',
69 | },
70 | },
71 | ];
72 | return (
73 | {
80 | // data
81 | // total
82 | return data;
83 | }}
84 | leftHeader={}
85 | pageParams={{
86 | curPageName: 'cur_page',
87 | pageSizeName: 'page_size',
88 | startPage: 1,
89 | }}
90 | searchParams={{
91 | apiName: 'search',
92 | placeholder: '模糊搜索表格内容(多个关键词请用空格分隔。如:key1 key2)',
93 | }}
94 | />
95 | );
96 | }
97 |
98 | ReactDOM.render(
99 |
100 |
101 |
,
102 | mountNode,
103 | );
104 | ```
105 |
--------------------------------------------------------------------------------
/components/data-table/demo/query.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 2
3 | title: query 模式
4 | ---
5 |
6 | 只传递 `columns` 和 `url` 这两个必要属性,以及一些必要的后端API请求参数。会展示默认的分页信息,以及通用的 「排序、搜索、过滤」等功能。
7 |
8 |
9 | ```jsx
10 | import { DataTable } from 'antd-advanced';
11 | import { Button } from 'antd';
12 | import moment from 'moment';
13 |
14 | const columns = [
15 | {
16 | title: '标题',
17 | dataIndex: 'title',
18 | },
19 | {
20 | title: '缩略图',
21 | dataIndex: 'image',
22 | render: (text, record, index) => (
23 |
24 | {record.thumbnail_pic_s ? (
25 |

26 | ) : (
27 | '暂无图片'
28 | )}
29 |
30 | ),
31 | },
32 | {
33 | title: '分类',
34 | dataIndex: 'category',
35 | },
36 | {
37 | title: '作者',
38 | dataIndex: 'author_name',
39 | },
40 | {
41 | title: '发布日期',
42 | dataIndex: 'date',
43 | sorter: true,
44 | apiSorter: {
45 | name: 'date_order',
46 | ascend: 'asc',
47 | descend: 'desc',
48 | },
49 | },
50 | ];
51 |
52 | const queryFormColumns = [
53 | {
54 | type: 'input',
55 | title: '标题',
56 | initialValue: '40',
57 | dataIndex: 'title',
58 | isInputPressEnterCallSearch: true,
59 | },
60 | {
61 | type: 'select',
62 | title: '分类',
63 | dataIndex: 'category',
64 | selectMode: 'multiple',
65 | options: [
66 | {
67 | title: '科技',
68 | value: '科技',
69 | },
70 | {
71 | title: '头条',
72 | value: '头条',
73 | },
74 | {
75 | title: '社会',
76 | value: '社会',
77 | },
78 | {
79 | title: '财经',
80 | value: '财经',
81 | },
82 | ],
83 | },
84 | ];
85 |
86 | const listUrl = 'https://service-dmgco1kc-1302187237.gz.apigw.tencentcs.com/release/table_api';
87 |
88 | const BasicExample: React.FC = () => {
89 |
90 | return (
91 |
95 | {
104 | // data
105 | // total
106 | return data;
107 | }}
108 | isQuerySearchOnChange={false}
109 | reloadBtnPos="left"
110 | reloadBtnType="btn"
111 | filterType="none"
112 | leftHeader={}
113 | pageParams={{
114 | curPageName: 'cur_page',
115 | pageSizeName: 'page_size',
116 | startPage: 1,
117 | }}
118 | />
119 |
120 | );
121 | }
122 |
123 | ReactDOM.render(
124 |
125 |
126 |
,
127 | mountNode,
128 | );
129 | ```
130 |
--------------------------------------------------------------------------------
/components/data-table/demo/queryCompact.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 3
3 | title: query 紧凑模式
4 | ---
5 |
6 | 只传递 `columns` 和 `url` 这两个必要属性,以及一些必要的后端API请求参数。会展示默认的分页信息,以及通用的 「排序、搜索、过滤」等功能。
7 |
8 |
9 | ```jsx
10 | import { DataTable } from 'antd-advanced';
11 | import { Button } from 'antd';
12 | import moment from 'moment';
13 |
14 | const columns = [
15 | {
16 | title: '标题',
17 | dataIndex: 'title',
18 | },
19 | {
20 | title: '缩略图',
21 | dataIndex: 'image',
22 | render: (text, record, index) => (
23 |
24 | {record.thumbnail_pic_s ? (
25 |

26 | ) : (
27 | '暂无图片'
28 | )}
29 |
30 | ),
31 | },
32 | {
33 | title: '分类',
34 | dataIndex: 'category',
35 | },
36 | {
37 | title: '作者',
38 | dataIndex: 'author_name',
39 | },
40 | {
41 | title: '发布日期',
42 | dataIndex: 'date',
43 | sorter: true,
44 | apiSorter: {
45 | name: 'date_order',
46 | ascend: 'asc',
47 | descend: 'desc',
48 | },
49 | },
50 | ];
51 |
52 | const queryFormColumns = [
53 | {
54 | type: 'input',
55 | title: '标题',
56 | dataIndex: 'title',
57 | },
58 | {
59 | type: 'input',
60 | title: '出版社',
61 | dataIndex: 'appUuids',
62 | },
63 | {
64 | type: 'select',
65 | title: '分类',
66 | initialValue: ['科技'],
67 | dataIndex: 'category',
68 | selectMode: 'multiple',
69 | options: [
70 | {
71 | title: '科技',
72 | value: '科技',
73 | },
74 | {
75 | title: '头条',
76 | value: '头条',
77 | },
78 | {
79 | title: '社会',
80 | value: '社会',
81 | },
82 | {
83 | title: '财经',
84 | value: '财经',
85 | },
86 | ],
87 | },
88 | ];
89 |
90 |
91 | const listUrl = 'https://service-dmgco1kc-1302187237.gz.apigw.tencentcs.com/release/table_api';
92 |
93 | const BasicExample: React.FC = () => {
94 | const [selectedRowKeys, setSelectedRowKeys] = React.useState([]);
95 | const onSelectChange = newKeys => {
96 | setSelectedRowKeys(newKeys);
97 | };
98 | const rowSelection = {
99 | selectedRowKeys,
100 | onChange: onSelectChange,
101 | };
102 | return (
103 | {
111 | // data
112 | // total
113 | return data;
114 | }}
115 | reloadBtnPos="left"
116 | reloadBtnType="btn"
117 | filterType="none"
118 | leftHeader={}
119 | pageParams={{
120 | curPageName: 'cur_page',
121 | pageSizeName: 'page_size',
122 | startPage: 1,
123 | }}
124 | />
125 | );
126 | }
127 |
128 | ReactDOM.render(
129 |
130 |
131 |
,
132 | mountNode,
133 | );
134 | ```
135 |
--------------------------------------------------------------------------------
/components/data-table/index.tsx:
--------------------------------------------------------------------------------
1 | import './style/index.less';
2 | import DataTable from './DataTable';
3 |
4 | export default DataTable;
5 |
--------------------------------------------------------------------------------
/components/data-table/style/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.less';
2 |
--------------------------------------------------------------------------------
/components/descriptions/Descriptions.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 |
4 | export interface IDescriptionItem {
5 | title?: string | number | React.ReactNode;
6 | content?: string | number | React.ReactNode;
7 | mainTitle?: string | number | React.ReactNode;
8 | }
9 |
10 | declare const AlignTypes: ['left', 'right'];
11 | export declare type AlignType = typeof AlignTypes[number];
12 |
13 | export interface IDescriptionsProps {
14 | prefixCls?: string;
15 | className?: string;
16 | bordered?: boolean;
17 | style?: React.CSSProperties;
18 | title?: string | React.ReactNode;
19 | titleStyle?: React.CSSProperties;
20 | showColon?: boolean;
21 | titleWidth?: number | string;
22 | titleAlign?: AlignType;
23 | itemTitleStyle?: React.CSSProperties;
24 | itemContentStyle?: React.CSSProperties;
25 | dataSource: IDescriptionItem[];
26 | }
27 |
28 | const Descriptions: React.FC = (props) => {
29 | const prefixCls = `${props.prefixCls || 'dantd'}-desc`;
30 | const {
31 | itemTitleStyle = {},
32 | itemContentStyle = {},
33 | className,
34 | bordered = false,
35 | style,
36 | titleAlign = 'right',
37 | showColon = false,
38 | titleStyle,
39 | } = props;
40 | const descClassName = classNames(prefixCls, bordered && `${prefixCls}-bordered`, className);
41 | const wrapperStyle = {
42 | ...style,
43 | };
44 | const defaultTitleWidth = 80;
45 | const mainTitleStyle = {
46 | width: props.titleWidth ? props.titleWidth : defaultTitleWidth,
47 | textAlign: titleAlign,
48 | ...titleStyle,
49 | };
50 |
51 | // const itemTitleClassName = classNames(
52 | // `${prefixCls}-item-title`,
53 | // showColon && `${prefixCls}-item-title-colon`,
54 | // );
55 |
56 | const itemTitleClassName = `${prefixCls}-item-title`;
57 |
58 | const localItemTitleStyle = {
59 | width: props.titleWidth ? props.titleWidth : defaultTitleWidth,
60 | textAlign: titleAlign,
61 | ...itemTitleStyle,
62 | };
63 |
64 | const dataSource = props.dataSource ? props.dataSource : [];
65 |
66 | return (
67 |
68 | {dataSource.length === 0 && '- -'}
69 | {props.title && (
70 |
71 | {props.title}
72 |
73 | )}
74 | {dataSource.map((dataItem, dataItemIdx) => {
75 | return dataItem.mainTitle ? (
76 |
77 |
78 | {dataItem.mainTitle}
79 |
80 |
81 | ) : (
82 |
83 |
84 | {dataItem.title ? dataItem.title : '- -'}
85 | {showColon && ':'}
86 |
87 |
88 | {dataItem.content ? dataItem.content : '- -'}
89 |
90 |
91 | );
92 | })}
93 |
94 | );
95 | };
96 |
97 | export default Descriptions;
98 |
--------------------------------------------------------------------------------
/components/descriptions/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { create, act } from 'react-test-renderer';
3 | import Descriptions from '../Descriptions';
4 |
5 | const data = [
6 | {
7 | title: '【番剧推荐】为寻你,吾宁赴深渊——《龙的牙医》',
8 | content:
9 | '冬天就是要看些温暖清新的剧才能治愈一下疲惫的心啊。《龙的牙医》这部短片动漫是小编无意中发现的,画风很是清新温暖,很适合在冬天观赏。故事背景是在一个与龙签订了契约的国度,有一群人作为龙的牙医为龙清理牙菌。动画讲述了男主贝尔在龙牙里复活,与身为牙医的野野子相遇,两人一同作为牙医,守护龙牙的故事。',
10 | },
11 | {
12 | title: '《紫罗兰永恒花园外传:永远与自动手记人偶》值得看吗?',
13 | content:
14 | '《紫罗兰永恒花园外传:永远与自动手记人偶》已于1月10日登录国内影院。考虑到影片国内刚刚上映没几天,本篇推荐并不会涉及太多剧情上的...',
15 | },
16 | ];
17 |
18 | test('列表渲染正常', () => {
19 | let comp: any;
20 |
21 | act(() => {
22 | comp = create();
23 | });
24 |
25 | expect(comp.toJSON()?.children.length).toEqual(data.length);
26 | });
27 |
28 | test('空数据渲染正常', () => {
29 | let comp: any;
30 |
31 | act(() => {
32 | comp = create();
33 | });
34 |
35 | expect(comp.toJSON()?.children).toEqual(['- -']);
36 | });
37 |
--------------------------------------------------------------------------------
/components/descriptions/demo/basic.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 0
3 | title: 基本
4 | ---
5 |
6 | 展示一个简单的描述列表,可以为其设置 `title`。
7 |
8 | ```jsx
9 | import { Descriptions } from 'antd-advanced';
10 | import { Row, Col, Card, Typography, Badge } from 'antd';
11 |
12 | export const data1 = [
13 | {
14 | title: '【番剧推荐】为寻你,吾宁赴深渊——《龙的牙医》',
15 | content: '呐,龙是用牙齿哭泣的啊——《龙的牙医》',
16 | },
17 | {
18 | title: '《紫罗兰永恒花园外传:永远与自动手记人偶》值得看吗?',
19 | content:
20 | '《紫罗兰永恒花园外传:永远与自动手记人偶》已于1月10日登录国内影院。考虑到影片国内刚刚上映没几天,本篇推荐并不会涉及太多剧情上的...',
21 | },
22 | ];
23 |
24 | export const data2 = [
25 | {
26 | title: 'content-length',
27 | content: 4,
28 | },
29 | {
30 | title: 'content-type',
31 | content: 'text/plain',
32 | },
33 | {
34 | title: 'date',
35 | content: 'Tue,03 Mar 2020 13:0',
36 | },
37 | {
38 | title: 'x-fc-code-checksum',
39 | content: '28283487410827349081',
40 | },
41 | {
42 | title: 'x-fc-invocation-dura',
43 | content: '60',
44 | },
45 | {
46 | title: 'x-fc-max—memory-usag',
47 | content: 7.65,
48 | },
49 | {
50 | title: 'x-fc-request-id',
51 | content: '342343434-3434534534',
52 | },
53 | ];
54 |
55 | ReactDOM.render(
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | , mountNode);
69 | ```
70 |
--------------------------------------------------------------------------------
/components/descriptions/demo/changeMode.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 3
3 | title: 模式切换
4 | ---
5 |
6 | 修改 `titleAlign`、`showColon`、`titleWidth` 参数,观察组件的变化。
7 |
8 | ```jsx
9 | import { useState } from 'react';
10 | import { Descriptions, QueryForm, EmptyLine } from 'antd-advanced';
11 | import { Row, Col, Card, Typography, Badge, InputNumber, Switch } from 'antd';
12 |
13 | export const data = [
14 | {
15 | title: '发生时间',
16 | content: '2020-09-20 19:00:00',
17 | },
18 | {
19 | title: '维度',
20 | content: '北京 西二旗 后厂村',
21 | },
22 | {
23 | title: '触发算子',
24 | content: '2020-09-20 19:00:00',
25 | },
26 | {
27 | title: '现场值',
28 | content: (
29 |
35 | 2020-09-20 19:00:00 900 800 700 600
36 | 2020-09-20 19:00:00 900 800
37 | 2020-09-20 19:00:00 900 800
38 | 2020-09-20 19:00:00 900 800
39 | 2020-09-20 19:00:00 900 800
40 |
41 | )
42 | },
43 | ];
44 |
45 | const queryColumns = [{
46 | type: 'select',
47 | title: '标题对齐(titleAlign)',
48 | dataIndex: 'titleAlign',
49 | initialValue: 'right',
50 | options: [
51 | {
52 | title: '右对齐',
53 | value: 'left',
54 | },
55 | {
56 | title: '左对齐',
57 | value: 'right',
58 | }
59 | ],
60 | },{
61 | type: 'custom',
62 | title: '是否展示冒号(showColon)',
63 | dataIndex: 'showColon',
64 | valuePropName: 'checked',
65 | initialValue: true,
66 | component: (
67 |
68 | )
69 | },{
70 | type: 'custom',
71 | title: '标题的宽度(titleWidth)',
72 | dataIndex: 'titleWidth',
73 | initialValue: 80,
74 | component: (
75 |
76 | )
77 | }]
78 |
79 | const Demo: React.FC = () => {
80 | const [query, setQuery] = useState({
81 | mode: "full",
82 | showColon: true,
83 | titleWidth: 80,
84 | });
85 | const handleQueryChange = queryValue => {
86 | setQuery(queryValue);
87 | };
88 | return (
89 |
93 |
选择Descriptions属性
94 |
99 |
100 |
107 |
113 |
114 |
115 | );
116 | }
117 |
118 | ReactDOM.render(
119 |
120 |
121 |
,
122 | mountNode,
123 | );
124 | ```
125 |
--------------------------------------------------------------------------------
/components/descriptions/demo/list.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 1
3 | title: 列表
4 | ---
5 |
6 | 以列表的方式使用组件,并增加边框
7 |
8 | ```jsx
9 | import { Descriptions } from 'antd-advanced';
10 | import { Row, Col, Card, Typography, Badge } from 'antd';
11 | const { Paragraph } = Typography;
12 | export const data = [
13 | {
14 | title: '场景',
15 | content: '线上,美东 01 - us01',
16 | },
17 | {
18 | title: 'VIP',
19 | content: '192.168.0.0',
20 | },
21 | {
22 | title: 'VIP 端口',
23 | content: 1080,
24 | },
25 | {
26 | title: '映射关系',
27 | content: '1080 : 35002',
28 | },
29 | {
30 | title: '域名类型',
31 | content: '办公室内网域名',
32 | },
33 | {
34 | title: '域名地址',
35 | content: (
36 |
37 |
38 | {'http://xxx.com/'}
39 |
40 |
41 | ),
42 | },
43 | {
44 | title: '域名端口',
45 | content: 8080,
46 | },
47 | ];
48 |
49 |
50 | ReactDOM.render(
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | , mountNode);
66 | ```
67 |
--------------------------------------------------------------------------------
/components/descriptions/demo/mainTitle.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 2
3 | title: 设置小标题
4 | ---
5 |
6 | `dataSource` 中,除了可以使用 `title + content`,还可以使用 `mainTitle` 设置小标题。
7 |
8 | ```jsx
9 | import { Descriptions } from 'antd-advanced';
10 | import { Row, Col, Card, Typography, Badge } from 'antd';
11 | const { Paragraph } = Typography;
12 | export const data1 = [
13 | {
14 | title: '节点',
15 | content: 'xxx.xxx.xxx',
16 | },
17 | {
18 | title: '名称',
19 | content: 'test-demo',
20 | },
21 | {
22 | title: '状态',
23 | content: ,
24 | },
25 | {
26 | title: '备注信息',
27 | content: 'test',
28 | },
29 | ];
30 |
31 | export const data2 = [
32 | {
33 | title: '占位',
34 | content: 'service 接口上线',
35 | },
36 | {
37 | title: '流量类型',
38 | content: 高峰期 星期五 00:00 - 00:00,
39 | },
40 | {
41 | mainTitle: '更改配置',
42 | },
43 | {
44 | title: '环境变量修改前',
45 | content: 'QQWWERTTQWQW:2',
46 | },
47 | {
48 | title: '环境变量修改后',
49 | content: 'QQWWERTTQWQWTTTT:2',
50 | },
51 | {
52 | title: '服务等级',
53 | content: P3,
54 | },
55 | {
56 | title: '备注',
57 | content: (
58 |
59 | {' '}测试 - 测试环境名称{' '}
60 |
61 | ),
62 | },
63 | ];
64 |
65 | ReactDOM.render(
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | , mountNode);
75 | ```
76 |
--------------------------------------------------------------------------------
/components/descriptions/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 组件
3 | type: 数据展示
4 | title: Descriptions
5 | subtitle: 描述列表
6 | cols: 1
7 | ---
8 |
9 | ## 何时使用
10 |
11 | 用于展示一个简单的描述列表
12 |
13 | ## API
14 |
15 | ### Descriptions
16 |
17 | | 参数 | 说明 | 类型 | 默认值 |
18 | | :--------- | :------------- | :------------------ | :----- |
19 | | title | 标题 | string | - |
20 | | titleStyle | 标题 | string | - |
21 | | titleWidth | 自定义标题的样式 | React.CSSProperties | - |
22 | | titleAlign | 标题对齐方式 | `['left', 'right']` | `'right'` |
23 | | dataSource | 需要展示的信息 | Descriptions.Item[] | - |
24 | | itemTitleStyle | 自定义详情标题的样式 | React.CSSProperties | - |
25 | | itemContentStyle | 自定义详情内容的样式 | React.CSSProperties | - |
26 | | bordered | 是否需要边框 | boolean | false |
27 | | style | 自定义组件容器的样式 | React.CSSProperties | - |
28 | | showColon | 是否展示标题后面的冒号 | boolean | false |
29 |
30 | ### Descriptions.Item
31 |
32 | | 参数 | 说明 | 类型 | 默认值 |
33 | | :------ | :------- | :----------------- | :----- |
34 | | title | 详情标题 | string \| number \| React.ReactNode | - |
35 | | content | 详情信息 | string \| number \| React.ReactNode | - |
36 | | mainTitle | 详情小标题 | string \| number \| React.ReactNode | - |
37 |
--------------------------------------------------------------------------------
/components/descriptions/index.tsx:
--------------------------------------------------------------------------------
1 | import './style/index.less';
2 | import Descriptions from './Descriptions';
3 |
4 | export default Descriptions;
5 |
--------------------------------------------------------------------------------
/components/descriptions/style/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/index.less';
2 | @import '~antd/es/style/themes/default.less';
3 |
4 | @dantd-desc-prefix-cls: ~'@{dantd-prefix}-desc';
5 |
6 | .@{dantd-desc-prefix-cls} {
7 | position: relative;
8 | display: flex;
9 | flex-direction: column;
10 | overflow: hidden;
11 | border-radius: 2px;
12 | padding: 15px 20px;
13 | &-bordered {
14 | border: @border-width-base @border-style-base @border-color-split;
15 | }
16 |
17 | &-title {
18 | margin-bottom: 15px;
19 | width: 80px;
20 | text-align: right;
21 | word-break: break-word;
22 | font-weight: bold;
23 | font-size: 16px;
24 | color: @heading-color;
25 | }
26 |
27 | &-item {
28 | display: flex;
29 | flex-wrap: wrap;
30 | margin-bottom: 4px;
31 | font-size: 12px;
32 |
33 | &-main-title {
34 | position: relative;
35 | flex: 0 1 auto;
36 | width: 80px;
37 | word-break: break-word;
38 | text-align: right;
39 | line-height: 2;
40 | font-weight: bold;
41 | color: @heading-color;
42 | font-size: 13px;
43 | > div,p,span {
44 | margin-bottom: 0 !important;
45 | font-size: 13px;
46 | }
47 | }
48 |
49 | &-title {
50 | position: relative;
51 | flex: 0 1 auto;
52 | width: 80px;
53 | margin-right: 12px;
54 | word-break: break-word;
55 | text-align: right;
56 | line-height: 1.8;
57 | font-weight: 400;
58 | color: @heading-color;
59 |
60 | &-colon {
61 | &:after {
62 | content: ':';
63 | position: absolute;
64 | top: -0.5px;
65 | margin: 0 8px 0 4px;
66 | }
67 | }
68 | > div,p,span {
69 | margin-bottom: 0 !important;
70 | font-size: 12px;
71 | }
72 | }
73 |
74 | &-content {
75 | flex: 1;
76 | width: auto;
77 | word-break: break-word;
78 | line-height: 1.8;
79 | color: @text-color;
80 |
81 | > div,p,span {
82 | margin-bottom: 0 !important;
83 | font-size: 12px;
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/components/descriptions/style/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.less';
2 |
--------------------------------------------------------------------------------
/components/empty-line/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { create, act } from 'react-test-renderer';
3 | import EmptyLine from '../empty-line';
4 |
5 | test('默认高度渲染正常', () => {
6 | let emptyLine: any;
7 |
8 | act(() => {
9 | emptyLine = create();
10 | });
11 |
12 | expect(emptyLine.toJSON()?.props.style).toEqual(
13 | expect.objectContaining({
14 | height: 20,
15 | }),
16 | );
17 | });
18 |
19 | test('自定义高度渲染正常', () => {
20 | let emptyLine: any;
21 |
22 | act(() => {
23 | emptyLine = create();
24 | });
25 |
26 | expect(emptyLine.toJSON()?.props.style).toEqual(
27 | expect.objectContaining({
28 | height: 30,
29 | }),
30 | );
31 | });
32 |
--------------------------------------------------------------------------------
/components/empty-line/demo/basic.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 0
3 | title: 基本
4 | ---
5 |
6 | 空行可以不传参数直接使用。增加文字或者组件之间的上下间距
7 |
8 | ```jsx
9 | import { EmptyLine } from 'antd-advanced';
10 |
11 | ReactDOM.render(
12 |
13 | 第一行文字
14 |
15 | 第二行文字
16 |
,
17 | mountNode,
18 | );
19 | ```
20 |
--------------------------------------------------------------------------------
/components/empty-line/demo/withHeight.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 1
3 | title: 自定义高度
4 | ---
5 |
6 | 自定义空行的高度
7 |
8 | ```jsx
9 | import { EmptyLine } from 'antd-advanced';
10 |
11 | ReactDOM.render(
12 |
13 | 第一行文字
14 |
15 | 第二行文字
16 |
,
17 | mountNode,
18 | );
19 | ```
20 |
--------------------------------------------------------------------------------
/components/empty-line/empty-line.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 |
4 | export interface IEmptyLineProps {
5 | prefixCls?: string;
6 | className?: string;
7 | style?: React.CSSProperties;
8 | height?: number;
9 | }
10 |
11 | const EmptyLine = ({ height = 20, prefixCls = 'dantd', className, style }: IEmptyLineProps) => {
12 | const emptyLineClassName = classNames(`${prefixCls}-empty-line`, className);
13 | const emptyLineStyle = {
14 | height,
15 | ...style,
16 | };
17 |
18 | return ;
19 | };
20 |
21 | export default EmptyLine;
22 |
--------------------------------------------------------------------------------
/components/empty-line/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 组件
3 | type: 数据展示
4 | title: EmptyLine
5 | subtitle: 空行
6 | ---
7 |
8 | ## 何时使用
9 |
10 | 需要在文字,或者组件之间添加一个固定高度的空隙
11 |
12 | ## API
13 |
14 | | 参数 | 说明 | 类型 | 默认值 |
15 | | :----- | :--------- | :------ | :----- |
16 | | height | 空行的高度 | number? | 20 |
17 |
--------------------------------------------------------------------------------
/components/empty-line/index.tsx:
--------------------------------------------------------------------------------
1 | import './style/index.less';
2 | import EmptyLine from './empty-line';
3 |
4 | export default EmptyLine;
5 |
--------------------------------------------------------------------------------
/components/empty-line/style/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/index.less';
2 |
3 | @dantd-empty-line-prefix-cls: ~'@{dantd-prefix}-empty-line';
4 |
5 | .@{dantd-empty-line-prefix-cls} {
6 | width: 100%;
7 | height: 20px;
8 | }
9 |
--------------------------------------------------------------------------------
/components/empty-line/style/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.less';
2 |
--------------------------------------------------------------------------------
/components/index.tsx:
--------------------------------------------------------------------------------
1 | export { default as EmptyLine } from './empty-line';
2 | export { default as Descriptions } from './descriptions';
3 | export { default as BasicTable } from './basic-table';
4 | export { default as DataTable } from './data-table';
5 | export { default as Switch } from './switch';
6 | export { default as Code } from './code';
7 | export { default as VirtualSelect } from './virtual-select';
8 | export { default as ColorSelect } from './color-select';
9 | export { default as BasicCard } from './card';
10 |
11 | // forms
12 | export { default as BasicFormItems } from './basic-form-items';
13 | export { default as QueryForm } from './query-form';
14 |
15 | // hooks
16 | export { default as useDebounce } from './use-debounce';
17 | export { default as useDynamicList } from './use-dynamic-list';
18 | export { default as useAsync } from './use-async';
19 | export { default as useAsyncFn } from './use-async-fn';
20 | export { default as useAsyncRetry } from './use-async-retry';
21 | export { default as useMountedState } from './use-mounted-state';
22 | export { default as useInterval } from './use-interval';
23 | export { default as useTimeout } from './use-timeout';
24 | export { default as useDeepCompareEffect } from './use-deep-compare-effect';
25 |
26 | // creat hook factory
27 | export { default as createGlobalState } from './create-global-state';
28 | export { default as createStateContext } from './create-state-context';
29 | export { default as createReducerContext } from './create-reducer-context';
30 |
31 | // localProvider
32 | export { default as IntlProvider } from './locale-provider';
33 |
--------------------------------------------------------------------------------
/components/locale-provider/context.tsx:
--------------------------------------------------------------------------------
1 | import { createContext } from 'react';
2 | import TRANSLATIONS from '../locale';
3 |
4 | interface IContextProps {
5 | t: (key: any) => any;
6 | }
7 |
8 | const locale = 'en-US';
9 |
10 | const IntlContext = createContext({
11 | t: (key) => TRANSLATIONS[locale][key],
12 | });
13 |
14 | export default IntlContext;
15 |
--------------------------------------------------------------------------------
/components/locale-provider/index.tsx:
--------------------------------------------------------------------------------
1 | import IntlProvider from './provider';
2 |
3 | export { default as IntlProvider } from './provider';
4 | export { default as useIntl } from './useIntl';
5 | export { default as withIntl } from './withIntl';
6 |
7 | export default IntlProvider;
8 |
--------------------------------------------------------------------------------
/components/locale-provider/provider.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import IntlContext from './context';
3 | import TRANSLATIONS from '../locale';
4 |
5 | interface IIntlProps {
6 | locale: 'zh-CN' | 'en-US' | string;
7 | children: React.ReactNode;
8 | }
9 |
10 | const IntlProvider = (props: IIntlProps) => {
11 | const locale = props.locale || 'en-US';
12 | const i18n = {
13 | t: (key) => TRANSLATIONS[locale][key],
14 | };
15 |
16 | return {props.children};
17 | };
18 |
19 | export default IntlProvider;
20 |
--------------------------------------------------------------------------------
/components/locale-provider/useIntl.tsx:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import IntlContext from './context';
3 |
4 | function useIntl(): any {
5 | const i18n = useContext(IntlContext);
6 | return i18n;
7 | }
8 |
9 | export default useIntl;
10 |
--------------------------------------------------------------------------------
/components/locale-provider/withIntl.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import IntlContext from './context';
3 |
4 | export const withIntl = () => {
5 | return (WrappedComponent) => {
6 | const ComponentWithIntl = (props) => (
7 |
8 | {(i18n) => }
9 |
10 | );
11 | return ComponentWithIntl;
12 | };
13 | };
14 |
15 | export default withIntl;
16 |
--------------------------------------------------------------------------------
/components/locale/en_US.tsx:
--------------------------------------------------------------------------------
1 | export default {
2 | back: 'Back',
3 | 'table.search.placeholder':
4 | 'Fuzzy search table content (multiple keywords separated by Spaces. Such as: key1 key2)',
5 | 'table.sort.ascend': 'Ascend',
6 | 'table.sort.descend': 'Descend',
7 | 'table.total.prefix': 'Total',
8 | 'table.total.suffix': 'Items',
9 | 'table.select.num': 'Selected: ',
10 | 'table.filter.search.placeholder': 'Please enter keywords',
11 | 'table.filter.search.btn.ok': 'Search',
12 | 'table.filter.search.btn.cancel': 'Clear',
13 | 'table.filter.header.title': 'Filtration Conditions:',
14 | 'table.filter.header.search': ': Search',
15 | 'table.filter.header.filter': ': Filter',
16 | 'table.filter.header.btn.clear': 'Clear',
17 | 'form.item.key.title': 'Custom Key',
18 | 'form.item.value.title': 'Custom Value',
19 | 'form.detail.nodata': 'No Data',
20 | 'form.item.key.placeholder': 'Please enter key',
21 | 'form.item.value.placeholder': 'Please enter value',
22 | 'form.item.add': 'Add Custom Parameters',
23 | 'form.placeholder.prefix': 'Please enter',
24 | 'form.selectplaceholder.prefix': 'Please select',
25 | 'queryform.reset': 'Clear',
26 | 'queryform.search': 'Search',
27 | 'queryform.collapsed': 'Collapsed',
28 | 'queryform.expand': 'Expand',
29 | 'color.placeholder': 'Please select color',
30 | };
31 |
--------------------------------------------------------------------------------
/components/locale/index.tsx:
--------------------------------------------------------------------------------
1 | import zh_CN from './zh_CN';
2 | import en_US from './en_US';
3 |
4 | export default {
5 | 'zh-CN': zh_CN,
6 | 'en-US': en_US,
7 | };
8 |
--------------------------------------------------------------------------------
/components/locale/zh_CN.tsx:
--------------------------------------------------------------------------------
1 | export default {
2 | back: '返回',
3 | 'table.search.placeholder': '模糊搜索表格内容(多个关键词请用空格分隔。如:key1 key2)',
4 | 'table.sort.ascend': '升序',
5 | 'table.sort.descend': '降序',
6 | 'table.total.prefix': '共',
7 | 'table.total.suffix': '条',
8 | 'table.select.num': '已选择: ',
9 | 'table.filter.search.placeholder': '请输入要搜索的内容',
10 | 'table.filter.search.btn.ok': '搜索',
11 | 'table.filter.search.btn.cancel': '清空',
12 | 'table.filter.header.title': '过滤条件:',
13 | 'table.filter.header.search': ':搜索',
14 | 'table.filter.header.filter': ':过滤',
15 | 'table.filter.header.btn.clear': '清空',
16 | 'form.item.key.title': '自定义Key',
17 | 'form.item.value.title': '自定义Value',
18 | 'form.detail.nodata': '暂无数据',
19 | 'form.item.key.placeholder': '请输入Key',
20 | 'form.item.value.placeholder': '请输入Value',
21 | 'form.item.add': '增加自定义参数',
22 | 'form.placeholder.prefix': '请输入',
23 | 'form.selectplaceholder.prefix': '请选择',
24 | 'queryform.reset': '重置',
25 | 'queryform.search': '查询',
26 | 'queryform.collapsed': '收起',
27 | 'queryform.expand': '展开',
28 | 'color.placeholder': '请选择颜色',
29 | };
30 |
--------------------------------------------------------------------------------
/components/query-form/demo/basic.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 0
3 | title: 基本
4 | ---
5 |
6 | 和 `Table` 组件类似,传入 `columns` 属性,会自动生成 `QueryForm`。
7 | 监听 `onChange` 属性使用起返回值进行 `query` 查询。
8 |
9 | ```jsx
10 | import { useState } from 'react';
11 | import { QueryForm } from 'antd-advanced';
12 | import { InputNumber } from 'antd';
13 |
14 | const columns = [
15 | {
16 | type: 'input',
17 | title: '实例名称',
18 | dataIndex: 'name',
19 | },
20 | {
21 | type: 'select',
22 | title: '报警等级',
23 | dataIndex: 'level',
24 | options: [
25 | {
26 | title: '全部',
27 | value: 'all',
28 | },
29 | {
30 | title: 'P0',
31 | value: 'p0',
32 | },
33 | {
34 | title: 'P1',
35 | value: 'p1',
36 | },
37 | {
38 | title: 'P2',
39 | value: 'p2',
40 | },
41 | ],
42 | },
43 | {
44 | type: 'select',
45 | title: '任务状态',
46 | dataIndex: 'status',
47 | selectMode: 'multiple',
48 | options: [
49 | {
50 | title: '进行中',
51 | value: 'processing',
52 | },
53 | {
54 | title: '成功',
55 | value: 'success',
56 | },
57 | {
58 | title: '失败',
59 | value: 'fail',
60 | },
61 | ],
62 | },
63 | {
64 | type: 'custom',
65 | title: '机器数量',
66 | dataIndex: 'number',
67 | component: (
68 |
74 | ),
75 | },
76 | ];
77 |
78 | const Demo: React.FC = () => {
79 | const [result, setResult] = useState({});
80 | const handleChange = queryValue => {
81 | setResult(queryValue);
82 | };
83 |
84 | return (
85 |
86 |
90 |
结果:
91 |
{JSON.stringify(result)}
92 |
93 | );
94 | }
95 |
96 | ReactDOM.render(
97 |
98 |
99 |
,
100 | mountNode,
101 | );
102 | ```
103 |
--------------------------------------------------------------------------------
/components/query-form/demo/customCols.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 2
3 | title: 自定义Column长度
4 | ---
5 |
6 | 自定义 Column 的长度等样式。但是此时收起时,无法计算需要隐藏的 Column 的宽度,现在收起时默认只展示一项,可以通过 `columnStyleHideNumber` 设置
7 |
8 | ```jsx
9 | import { useState } from 'react';
10 | import { QueryForm } from 'antd-advanced';
11 | import { InputNumber } from 'antd';
12 |
13 | const columns = [
14 | {
15 | type: 'input',
16 | title: '实例名称',
17 | dataIndex: 'name',
18 | initialValue: '机器3011',
19 | isInputPressEnterCallSearch: true,
20 | colStyle: {
21 | width: '600px'
22 | }
23 | },
24 | {
25 | type: 'select',
26 | title: '报警等级',
27 | dataIndex: 'level',
28 | initialValue: 'all',
29 | options: [
30 | {
31 | title: '全部',
32 | value: 'all',
33 | },
34 | {
35 | title: 'P0',
36 | value: 'p0',
37 | },
38 | {
39 | title: 'P1',
40 | value: 'p1',
41 | },
42 | {
43 | title: 'P2',
44 | value: 'p2',
45 | },
46 | ],
47 | },
48 | {
49 | type: 'select',
50 | title: '任务状态',
51 | dataIndex: 'status',
52 | selectMode: 'multiple',
53 | initialValue: 'success',
54 | options: [
55 | {
56 | title: '进行中',
57 | value: 'processing',
58 | },
59 | {
60 | title: '成功',
61 | value: 'success',
62 | },
63 | {
64 | title: '失败',
65 | value: 'fail',
66 | },
67 | ],
68 | },
69 | {
70 | type: 'custom',
71 | title: '机器数量',
72 | dataIndex: 'number',
73 | initialValue: 10,
74 | component: (
75 |
81 | ),
82 | },
83 | ];
84 |
85 | const Demo: React.FC = () => {
86 | const [result, setResult] = useState({});
87 |
88 | const handleChange = queryValue => {
89 | setResult(queryValue);
90 | };
91 |
92 | const handleSearch = queryValue => {
93 | setResult(queryValue);
94 | };
95 |
96 | return (
97 |
98 |
104 |
结果:
105 |
{JSON.stringify(result)}
106 |
107 | );
108 | }
109 |
110 | ReactDOM.render(
111 |
112 |
113 |
,
114 | mountNode,
115 | );
116 | ```
117 |
--------------------------------------------------------------------------------
/components/query-form/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 组件
3 | type: 数据录入
4 | cols: 1
5 | title: QueryForm
6 | subtitle: 查询表单
7 | ---
8 |
9 | ## 何时使用
10 |
11 | 需要一个数据查询组件时使用,组件会自己管理状态并返回查询的数据。
12 |
13 | ## API
14 |
15 |
16 | ### QueryForm
17 |
18 | | 参数 | 说明 | 类型 | 默认值 |
19 | | :--------- | :------------- | :------------------ | :----- |
20 | | columns | 表单列的配置描述,具体项见下表(必填) | ColumnProps | [] |
21 | | searchText | 搜索按钮的文案 | string \| React.ReactNode | 查询 |
22 | | resetText | 重置按钮的文案 | string \| React.ReactNode | 重置 |
23 | | mode | `FormItem` 的标题展示模式,`full`是占满整行,左对齐; `align` 会根据标题右对齐 | `['full', 'align']` | `'full'` |
24 | | colMode | `FormItem` 的展示模式,`grid`是等分的栅格布局; `style` 会根据会对每个 `Col` 增加固定 `300px` 的宽度,也可以通过 `Column.colStyle` 自定义宽度等样式 | `['grid', 'style']` | `'grid'` |
25 | | showOptionBtns | 是否展示右下角的「查询」「重置」按钮,以及「展开」「收起」 | boolean | true |
26 | | showCollapseButton | 是否展示右下角的「展开」「收起」 | boolean | true |
27 | | onChange | 表单的值改变时触发的回调 | Function(values, form) | - |
28 | | onSearch | 点击查询按钮的回调 | Function(values, form) | - |
29 | | onReset | 点击重置按钮的回调 | Function(form) | - |
30 | | isResetClearAll | 点击重置时,是清空form里面的值,还是根据 `initialValue` 重置 | boolean | false |
31 | | getFormInstance | 只用来获取Form实例的回调 | Function(form) | - |
32 | | defaultCollapse | 是否默认「展开」 | boolean | true |
33 | | colConfig | Col 布局配置 | `{lg:number;md:number;xxl:number;xl:number;sm:number;xs:number}` | `{xs:24,sm:24,md:12,lg:12,xl:8,xxl:6}` |
34 | | antConfig | 使用 `Antd ConfigProvider` 进行的全局配置,需要通过这个属性传进来 | [ConfigProviderProps](https://github.com/ant-design/ant-design/blob/master/components/config-provider/index.tsx) | - |
35 |
36 | ### Columns
37 |
38 | 表单列的配置描述,目前支持 `['input', 'select', 'custom']` 这三种。
39 |
40 | | 参数 | 说明 | 类型 | 默认值 |
41 | | :--------- | :------------- | :------------------ | :----- |
42 | | type | 动态表单组件的类型,内置 `input`, `select`;也可以自定义 | `['input', 'select', 'custom']` | - |
43 | | title | 标题 | string | - |
44 | | dataIndex | form表单的唯一标识,不可以重复 | string | - |
45 | | placeholder | 占位文案,默认会根据 `title` 自动生成 | string | - |
46 | | isInputPressEnterCallSearch | 输入框按回车的时候,触发搜索 | boolean | - |
47 | | valuePropName | 子节点的值的属性,如 Switch 的是 'checked' | string | 'value' |
48 | | required | 是否对参数进行必填校验 | boolean | true |
49 | | colStyle | `colMode='style'` 时,可以设置单个 `Column` 的样式| `React.CSSProperties` | - |
50 | | columnStyleHideNumber | `colMode='style'` 时,收起时默认只展示一项,可以设置展示多项| number | 1 |
51 | | initialValue | 表单的初始化值 | any | - |
52 | | formItemLayout | 表单的Layout | `{labelCol:{xs:{span:number},sm:{span:number},md:{span:number},lg:{span:number},xl:{span:number},xxl:{span:number}},wrapperCol:{xs:{span:number},sm:{span:number},md:{span:number},lg:{span:number},xl:{span:number},xxl:{span:number}}}` | `{labelCol:{xs:{span:5},sm:{span:5},md:{span:7},lg:{span:7},xl:{span:8},xxl:{span:8},},wrapperCol:{xs:{span:19},sm:{span:19},md:{span:17},lg:{span:17},xl:{span:16},xxl:{span:16},},}` |
53 | | rules | 自定义表单项的校验规则 | `object[]` | - |
54 | | size | 表单项的 `size` 属性 | `large` \| `default` \| `small` | `default` |
55 | | componentProps | `type="input|select"` 时,可以通过该属性 ant 组件的Props | any | - |
56 | | component | `type="custom"` 时,可以通过该属性传递 `React.ReactNode` | React.ReactNode | - |
57 | | selectMode | `type="select"` 时的 `mode` 属性 | `default` \| `multiple` | `default` |
58 | | options | `type="select"` 时,通过该属性设置下拉选项 | {title: string;value: string;}[] | [] |
--------------------------------------------------------------------------------
/components/query-form/index.tsx:
--------------------------------------------------------------------------------
1 | import './style/index.less';
2 | import QueryForm from './QueryForm';
3 | export default QueryForm;
4 |
--------------------------------------------------------------------------------
/components/query-form/style/index.less:
--------------------------------------------------------------------------------
1 | @import '../../style/index.less';
2 |
3 | @dantd-query-form-prefix-cls: ~'@{dantd-prefix}-query-form';
4 |
5 | .@{dantd-query-form-prefix-cls} {
6 | position: relative;
7 | padding: 22px 20px 0;
8 | background: #fff;
9 | &-formitem-full {
10 | display: flex !important;
11 | .ant-form-item-control-wrapper {
12 | flex: 1;
13 | width: 100%;
14 | }
15 | }
16 | &-option {
17 | .ant-form-item-control {
18 | display: flex;
19 | align-items: center;
20 | justify-content: flex-end;
21 | text-align: right;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/components/query-form/style/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.less';
2 |
--------------------------------------------------------------------------------
/components/query-form/use-media-antd-query.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import useMediaQuery from './use-media';
3 |
4 | export const MediaQueryEnum = {
5 | xs: '(max-width: 575px)',
6 | sm: '(min-width: 576px) and (max-width: 767px)',
7 | md: '(min-width: 768px) and (max-width: 991px)',
8 | lg: '(min-width: 992px) and (max-width: 1199px)',
9 | xl: '(min-width: 1200px) and (max-width: 1599px)',
10 | xxl: '(min-width: 1600px)',
11 | };
12 |
13 | export type MediaQueryKey = keyof typeof MediaQueryEnum;
14 |
15 | /**
16 | * loop query screen className
17 | * Array.find will throw a error
18 | * `Rendered more hooks than during the previous render.`
19 | * So should use Array.forEach
20 | */
21 | export const getScreenClassName = () => {
22 | let className: MediaQueryKey = 'md';
23 | // support ssr
24 | if (typeof window === 'undefined') {
25 | return className;
26 | }
27 | const mediaQueryKey = (Object.keys(MediaQueryEnum) as MediaQueryKey[]).find((key) => {
28 | const matchMedia = MediaQueryEnum[key];
29 | if (window.matchMedia(matchMedia).matches) {
30 | return true;
31 | }
32 | return false;
33 | });
34 | className = (mediaQueryKey as unknown) as MediaQueryKey;
35 | return className;
36 | };
37 |
38 | const useMedia = () => {
39 | const isMd = useMediaQuery(MediaQueryEnum.md);
40 | const isLg = useMediaQuery(MediaQueryEnum.lg);
41 | const isXxl = useMediaQuery(MediaQueryEnum.xxl);
42 | const isXl = useMediaQuery(MediaQueryEnum.xl);
43 | const isSm = useMediaQuery(MediaQueryEnum.sm);
44 | const isXs = useMediaQuery(MediaQueryEnum.xs);
45 | const [colSpan, setColSpan] = useState(getScreenClassName());
46 |
47 | useEffect(() => {
48 | if (isXxl) {
49 | setColSpan('xxl');
50 | return;
51 | }
52 | if (isXl) {
53 | setColSpan('xl');
54 | return;
55 | }
56 | if (isLg) {
57 | setColSpan('lg');
58 | return;
59 | }
60 | if (isMd) {
61 | setColSpan('md');
62 | return;
63 | }
64 | if (isSm) {
65 | setColSpan('sm');
66 | return;
67 | }
68 | if (isXs) {
69 | setColSpan('xs');
70 | return;
71 | }
72 | setColSpan('md');
73 | }, [isMd, isLg, isXxl, isXl, isSm, isXs]);
74 |
75 | return colSpan;
76 | };
77 |
78 | export default useMedia;
79 |
--------------------------------------------------------------------------------
/components/query-form/use-media.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { isClient } from '../utils';
3 |
4 | const useMedia = (query: string, defaultState: boolean = false) => {
5 | const [state, setState] = useState(
6 | isClient ? () => window.matchMedia(query).matches : defaultState,
7 | );
8 |
9 | useEffect(() => {
10 | let mounted = true;
11 | const mql = window.matchMedia(query);
12 | const onChange = () => {
13 | if (!mounted) {
14 | return;
15 | }
16 | setState(!!mql.matches);
17 | };
18 |
19 | mql.addListener(onChange);
20 | setState(mql.matches);
21 |
22 | return () => {
23 | mounted = false;
24 | mql.removeListener(onChange);
25 | };
26 | }, [query]);
27 |
28 | return state;
29 | };
30 |
31 | export default useMedia;
32 |
--------------------------------------------------------------------------------
/components/style/antd-extension.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/table/style/index.less';
2 | @import '~antd/es/style/themes/default.less';
3 |
4 | // 扩展 Antd v3 Table filter icon selected color
5 | .@{table-prefix-cls} {
6 | &-thead > tr > th {
7 | .@{table-prefix-cls}-filter-selected.@{iconfont-css-prefix}-search {
8 | color: @primary-color;
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/components/style/index.less:
--------------------------------------------------------------------------------
1 | @import './mixins.less';
2 | @import './antd-extension.less';
3 | @import './variable.less';
4 |
--------------------------------------------------------------------------------
/components/style/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.less';
2 |
--------------------------------------------------------------------------------
/components/style/mixins.less:
--------------------------------------------------------------------------------
1 | // mixins for clearfix
2 | // ------------------------
3 | .clearfix() {
4 | zoom: 1;
5 | &::before,
6 | &::after {
7 | display: table;
8 | width: 100%;
9 | content: '';
10 | }
11 | &::after {
12 | clear: both;
13 | }
14 | }
15 |
16 | .reset-space {
17 | margin: 0;
18 | padding: 0;
19 | }
20 |
21 | .reset-space-border {
22 | margin: 0;
23 | padding: 0;
24 | border: 0;
25 | }
26 |
27 | .hide-fake {
28 | width: 0;
29 | height: 0;
30 | position: absolute;
31 | top: -9999px;
32 | right: -9999px;
33 | overflow: hidden;
34 | display: none;
35 | }
--------------------------------------------------------------------------------
/components/style/themes.less:
--------------------------------------------------------------------------------
1 | // 引入所有使用 theme 的变量
2 | @import '../switch/style/index.less';
3 | @import '../data-table/style/index.less';
4 | @import '../basic-table/style/index.less';
--------------------------------------------------------------------------------
/components/style/variable.less:
--------------------------------------------------------------------------------
1 | @dantd-prefix: dantd;
2 |
--------------------------------------------------------------------------------
/components/switch/__tests__/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render, fireEvent, cleanup } from '@testing-library/react';
3 | import Switch from '../switch';
4 |
5 | describe('Switch Testing', () => {
6 | afterEach(cleanup);
7 | // works
8 | it('works', () => {
9 | const { getByTestId } = render();
10 | const input = getByTestId('switch-input');
11 | const label = getByTestId('switch-label');
12 | fireEvent.click(label);
13 | expect(input).toBeChecked();
14 | });
15 | // defaultChecked
16 | it('defaultChecked', () => {
17 | const { getByTestId } = render();
18 | const input = getByTestId('switch-input');
19 | expect(input).toBeChecked();
20 | });
21 | // onChange & onClick
22 | it('onChange & onClick should called', () => {
23 | const onChange = jest.fn();
24 | const onClick = jest.fn();
25 | const { getByTestId } = render();
26 | const input = getByTestId('switch-input');
27 | fireEvent.click(input);
28 | expect(onChange).toHaveBeenCalled();
29 | expect(onClick).toHaveBeenCalled();
30 | });
31 | // should not toggle when clicked in a disabled state
32 | it('should not toggle when clicked in a disabled state', () => {
33 | const { getByTestId } = render();
34 | const input = getByTestId('switch-input');
35 | const label = getByTestId('switch-label');
36 | fireEvent.click(label);
37 | expect(input).not.toBeChecked();
38 | });
39 | // unCheckedChildren & checkedChildren
40 | it('unCheckedChildren & checkedChildren', () => {
41 | const checkedChildren = '中';
42 | const unCheckedChildren = 'EN';
43 | const { getByTestId } = render(
44 | ,
45 | );
46 | const contentLeft = getByTestId('content-left');
47 | const contentRight = getByTestId('content-right');
48 | const label = getByTestId('switch-label');
49 | expect(contentLeft).toHaveTextContent(checkedChildren);
50 | expect(contentRight).toHaveTextContent(unCheckedChildren);
51 | fireEvent.click(label);
52 | expect(contentLeft).toHaveTextContent(unCheckedChildren);
53 | expect(contentRight).toHaveTextContent(checkedChildren);
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/components/switch/demo/disabled.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 2
3 | title: 禁用
4 | ---
5 |
6 | 使用 `disabled` 属性,禁用组件。
7 |
8 | ```jsx
9 | import { Switch, EmptyLine } from 'antd-advanced';
10 |
11 | ReactDOM.render(
12 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
,
27 | mountNode,
28 | );
29 | ```
30 |
--------------------------------------------------------------------------------
/components/switch/demo/small.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 0
3 | title: 基本 - small
4 | ---
5 |
6 | `size` 传 `small`,可展示一个小型的开关。
7 |
8 | ```jsx
9 | import { Switch } from 'antd-advanced';
10 |
11 | ReactDOM.render(
12 |
13 |
14 |
,
15 | mountNode,
16 | );
17 | ```
18 |
--------------------------------------------------------------------------------
/components/switch/demo/text.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 1
3 | title: 文本
4 | ---
5 |
6 | 使用 `checkedChildren` 和 `unCheckedChildren`,传入中英文的文案。
7 |
8 | ```jsx
9 | import { Switch } from 'antd-advanced';
10 |
11 | ReactDOM.render(
12 |
13 |
14 |
,
15 | mountNode,
16 | );
17 | ```
18 |
--------------------------------------------------------------------------------
/components/switch/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 组件
3 | type: 数据录入
4 | title: Switch
5 | subtitle: 开关
6 | ---
7 |
8 | ## 何时使用
9 |
10 | 需要表示开关状态/两种状态之间的切换时;
11 |
12 | ## API
13 |
14 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
15 | | --- | --- | --- | --- | --- |
16 | | checked | 指定当前是否选中 | boolean | false | |
17 | | checkedChildren | 选中时的内容 | string\|ReactNode | | |
18 | | defaultChecked | 初始是否选中 | boolean | false | |
19 | | disabled | 是否禁用 | boolean | false | |
20 | | size | 开关大小,可选值:`default` `small` | string | default | |
21 | | unCheckedChildren | 非选中时的内容 | string\|ReactNode | | |
22 | | onChange | 变化时回调函数 | Function(checked: boolean, event: Event) | | |
23 | | onClick | 点击时回调函数 | Function(checked: boolean, event: Event) | | |
24 | | className | Switch 器类名 | string | - | |
25 |
--------------------------------------------------------------------------------
/components/switch/index.tsx:
--------------------------------------------------------------------------------
1 | import './style/index.less';
2 | import Switch from './switch';
3 |
4 | export default Switch;
5 |
--------------------------------------------------------------------------------
/components/switch/style/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.less';
2 |
--------------------------------------------------------------------------------
/components/switch/switch.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import classNames from 'classnames';
3 | import { uuidv4 } from '../utils';
4 |
5 | export type SwitchSize = 'small' | 'default';
6 | export type SwitchChangeEventHandler = (checked: boolean, event: MouseEvent) => void;
7 | export type SwitchClickEventHandler = SwitchChangeEventHandler;
8 |
9 | export interface ISwitchProps {
10 | prefixCls?: string;
11 | size?: SwitchSize;
12 | className?: string;
13 | id?: string;
14 | checked?: boolean;
15 | defaultChecked?: boolean;
16 | onChange?: SwitchChangeEventHandler;
17 | onClick?: SwitchClickEventHandler;
18 | unCheckedChildren?: React.ReactNode;
19 | checkedChildren?: React.ReactNode;
20 | disabled?: boolean;
21 | loading?: boolean;
22 | autoFocus?: boolean;
23 | style?: React.CSSProperties;
24 | }
25 |
26 | function Switch(props: ISwitchProps) {
27 | const prefixCls = `${props.prefixCls || 'dantd'}-switch`;
28 |
29 | const switchClassName = classNames(
30 | prefixCls,
31 | {
32 | [`${prefixCls}-small`]: props.size === 'small',
33 | [`${prefixCls}-default`]: props.size !== 'small',
34 | [`${prefixCls}-disabled`]: !!props.disabled,
35 | },
36 | props.className,
37 | );
38 |
39 | const switchStyle = {
40 | ...props.style,
41 | };
42 | const id = props.id || `dantd-switch-${uuidv4()}`;
43 | let checked = false;
44 | if ('checked' in props) {
45 | checked = !!props.checked;
46 | } else {
47 | checked = !!props.defaultChecked;
48 | }
49 |
50 | const [checkedState, setChacked] = useState(checked);
51 |
52 | useEffect(() => {
53 | if ('checked' in props && props.checked !== checkedState && props.checked !== undefined) {
54 | setChacked(props.checked);
55 | }
56 | // eslint-disable-next-line react-hooks/exhaustive-deps
57 | }, [props.checked]);
58 |
59 | const handleChange = (e) => {
60 | const { disabled, onChange, onClick } = props;
61 | if (disabled) {
62 | return;
63 | }
64 | if (!('checked' in props)) {
65 | setChacked(e.target.checked);
66 | }
67 | if (onChange) {
68 | onChange(e.target.checked, e);
69 | }
70 | if (onClick) {
71 | onClick(e.target.checked, e);
72 | }
73 | };
74 |
75 | return (
76 |
99 | );
100 | }
101 | export default Switch;
102 |
--------------------------------------------------------------------------------
/components/use-async-fn/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 自定义Hook
3 | cols: 1
4 | type: hook
5 | title: useAsyncFn
6 | subtitle: 函数请求
7 | ---
8 |
9 | ## 何时使用
10 |
11 | 这个 `Hook` 用于解析 async 函数或返回 promise 的函数。
12 |
13 |
14 | ```jsx
15 | import { useAsyncFn } from 'antd-advanced';
16 |
17 | const Demo = (url) => {
18 | const [state, fetch] = useAsyncFn(async () => {
19 | const response = await fetch(url);
20 | const result = await response.text();
21 | return result
22 | }, [url]);
23 |
24 | return (
25 |
26 | {state.loading
27 | ?
Loading...
28 | : state.error
29 | ?
Error: {state.error.message}
30 | : state.value &&
Value: {state.value}
31 | }
32 |
33 |
34 | );
35 | };
36 |
37 | ReactDOM.render(
38 |
39 |
40 |
,
41 | mountNode,
42 | );
43 | ```
44 |
45 | copy 自 [react-use](https://github.com/streamich/react-use/blob/master/docs/useAsync.md)
46 |
--------------------------------------------------------------------------------
/components/use-async-fn/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import { DependencyList, useCallback, useState, useRef } from 'react';
3 | import useMountedState from '../use-mounted-state';
4 |
5 | export type AsyncState =
6 | | {
7 | loading: boolean;
8 | error?: undefined;
9 | value?: undefined;
10 | }
11 | | {
12 | loading: false;
13 | error: Error;
14 | value?: undefined;
15 | }
16 | | {
17 | loading: false;
18 | error?: undefined;
19 | value: T;
20 | };
21 |
22 | export type AsyncFn = [
23 | AsyncState,
24 | (...args: Args | []) => Promise
25 | ];
26 |
27 | export default function useAsyncFn(
28 | fn: (...args: Args | []) => Promise,
29 | deps: DependencyList = [],
30 | initialState: AsyncState = { loading: false }
31 | ): AsyncFn {
32 | const lastCallId = useRef(0);
33 | const [state, set] = useState>(initialState);
34 |
35 | const isMounted = useMountedState();
36 |
37 | const callback = useCallback((...args: Args | []) => {
38 | const callId = ++lastCallId.current;
39 | set({ loading: true });
40 |
41 | return fn(...args).then(
42 | value => {
43 | isMounted() && callId === lastCallId.current && set({ value, loading: false });
44 |
45 | return value;
46 | },
47 | error => {
48 | isMounted() && callId === lastCallId.current && set({ error, loading: false });
49 |
50 | return error;
51 | }
52 | );
53 | }, deps);
54 |
55 | return [state, callback];
56 | }
57 |
--------------------------------------------------------------------------------
/components/use-async-retry/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 自定义Hook
3 | cols: 1
4 | type: hook
5 | title: useAsyncRetry
6 | subtitle: retry请求
7 | ---
8 |
9 | ## 何时使用
10 |
11 | 使用 `useAsync` 和一个额外的retry方法来轻松重试/刷新异步函数;
12 |
13 |
14 | ```jsx
15 | import { useAsyncRetry } from 'antd-advanced';
16 |
17 | // Returns a Promise that resolves after one second.
18 | const fn = () => new Promise((resolve, reject) => {
19 | setTimeout(() => {
20 | if (Math.random() > 0.5) {
21 | reject(new Error('Random error!'));
22 | } else {
23 | resolve('RESOLVED');
24 | }
25 | }, 1000);
26 | });
27 |
28 | const Demo = () => {
29 | const state = useAsyncRetry(fn);
30 |
31 | return (
32 |
33 | {state.loading?
34 |
Loading...
35 | : state.error?
36 |
Error...
37 | :
Value: {state.value}
38 | }
39 | {!state.loading?
40 |
state.retry()}>Retry
41 | : null
42 | }
43 |
44 | );
45 | };
46 |
47 | ReactDOM.render(
48 |
49 |
50 |
,
51 | mountNode,
52 | );
53 | ```
54 |
55 | copy 自 [react-use](https://github.com/streamich/react-use/blob/master/docs/useAsyncRetry.md)
56 |
--------------------------------------------------------------------------------
/components/use-async-retry/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import { DependencyList, useCallback, useState } from 'react';
3 | import {AsyncState} from '../use-async-fn';
4 | import useAsync from '../use-async';
5 |
6 | export type AsyncStateRetry = AsyncState & {
7 | retry(): void;
8 | };
9 |
10 | const useAsyncRetry = (fn: () => Promise, deps: DependencyList = []) => {
11 | const [attempt, setAttempt] = useState(0);
12 | const state = useAsync(fn, [...deps, attempt]);
13 |
14 | const stateLoading = state.loading;
15 | const retry = useCallback(() => {
16 | if (stateLoading) {
17 | if (process.env.NODE_ENV === 'development') {
18 | console.log('You are calling useAsyncRetry hook retry() method while loading in progress, this is a no-op.');
19 | }
20 |
21 | return;
22 | }
23 |
24 | setAttempt(currentAttempt => currentAttempt + 1);
25 | }, [...deps, stateLoading]);
26 |
27 | return { ...state, retry };
28 | };
29 |
30 | export default useAsyncRetry;
31 |
--------------------------------------------------------------------------------
/components/use-async/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 自定义Hook
3 | cols: 1
4 | type: hook
5 | title: useAsync
6 | subtitle: 请求
7 | ---
8 |
9 | ## 何时使用
10 |
11 | 接收一个 async 函数或返回 promise 的函数,返回状态,数据形状与 `useAsync` 相同。
12 |
13 |
14 | ```jsx
15 | import { useAsyncFn } from 'antd-advanced';
16 |
17 | const fn = () => new Promise((resolve) => {
18 | setTimeout(() => {
19 | resolve('RESOLVED');
20 | }, 1000);
21 | });
22 |
23 | const Demo = () => {
24 | const state = useAsync(fn);
25 |
26 | return (
27 |
28 | {state.loading?
29 |
Loading...
30 | : state.error?
31 |
Error...
32 | :
Value: {state.value}
33 | }
34 |
35 | );
36 | };
37 |
38 | ReactDOM.render(
39 |
40 |
41 |
,
42 | mountNode,
43 | );
44 | ```
45 |
46 |
47 | copy 自 [react-use](https://github.com/streamich/react-use/blob/master/docs/useAsyncFn.md)
48 |
--------------------------------------------------------------------------------
/components/use-async/index.ts:
--------------------------------------------------------------------------------
1 | import { DependencyList, useEffect } from 'react';
2 | import useAsyncFn from '../use-async-fn';
3 |
4 | export default function useAsync(
5 | fn: (...args: Args | []) => Promise,
6 | deps: DependencyList = [],
7 | ) {
8 | const [state, callback] = useAsyncFn(fn, deps, {
9 | loading: true,
10 | });
11 |
12 | useEffect(() => {
13 | callback();
14 | }, [callback]);
15 |
16 | return state;
17 | }
18 |
--------------------------------------------------------------------------------
/components/use-debounce/demo/useDebounce.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 组件
3 | cols: 1
4 | type: hook
5 | title: useDebounce
6 | subtitle: 防抖
7 | ---
8 |
9 | 配合 `useEffect` ,监听 `input` 输入值的变化。
10 |
11 | ```jsx
12 | import { Table, Input, Icon, Row, Col, Button } from 'antd';
13 | import { useDebounce } from 'antd-advanced';
14 | const listUrl = 'https://easy-mock.com/mock/5f7e96fbf9d9bd19dca629b0/dantd/list';
15 | const columns = [
16 | {
17 | title: '标题',
18 | dataIndex: 'title',
19 | commonSearch: true,
20 | },
21 | {
22 | title: '缩略图',
23 | dataIndex: 'image',
24 | render: (text, record, index) => (
25 |
26 | {record.thumbnail_pic_s ? (
27 |

28 | ) : (
29 | '暂无图片'
30 | )}
31 |
32 | ),
33 | },
34 | {
35 | title: '分类',
36 | dataIndex: 'category',
37 | commonFilter: true,
38 | },
39 | {
40 | title: '作者',
41 | dataIndex: 'author_name',
42 | commonFilter: true,
43 | },
44 | {
45 | title: '发布日期',
46 | dataIndex: 'date',
47 | commonSorter: true,
48 | },
49 | ];
50 |
51 | const BasicExample: React.FC = () => {
52 | const [dataSource, setDataSource] = React.useState([]);
53 | const [loading, setLoading] = React.useState(false);
54 | const [searchQuery, setSearchQuery] = React.useState();
55 | const debouncedSearchQuery = useDebounce(searchQuery, 500);
56 |
57 | React.useEffect(() => {
58 | fetchData();
59 | }, [])
60 |
61 | React.useEffect(() => {
62 | const fetchParams = {
63 | title: debouncedSearchQuery
64 | }
65 |
66 | fetchData(fetchParams);
67 | // eslint-disable-next-line react-hooks/exhaustive-deps
68 | }, [debouncedSearchQuery]);
69 |
70 | async function fetchData(fetchParams = {}) {
71 | let url = new URL(listUrl) as any;
72 | url.search = new URLSearchParams(fetchParams);
73 | setLoading(true);
74 | const res = await fetch(url);
75 | res
76 | .json()
77 | .then(res => {
78 | setDataSource(res.data);
79 | setLoading(false);
80 | })
81 | .catch(() => {
82 | setLoading(false);
83 | });
84 | }
85 |
86 | const handleSearchChange = e => {
87 | const query = e.target.value;
88 | setSearchQuery(query);
89 | };
90 |
91 | return (
92 |
105 | );
106 | }
107 |
108 | ReactDOM.render(
109 |
110 |
111 |
,
112 | mountNode,
113 | );
114 | ```
115 |
--------------------------------------------------------------------------------
/components/use-debounce/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 自定义Hook
3 | cols: 1
4 | type: hook
5 | title: useDebounce
6 | subtitle: 防抖
7 | ---
8 |
9 | ## 何时使用
10 |
11 | 这个 `Hook` 可以用防抖动的方式来处理改变过快的值。当 `useDebounce` 在指定的时间段内没有被调用时,`hook` 仅返回最新的值。当与 `useEffect` 一起使用时,就像我们在下面的示例中所做的那样,您可以轻松地确保昂贵的操作(如API调用)不会执行得太频繁。
12 |
--------------------------------------------------------------------------------
/components/use-debounce/index.tsx:
--------------------------------------------------------------------------------
1 | // https://usehooks.com/useDebounce/
2 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
3 | import React, { useState, useEffect } from 'react';
4 |
5 | export default function useDebounce(value, delay) {
6 | // State and setters for debounced value
7 | const [debouncedValue, setDebouncedValue] = useState(value);
8 |
9 | useEffect(
10 | () => {
11 | // Update debounced value after delay
12 | const handler = setTimeout(() => {
13 | setDebouncedValue(value);
14 | }, delay);
15 |
16 | // Cancel the timeout if value changes (also on delay change or unmount)
17 | // This is how we prevent debounced value from updating if value is changed ...
18 | // .. within the delay period. Timeout gets cleared and restarted.
19 | return () => {
20 | clearTimeout(handler);
21 | };
22 | },
23 | [value, delay], // Only re-call effect if value or delay changes
24 | );
25 |
26 | return debouncedValue;
27 | }
28 |
--------------------------------------------------------------------------------
/components/use-deep-compare-effect/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 自定义Hook
3 | cols: 1
4 | type: hook
5 | title: useDeepCompareEffect
6 | subtitle: 深比较Effect
7 | ---
8 |
9 | ## 何时使用
10 |
11 | 需要深比较依赖,触发Effect的时候
12 |
13 |
14 | ```jsx
15 | import { useState } from 'react';
16 | import { useDeepCompareEffect } from 'antd-advanced';
17 |
18 | const Demo = () => {
19 | const [count, setCount] = useState(0);
20 | const options = { step: 2 };
21 |
22 | useDeepCompareEffect(() => {
23 | setCount(count + options.step);
24 | }, [options]);
25 |
26 | return (
27 |
28 |
useDeepCompareEffect: {count}
29 |
30 | );
31 | };
32 | ```
33 |
34 |
35 | copy 自 [react-use](https://github.com/streamich/react-use/blob/master/docs/useAsyncFn.md)
36 |
--------------------------------------------------------------------------------
/components/use-deep-compare-effect/index.ts:
--------------------------------------------------------------------------------
1 | import { DependencyList, EffectCallback } from 'react';
2 | import isDeepEqualReact from 'react-fast-compare';
3 | import useCustomCompareEffect from './useCustomCompareEffect';
4 |
5 | const isDeepEqual: (a: any, b: any) => boolean = isDeepEqualReact;
6 | const isPrimitive = (val: any) => val !== Object(val);
7 |
8 | const useDeepCompareEffect = (effect: EffectCallback, deps: DependencyList) => {
9 | if (process.env.NODE_ENV !== 'production') {
10 | if (!(deps instanceof Array) || !deps.length) {
11 | console.warn(
12 | '`useDeepCompareEffect` should not be used with no dependencies. Use React.useEffect instead.',
13 | );
14 | }
15 |
16 | if (deps.every(isPrimitive)) {
17 | console.warn(
18 | '`useDeepCompareEffect` should not be used with dependencies that are all primitive values. Use React.useEffect instead.',
19 | );
20 | }
21 | }
22 |
23 | useCustomCompareEffect(effect, deps, isDeepEqual);
24 | };
25 |
26 | export default useDeepCompareEffect;
27 |
--------------------------------------------------------------------------------
/components/use-deep-compare-effect/useCustomCompareEffect.ts:
--------------------------------------------------------------------------------
1 | import { DependencyList, EffectCallback, useEffect, useRef } from 'react';
2 |
3 | const isPrimitive = (val: any) => val !== Object(val);
4 |
5 | type DepsEqualFnType = (prevDeps: TDeps, nextDeps: TDeps) => boolean;
6 |
7 | const useCustomCompareEffect = (
8 | effect: EffectCallback,
9 | deps: TDeps,
10 | depsEqual: DepsEqualFnType,
11 | ) => {
12 | if (process.env.NODE_ENV !== 'production') {
13 | if (!(deps instanceof Array) || !deps.length) {
14 | console.warn(
15 | '`useCustomCompareEffect` should not be used with no dependencies. Use React.useEffect instead.',
16 | );
17 | }
18 |
19 | if (deps.every(isPrimitive)) {
20 | console.warn(
21 | '`useCustomCompareEffect` should not be used with dependencies that are all primitive values. Use React.useEffect instead.',
22 | );
23 | }
24 |
25 | if (typeof depsEqual !== 'function') {
26 | console.warn(
27 | '`useCustomCompareEffect` should be used with depsEqual callback for comparing deps list',
28 | );
29 | }
30 | }
31 |
32 | const ref = useRef(undefined);
33 |
34 | if (!ref.current || !depsEqual(deps, ref.current)) {
35 | ref.current = deps;
36 | }
37 |
38 | useEffect(effect, ref.current);
39 | };
40 |
41 | export default useCustomCompareEffect;
42 |
--------------------------------------------------------------------------------
/components/use-dynamic-list/demo/basic.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 0
3 | title: 基本
4 | ---
5 |
6 | 这是一个基础的动态表单。
7 |
8 | ```jsx
9 | import React, { useState } from 'react';
10 | import { Form, Button, Input, Icon } from 'antd';
11 | import { useDynamicList } from 'antd-advanced';
12 |
13 | const Demo = props => {
14 | const { list, remove, getKey, push } = useDynamicList(['David', 'Jack']);
15 | const { getFieldDecorator, validateFields } = props.form;
16 |
17 | const [result, setResult] = useState('');
18 |
19 | const Row = (index, item) => (
20 |
21 | {getFieldDecorator(`names[${getKey(index)}]`, {
22 | initialValue: item,
23 | rules: [
24 | {
25 | required: true,
26 | message: 'required',
27 | },
28 | ],
29 | })()}
30 | {list.length > 1 && (
31 | {
35 | remove(index);
36 | }}
37 | />
38 | )}
39 | {
43 | push('');
44 | }}
45 | />
46 |
47 | );
48 |
49 | return (
50 | <>
51 |
52 |
65 | {result}
66 | >
67 | );
68 | }
69 | const BasicDemoForm = Form.create({ name: 'basic-form' })(Demo);
70 |
71 | ReactDOM.render(, mountNode);
72 | ```
73 |
--------------------------------------------------------------------------------
/components/use-dynamic-list/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 自定义Hook
3 | cols: 1
4 | type: hook
5 | title: useDynamicList
6 | subtitle: 动态表单
7 | ---
8 |
9 | > 下面文档出自 [umi hook useDynamicList v1.9.2](https://hooks.umijs.org/zh-CN/ui/use-dynamic-list)
10 |
11 | > 已经在使用 `@umijs/hooks` 的项目推荐使用原版的 `useDynamicList`。
12 |
13 | # 何时使用
14 |
15 | 一个帮助你管理列表状态,并能生成唯一 key 的 Hook。
16 |
17 | ## API
18 |
19 | ```typescript
20 | const result: Result = useDynamicList(initialValue: T[]);
21 | ```
22 |
23 | ### Result
24 |
25 | | 参数 | 说明 | 类型 | 备注 |
26 | |--------------|--------------|----------------------|---------------|
27 | | list | 当前的列表 | T[] | - |
28 | | resetList | 重新设置 list 的值 | (list: T[]) => void; | - |
29 | | insert | 在指定位置插入元素 | (index: number, obj: T) => void | - |
30 | | merge | 在指定位置插入多个元素 | (index: number, obj: T) => void | - |
31 | | replace | 替换指定元素 | (index: number, obj: T) => void | - |
32 | | remove | 删除指定元素 | (index: number) => void; | - |
33 | | move | 移动元素 | (oldIndex: number, newIndex: number) => void; | - |
34 | | getKey | 获得某个元素的 uuid | (index: number) => number; | - |
35 | | getIndex | 获得某个key的 index | (key: number) => number; | - |
36 | | sortForm | 根据表单结果自动排序 | (list: unknown[]) => unknown[]; | 使用方法详见[`动态表格(可拖拽)`](#动态表格可拖拽) |
37 | | push | 在列表末尾添加元素 | (obj: T) => void; | - |
38 | | pop | 移除末尾元素 | () => void; | - |
39 | | unshift | 在列表起始位置添加元素 | (obj: T) => void; | - |
40 | | shift | 移除起始位置元素 | () => void; | - |
41 |
42 | ### 参数
43 |
44 | | 参数 | 说明 | 类型 | |
45 | |--------------|--------------|----------------------|---|
46 | | initialValue | 列表的初始值 | T[] | |
47 |
--------------------------------------------------------------------------------
/components/use-interval/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 自定义Hook
3 | cols: 1
4 | type: hook
5 | title: useInterval
6 | subtitle: 定时器
7 | ---
8 |
9 | ## 何时使用
10 |
11 | 一个定时器的hook,基于[Josh的文章](https://joshwcomeau.com/snippets/react-hooks/use-interval)。
12 | 可以通过将延迟设置为null来暂停时间间隔。
13 | 当组件卸载的时候,会自动清除定时器。
14 | 也可以通过 `window.clearInterval` 来强行停止定制器。
15 |
16 | ### 关于 Antd 的 Tab 组件
17 |
18 | 但是一般不推荐这么做,比如 `Antdv3` 的 [Tab](https://ant-design-3x.gitee.io/components/tabs-cn/) 组件,
19 | 可以添加 [destroyInactiveTabPane](https://github.com/ant-design/ant-design/issues/15102) 属性,当切换Tab时,自动卸载Tab里面的东西即可。
20 |
21 | ```jsx
22 | import { useInterval } from 'antd-advanced';
23 | import { Button } from 'antd';
24 |
25 | const IntervalDemo = () => {
26 | const [status, setStatus] = React.useState('running');
27 | const [timeElapsed, setTimeElapsed] = React.useState(0);
28 | const instance = useInterval(
29 | () => {
30 | setTimeElapsed(timeElapsed => timeElapsed + 1);
31 | },
32 | status === 'running' ? 1000 : null,
33 | );
34 | const toggle = () => {
35 | setTimeElapsed(0);
36 | setStatus(status => (status === 'running' ? 'idle' : 'running'));
37 | };
38 |
39 | const stop = () => {
40 | window.clearInterval(instance);
41 | };
42 | return (
43 | <>
44 | Time Elapsed: {timeElapsed} second(s)
45 |
46 |
47 | >
48 | );
49 | };
50 | ```
51 |
52 |
53 | copy 自 [useInterval](https://joshwcomeau.com/snippets/react-hooks/use-interval)
54 |
--------------------------------------------------------------------------------
/components/use-interval/index.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef } from 'react';
2 |
3 | const useInterval = (callback: Function, delay?: number | null) => {
4 | const intervalId = useRef(null);
5 | const savedCallback = useRef(() => {});
6 |
7 | useEffect(() => {
8 | savedCallback.current = callback;
9 | });
10 |
11 | useEffect(() => {
12 | const tick = () => savedCallback.current();
13 | if (typeof delay === 'number') {
14 | intervalId.current = window.setInterval(tick, delay);
15 | return () => window.clearInterval(intervalId.current);
16 | }
17 | }, [delay]);
18 |
19 | return intervalId.current;
20 | };
21 |
22 | export default useInterval;
23 |
--------------------------------------------------------------------------------
/components/use-mounted-state/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 自定义Hook
3 | cols: 1
4 | type: hook
5 | title: useMountedState
6 | subtitle: Mounted
7 | ---
8 |
9 | ## 何时使用
10 |
11 | 生命周期钩子提供检查组件的挂载状态的能力。
12 |
13 | 给出一个函数,如果组件被挂载,该函数将返回true,否则返回false。
14 |
15 |
16 | ```jsx
17 | import { useMountedState } from 'antd-advanced';
18 |
19 |
20 | const Demo = () => {
21 | const isMounted = useMountedState();
22 |
23 | React.useEffect(() => {
24 | setTimeout(() => {
25 | if (isMounted()) {
26 | // ...
27 | } else {
28 | // ...
29 | }
30 | }, 1000);
31 | });
32 | };
33 |
34 | ReactDOM.render(
35 |
36 |
37 |
,
38 | mountNode,
39 | );
40 | ```
41 |
42 |
43 | copy 自 [react-use](https://github.com/streamich/react-use/blob/master/docs/useMountedState.md)
44 |
--------------------------------------------------------------------------------
/components/use-mounted-state/index.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useRef } from 'react';
2 |
3 | export default function useMountedState(): () => boolean {
4 | const mountedRef = useRef(false);
5 | const get = useCallback(() => mountedRef.current, []);
6 |
7 | useEffect(() => {
8 | mountedRef.current = true;
9 |
10 | return () => {
11 | mountedRef.current = false;
12 | };
13 | });
14 |
15 | return get;
16 | }
17 |
--------------------------------------------------------------------------------
/components/use-timeout/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | category: 自定义Hook
3 | cols: 1
4 | type: hook
5 | title: useTimeout
6 | subtitle: 间隔
7 | ---
8 |
9 | ## 何时使用
10 |
11 | 在指定的毫秒数后重新渲染组件。 提供取消或重置超时的调用方式。
12 |
13 |
14 | ```jsx
15 | import { useTimeout } from 'antd-advanced';
16 |
17 | function TestComponent(props: { ms?: number } = {}) {
18 | const ms = props.ms || 5000;
19 | const [isReady, cancel] = useTimeout(ms);
20 |
21 | return (
22 |
23 | { isReady() ? 'I\'m reloaded after timeout' : `I will be reloaded after ${ ms / 1000 }s` }
24 | { isReady() === false ? : '' }
25 |
26 | );
27 | }
28 |
29 | const Demo = () => {
30 | return (
31 |
32 |
33 |
34 |
35 | );
36 | };
37 | ```
38 |
39 |
40 | copy 自 [react-use](https://github.com/streamich/react-use/blob/master/docs/useTimeout.md)
41 |
--------------------------------------------------------------------------------
/components/use-timeout/index.ts:
--------------------------------------------------------------------------------
1 | import useTimeoutFn from './useTimeoutFn';
2 | import useUpdate from './useUpdate';
3 |
4 | export type UseTimeoutReturn = [() => boolean | null, () => void, () => void];
5 |
6 | export default function useTimeout(ms: number = 0): UseTimeoutReturn {
7 | const update = useUpdate();
8 |
9 | return useTimeoutFn(update, ms);
10 | }
11 |
--------------------------------------------------------------------------------
/components/use-timeout/useTimeoutFn.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | import { useCallback, useEffect, useRef } from 'react';
3 |
4 | export type UseTimeoutFnReturn = [() => boolean | null, () => void, () => void];
5 |
6 | export default function useTimeoutFn(fn: Function, ms: number = 0): UseTimeoutFnReturn {
7 | const ready = useRef(false);
8 | const timeout = useRef>();
9 | const callback = useRef(fn);
10 |
11 | const isReady = useCallback(() => ready.current, []);
12 |
13 | const set = useCallback(() => {
14 | ready.current = false;
15 | timeout.current && clearTimeout(timeout.current);
16 |
17 | timeout.current = setTimeout(() => {
18 | ready.current = true;
19 | callback.current();
20 | }, ms);
21 | }, [ms]);
22 |
23 | const clear = useCallback(() => {
24 | ready.current = null;
25 | timeout.current && clearTimeout(timeout.current);
26 | }, []);
27 |
28 | // update ref when function changes
29 | useEffect(() => {
30 | callback.current = fn;
31 | }, [fn]);
32 |
33 | // set on mount, clear on unmount
34 | useEffect(() => {
35 | set();
36 |
37 | return clear;
38 | }, [ms]);
39 |
40 | return [isReady, clear, set];
41 | }
42 |
--------------------------------------------------------------------------------
/components/use-timeout/useUpdate.ts:
--------------------------------------------------------------------------------
1 | import { useReducer } from 'react';
2 |
3 | const updateReducer = (num: number): number => (num + 1) % 1_000_000;
4 |
5 | const useUpdate = () => {
6 | const [, update] = useReducer(updateReducer, 0);
7 | return update as () => void;
8 | };
9 |
10 | export default useUpdate;
11 |
--------------------------------------------------------------------------------
/components/utils/index.tsx:
--------------------------------------------------------------------------------
1 | import queryString from 'query-string';
2 |
3 | export function uuidv4() {
4 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
5 | var r = (Math.random() * 16) | 0,
6 | v = c === 'x' ? r : (r & 0x3) | 0x8;
7 | return v.toString(16);
8 | });
9 | }
10 |
11 | export function prefixFieldKey(name, prefix = '') {
12 | if (!prefix) {
13 | return name;
14 | } else {
15 | name = name.replace(name[0], name[0].toUpperCase());
16 | return `${prefix}${name}`;
17 | }
18 | }
19 |
20 | export function processBasicFormItemsData(values) {
21 | const result = {};
22 | Object.entries(values).forEach(([formKey, formValue]) => {
23 | if (Array.isArray(formValue)) {
24 | formValue = formValue.filter((e) => !!e);
25 | }
26 | result[formKey] = formValue;
27 | });
28 | return result;
29 | }
30 |
31 | export const isClient = typeof window === 'object';
32 |
33 | /**
34 | * formatUrl 处理Url
35 | * @export
36 | * @param {*} url 需要处理的url,比如 /deploy/order/:id
37 | * @param {*} params 需要处理的参数 ,比如 {id: 123}
38 | * @param {*} query 需要处理的参数 ,比如 {id: 123}
39 | * @returns
40 | */
41 | export function formatUrl(url: string): string;
42 | export function formatUrl(url: string, params?: object): string;
43 | export function formatUrl(url: string, params?: object, query?: object): string;
44 | export function formatUrl(url: string, params?: object, query?: object): string {
45 | if (params) {
46 | Object.entries(params).forEach(([paramKey, paramValue]) => {
47 | var regex = new RegExp(`:${paramKey}`, 'i');
48 | url = url.replace(regex, paramValue);
49 | });
50 | }
51 |
52 | if (query) {
53 | url = `${url}?${queryString.stringify(query)}`;
54 | }
55 |
56 | return url;
57 | }
58 |
59 | export function hexToRgb(hex) {
60 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
61 | return result
62 | ? {
63 | r: parseInt(result[1], 16),
64 | g: parseInt(result[2], 16),
65 | b: parseInt(result[3], 16),
66 | }
67 | : null;
68 | }
69 |
--------------------------------------------------------------------------------
/components/virtual-select/demo/basic.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 0
3 | title: 基本
4 | ---
5 |
6 | 基本使用。
7 |
8 | ```jsx
9 | import { VirtualSelect as Select } from 'antd-advanced';
10 |
11 | const { Option } = Select;
12 |
13 | function handleChange(value) {
14 | console.log(`selected ${value}`);
15 | }
16 |
17 | ReactDOM.render(
18 |
19 |
27 |
30 |
33 |
,
34 | mountNode,
35 | );
36 | ```
37 |
--------------------------------------------------------------------------------
/components/virtual-select/demo/bigdata.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 4
3 | title: 大数据
4 | ---
5 |
6 | 大数据,虚拟滚动。
7 |
8 | ```jsx
9 | import { VirtualSelect as Select } from 'antd-advanced';
10 |
11 | const { Option } = Select;
12 |
13 | const bigOptions = [] as any[];
14 | for (let i = 0; i < 10000; i++) {
15 | bigOptions.push();
16 | }
17 |
18 | function handleChange(value) {
19 | console.log(`selected ${value}`);
20 | }
21 |
22 | ReactDOM.render(
23 | ,
33 | mountNode,
34 | );
35 | ```
36 |
--------------------------------------------------------------------------------
/components/virtual-select/demo/multiple.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 2
3 | title: 多选
4 | ---
5 |
6 | 多选,从已有条目中选择。
7 |
8 | ```jsx
9 | import { VirtualSelect as Select } from 'antd-advanced';
10 |
11 | const { Option } = Select;
12 |
13 | const children = [];
14 | for (let i = 10; i < 36; i++) {
15 | children.push();
16 | }
17 |
18 | function handleChange(value) {
19 | console.log(`selected ${value}`);
20 | }
21 |
22 | ReactDOM.render(
23 | ,
33 | mountNode,
34 | );
35 | ```
36 |
--------------------------------------------------------------------------------
/components/virtual-select/demo/search.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 1
3 | title: 带搜索框
4 | ---
5 |
6 | 展开后可对选项进行搜索。
7 |
8 | ```jsx
9 | import { VirtualSelect as Select } from 'antd-advanced';
10 |
11 | const { Option } = Select;
12 |
13 | function onChange(value) {
14 | console.log(`selected ${value}`);
15 | }
16 |
17 | function onBlur() {
18 | console.log('blur');
19 | }
20 |
21 | function onFocus() {
22 | console.log('focus');
23 | }
24 |
25 | function onSearch(val) {
26 | console.log('search:', val);
27 | }
28 |
29 | ReactDOM.render(
30 | ,
47 | mountNode,
48 | );
49 | ```
50 |
--------------------------------------------------------------------------------
/components/virtual-select/demo/theme.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 3
3 | title: 暗黑主题
4 | ---
5 |
6 | 暗黑主题
7 |
8 | ```jsx
9 | import { VirtualSelect as Select } from 'antd-advanced';
10 |
11 | const { Option } = Select;
12 |
13 | const children = [];
14 | for (let i = 10; i < 36; i++) {
15 | children.push();
16 | }
17 |
18 | function handleChange(value) {
19 | console.log(`selected ${value}`);
20 | }
21 |
22 | ReactDOM.render(
23 |
24 |
34 |
42 |
,
43 | mountNode,
44 | );
45 | ```
46 |
--------------------------------------------------------------------------------
/components/virtual-select/index.tsx:
--------------------------------------------------------------------------------
1 | import './style/index.less';
2 | import VirtualSelect from './VirtualSelect';
3 |
4 | export default VirtualSelect;
5 |
--------------------------------------------------------------------------------
/components/virtual-select/style/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.less';
2 |
--------------------------------------------------------------------------------
/docs/develop.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 1
3 | title: 如何开发一个组件?
4 | ---
5 |
6 | ## 注意事项
7 |
8 | - 组件,以及文档的文件命名,需要使用「小写」+「-」的形式,否则会有识别问题。
9 | - git分支规范参考 [git-commit-style-guide](https://github.com/feflow/git-commit-style-guide/blob/master/doc/GIT_COMMIT_STANDARD.md)
10 |
11 | ## 组件
12 |
13 | ### 开发
14 |
15 | 在 `src` 目录下,新增一个组件的目录,类似上面的 `empty-line` 组件。目录名需要保持**小写**。如果是自定义组件,最好需要取一个 `antd` 中所不包含的组件名称。添加完文件之后,在 `entry/config.tsx` 中增加 `demo` 的配置。此时应该可以看到组件,并继续开发了。
16 |
17 | ### 样式
18 |
19 | 命名规范主要参考 antd 的样式命名,一种类似 [BEM](http://getbem.com/) 的方式
20 |
21 | ##### 设置样式前缀
22 |
23 | 需要增加 `dantd` 前缀,组件库前缀不应该与 `antd` 重名。
24 |
25 | ```less
26 | @dantd-table-prefix-cls: ~'@{dantd-prefix}-table';
27 |
28 | .@{dantd-table-prefix-cls} {
29 | &-header {
30 | ...
31 | }
32 | ...
33 | }
34 | ```
35 |
36 | ##### 使用 `ant` 的 `less` 变量
37 |
38 | 引入变量,使用即可
39 |
40 | ```less
41 | @import '~antd/es/style/themes/default.less';
42 | ...
43 |
44 | .active {
45 | color: @primary-color;
46 | }
47 | ```
48 |
49 | ##### 国际化
50 |
51 | > 类组件使用国际化的方式待补充
52 |
53 | 组件库中提供了两个国际化工具,自定义hooks `useIntl` ,高阶组件 `withIntl` 。使用方式如下:
54 |
55 | ```
56 | // useIntl
57 | import { useIntl } from '../locale-provider';
58 | const { t } = useIntl();
59 | t('xxx.xxx');
60 | ```
61 |
62 | ```
63 | // withIntl
64 | import { withIntl } from '../locale-provider';
65 |
66 | props.t('xxx.xxx')
67 |
68 | export default withIntl(Comp);
69 | ```
70 |
71 | 然后在 `../locale` 补充文案即可。
72 |
73 | ### 测试
74 |
75 | 测试文件需要保持 `.test.tsx` 的后缀。相关技术栈以及文档:
76 |
77 | - [Jest](https://jestjs.io/):JavaScript 测试框架。
78 | - [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro):将 React 组件转化成 Dom 节点来测试,而不是渲染的 React 组件的实例,可以当做是 [Enzyme](http://airbnb.io/enzyme/) 的替代。编写测试脚本,并保证希望测试到的地方已经覆盖。
79 |
80 | 更多请参考:[使用 React Testing Library 和 Jest 完成单元测试](https://juejin.im/post/6844904095682134029)
81 |
82 | ### 发布
83 |
84 | ```
85 | $ npm run build
86 | $ npm login
87 | $ npm version patch
88 | $ git push
89 | $ npm publish
90 | ```
91 |
92 | # 文档
93 |
94 | ### 开发文档
95 |
96 | 在组件的 `demo` 文件夹中添加 `.md` 文件开发文档,并运行命令查看效果
97 |
98 | ```
99 | npm start
100 | ```
101 |
102 | ### 构建文档
103 |
104 | ```
105 | npm run site
106 | ```
107 |
108 | ### 发布文档
109 |
110 | ```
111 | npm run deploy
112 | ```
113 |
--------------------------------------------------------------------------------
/docs/i18n.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 3
3 | title: 国际化
4 | ---
5 |
6 |
7 | `dantd` 和 `antd`保持一致,默认文案是英文,如果需要使用其他语言,可以参考下面的方案。
8 |
9 | ## IntlProvider
10 |
11 | ```
12 | import {IntlProvider as DantdIntlProvider} from 'antd-advanced';
13 |
14 | const language = isEn ? 'en-US' : 'zh-CN';
15 |
16 | return (
17 |
18 |
19 |
20 | );
21 | ```
22 |
23 | 目前支持以下语言:
24 |
25 | | 语言 | key |
26 | | :-- | :-- |
27 | | 英文(默认) | en-US |
28 | | 中文 | zh-CN |
--------------------------------------------------------------------------------
/docs/introduce.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 0
3 | title: Dantd of React
4 | ---
5 |
6 | `dantd` 一个基于 [Antd-v3](https://ant.design/) 所封装的业务组件库
7 |
8 | ## ✨ 特性
9 |
10 | - 📦 开箱即用的高质量 React 组件。
11 | - 🛡 使用 TypeScript 开发,提供完整的类型定义文件。
12 | - 🌈 赋能业务开发,提供业务常用,而 `Antd` 没有提供的组件。
13 |
14 | ## 安装
15 |
16 | ```
17 | $ npm install antd-advanced
18 | ```
19 |
20 | ## 示例
21 |
22 | ```jsx
23 | import { EmptyLine } from 'antd-advanced';
24 |
25 | ReactDOM.render(, mountNode);
26 | ```
27 |
28 | ## 博客
29 |
30 | - [手摸手,打造属于自己的 React 组件库 —— 基础篇](https://juejin.im/post/6844904054347268103)
31 | - [手摸手,打造属于自己的 React 组件库 —— 测试篇](https://juejin.im/post/6844904054351462408)
32 | - [手摸手,打造属于自己的 React 组件库 —— 打包篇](https://juejin.im/post/6844904054351462413)
33 |
--------------------------------------------------------------------------------
/docs/theme.md:
--------------------------------------------------------------------------------
1 | ---
2 | order: 4
3 | title: 动态主题
4 | ---
5 |
6 | 简单几步可以实现 `Antd` 动态主题,此方法适用于 [Antd-v3](https://ant.design/) 版本。
7 |
8 | ### antd 主题切换
9 |
10 | antd 中的动态主题能力来自于 [antd-theme-webpack-plugin](https://github.com/mzohaibqc/antd-theme-webpack-plugin)
11 |
12 | > 之前有提到 `url(some url)` 的问题已经在最新版本修复,可以放心使用了~
13 | > `dist/theme` 目录,也移除了自定义插件。
14 |
15 | #### install
16 |
17 | ```bash
18 | npm install antd-theme-webpack-plugin@^1.3.3
19 | ```
20 |
21 | #### config-overrides.js
22 |
23 | ```js
24 | // config-overrides.js
25 | const AntDesignThemePlugin = require('antd-theme-webpack-plugin');
26 | ...
27 | const options = {
28 | antDir: path.join(__dirname, './node_modules/antd'),
29 | stylesDir: path.join(__dirname, './src'),
30 | varFile: path.join(__dirname, './src/variables.less'),
31 | mainLessFile: path.join(__dirname, './src/style.less'),
32 | themeVariables: ['@primary-color'],
33 | indexFileName: 'index.html',
34 | generateOnce: false, // generate color.less on each compilation
35 | };
36 | ...
37 | addWebpackPlugin(new AntDesignThemePlugin(options))
38 | ```
39 | 插件的参数可以参考原来的文档:[antd-theme-webpack-plugin](https://github.com/mzohaibqc/antd-theme-webpack-plugin)。
40 |
41 | 这里在设置参数的时候,有几个需要**注意**的地方:
42 |
43 | - `stylesDir` 需要包含本地开发的所有的样式文件。
44 | - `mainLessFile` 需要引入所有 `Antd` 的变量,比如下面的例子:
45 |
46 | ```js
47 | // antd variables
48 | @import '~antd/es/style/themes/default.less';
49 | // dantd variables
50 | @import '~antd-advanced/es/style/themes.less';
51 | ```
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | transform: {
4 | '^.+\\.tsx?$': 'ts-jest',
5 | },
6 | globals: {
7 | 'ts-jest': {
8 | babelConfig: {
9 | presets: ['@babel/preset-env', '@babel/preset-react'],
10 | },
11 | },
12 | },
13 | testEnvironment: 'jsdom',
14 | testMatch: [
15 | '/components/**/__tests__/**/*.{ts,tsx,js,jsx,mjs}',
16 | '/components/**/?(*.)(spec|test).{ts,tsx,js,jsx,mjs}',
17 | ],
18 | reporters: ['default', 'jest-html-reporters'],
19 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
20 | collectCoverage: true,
21 | collectCoverageFrom: ['app/react/**/*.{ts,tsx}', '!app/react/__tests__/api/api-test-helpers.ts'],
22 | };
23 |
--------------------------------------------------------------------------------
/jest.setup.js:
--------------------------------------------------------------------------------
1 | jest.setTimeout(100000);
2 |
--------------------------------------------------------------------------------
/scripts/changeLess2Css.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const klawSync = require('klaw-sync');
3 | const fs = require('fs');
4 |
5 | const filesRegex = /(.js|.d.ts)$/;
6 |
7 | const fileFilterFn = item => {
8 | const basename = path.basename(item.path);
9 | return filesRegex.test(basename) || basename.indexOf('.') < 0;
10 | };
11 |
12 | const esPaths = klawSync(path.resolve(__dirname, '../es'), {
13 | filter: fileFilterFn,
14 | nodir: true,
15 | }).map(item => item.path);
16 |
17 | const libPaths = klawSync(path.resolve(__dirname, '../lib'), {
18 | filter: fileFilterFn,
19 | nodir: true,
20 | }).map(item => item.path);
21 |
22 | const allPaths = esPaths.concat(libPaths);
23 |
24 | allPaths.forEach(fileItem => {
25 | const fileContent = fs.readFileSync(fileItem, 'utf8');
26 | const newFileContent = fileContent.replace(/.less/gi, '.css');
27 | fs.writeFileSync(fileItem, newFileContent, 'utf8');
28 | });
29 |
30 | console.log('.less => .css 文件后缀改写成功!');
--------------------------------------------------------------------------------
/scripts/copyStatic.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const shell = require('shelljs');
3 |
4 | const staticPath = path.join(process.cwd(), 'static');
5 | const distDir = path.resolve(process.cwd(), '_site/static');
6 |
7 | shell.mkdir('-p', distDir);
8 | shell.cp('-R', `${staticPath}/*`, distDir);
9 |
--------------------------------------------------------------------------------
/scripts/deploy.js:
--------------------------------------------------------------------------------
1 | var ghpages = require('gh-pages');
2 |
3 | ghpages.publish('_site', function (err) {});
4 |
--------------------------------------------------------------------------------
/scripts/moveDeclare.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const klawSync = require('klaw-sync');
3 | const fs = require('fs');
4 |
5 | const declarePaths = klawSync(path.resolve(__dirname, '../dist'), {
6 | nodir: true,
7 | });
8 |
9 | declarePaths.forEach(pathItem => {
10 | if (
11 | fs.existsSync(pathItem.path) &&
12 | pathItem.path.indexOf('index.umd') < 0 &&
13 | pathItem.path.indexOf('__snapshots__') < 0 &&
14 | pathItem.path.indexOf('__tests__') < 0
15 | ) {
16 | const esPath = pathItem.path.replace('/dist', '/es');
17 | const libPath = pathItem.path.replace('/dist', '/lib');
18 | fs.copyFileSync(pathItem.path, esPath);
19 | fs.copyFileSync(pathItem.path, libPath);
20 | }
21 | });
22 |
23 | console.log('.d.ts 文件拷贝成功!');
24 |
25 | const filesRegex = /(demo.js|demo.d.ts|mock.js|mock.d.ts|test.js|test.d.ts|.snap|.md)$/;
26 |
27 | const fileFilterFn = item => {
28 | const basename = path.basename(item.path);
29 | return filesRegex.test(basename) || basename.indexOf('.') < 0;
30 | };
31 |
32 | // 删除 demo,test
33 | const esPaths = klawSync(path.resolve(__dirname, '../es'), {
34 | filter: fileFilterFn,
35 | }).map(item => item.path);
36 |
37 | const libPaths = klawSync(path.resolve(__dirname, '../lib'), {
38 | filter: fileFilterFn,
39 | }).map(item => item.path);
40 |
41 | const allPaths = esPaths.concat(libPaths);
42 |
43 | allPaths.forEach(fileItem => {
44 | if (fs.existsSync(fileItem) && filesRegex.test(fileItem)) {
45 | fs.unlinkSync(fileItem);
46 | }
47 | });
48 |
49 | allPaths.forEach(fileItem => {
50 | if (
51 | fs.existsSync(fileItem) &&
52 | fs.lstatSync(fileItem).isDirectory() &&
53 | /__snapshots__$/.test(fileItem)
54 | ) {
55 | fs.rmdirSync(fileItem);
56 | }
57 | });
58 |
59 | allPaths.forEach(fileItem => {
60 | if (
61 | fs.existsSync(fileItem) &&
62 | fs.lstatSync(fileItem).isDirectory() &&
63 | /__tests__$/.test(fileItem)
64 | ) {
65 | fs.rmdirSync(fileItem);
66 | }
67 | });
68 |
69 | allPaths.forEach(fileItem => {
70 | if (fs.existsSync(fileItem) && fs.lstatSync(fileItem).isDirectory() && /demo$/.test(fileItem)) {
71 | fs.rmdirSync(fileItem);
72 | }
73 | });
74 |
75 | console.log('demo __test__ 多余文件清理成功!');
76 |
--------------------------------------------------------------------------------
/scripts/optimizeDist.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 | const klawSync = require('klaw-sync');
4 | var rimraf = require('rimraf');
5 |
6 | const distName = 'dist';
7 | const distPath = path.resolve(__dirname, `../${distName}`);
8 |
9 | const umdFilesPaths = klawSync(distPath, {
10 | depthLimit: 0,
11 | nodir: true,
12 | })
13 | .map(pathItem => pathItem.path)
14 | .filter(path => {
15 | return path.indexOf('index.umd') >= 0;
16 | });
17 |
18 | const folderPaths = klawSync(distPath, {
19 | depthLimit: 0,
20 | nofile: true,
21 | }).map(pathItem => pathItem.path);
22 |
23 | // rename umd files
24 | umdFilesPaths.forEach(umdPath => {
25 | const newPath = umdPath.replace('.umd', '');
26 | fs.renameSync(umdPath, newPath);
27 | });
28 |
29 | // remove other folders
30 | folderPaths.forEach(folderPath => {
31 | rimraf.sync(folderPath);
32 | });
33 |
34 | // theme dir
35 | const themePath = path.resolve(__dirname, `../${distName}/theme`);
36 | if (!fs.existsSync(themePath)) {
37 | fs.mkdirSync(themePath);
38 | }
39 |
40 | // 将所需要的 theme 文件复制到输出中
41 | const oldVarsPath = path.resolve(__dirname, '../site/theme/static/customer/antd-variables.less');
42 | const newVarsPath = path.resolve(__dirname, `../${distName}/theme/antd-variables.less`);
43 | fs.copyFileSync(oldVarsPath, newVarsPath);
44 |
45 | // copy scripts dist
46 | // const distThemePath = klawSync(path.resolve(__dirname, '../scripts/theme')).map(
47 | // pathItem => pathItem.path,
48 | // );
49 |
50 | // distThemePath.forEach(distItem => {
51 | // const newDistItem = distItem.replace('/scripts/theme', `/${distName}/theme`);
52 | // fs.copyFileSync(distItem, newDistItem);
53 | // });
54 | console.info('拷贝 {dist} 文件成功!');
55 |
--------------------------------------------------------------------------------
/scripts/publish.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | try {
5 | const indexPath = path.resolve(__dirname, '../.doc/index.html');
6 | let htmlContent = fs.readFileSync(indexPath, 'utf8');
7 | htmlContent = htmlContent.replace(/src="\/dantd\/static/gi, 'src="/static');
8 | htmlContent = htmlContent.replace(/href="\/dantd\/static/gi, 'href="/static');
9 |
10 | fs.writeFileSync(indexPath, htmlContent, 'utf8');
11 | } catch (error) {
12 | console.info('重写失败', error);
13 | }
14 |
15 | console.info('重写成功!');
16 |
--------------------------------------------------------------------------------
/scripts/renameEnd.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const fse = require('fs-extra');
4 |
5 | try {
6 | const srcPath = path.resolve(__dirname, '../src');
7 | const componentsPath = path.resolve(__dirname, '../components');
8 | fs.renameSync(srcPath, componentsPath);
9 |
10 | const tsconfig = path.resolve(__dirname, '../tsconfig.json');
11 | const tsconfigFather = path.resolve(__dirname, '../tsconfig.father.json');
12 | const tsconfigNormal = path.resolve(__dirname, '../tsconfig.normal.json');
13 | fs.renameSync(tsconfig, tsconfigFather);
14 | fs.renameSync(tsconfigNormal, tsconfig);
15 |
16 | console.log('Successfully renamed the directory.');
17 | } catch (err) {
18 | console.log(err);
19 | }
20 |
--------------------------------------------------------------------------------
/scripts/renameStart.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const fse = require('fs-extra');
4 |
5 | try {
6 | const srcPath = path.resolve(__dirname, '../src');
7 | const componentsPath = path.resolve(__dirname, '../components');
8 | fs.renameSync(componentsPath, srcPath);
9 |
10 | const tsconfig = path.resolve(__dirname, '../tsconfig.json');
11 | const tsconfigFather = path.resolve(__dirname, '../tsconfig.father.json');
12 | const tsconfigNormal = path.resolve(__dirname, '../tsconfig.normal.json');
13 | fs.renameSync(tsconfig, tsconfigNormal);
14 | fs.renameSync(tsconfigFather, tsconfig);
15 | console.log('Successfully renamed the directory.');
16 | } catch (err) {
17 | console.log(err);
18 | }
19 |
--------------------------------------------------------------------------------
/scripts/sync.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const klawSync = require('klaw-sync');
3 | const fs = require('fs');
4 | const fse = require('fs-extra');
5 |
6 | if (!fs.existsSync(path.resolve(__dirname, '../../dantd'))) {
7 | console.error('未找到迁移目标项目:dantd,请检查后重试');
8 | return;
9 | }
10 |
11 | // const fileReg = new RegExp('(.test.tsx|demo.tsx|mock.ts|mock.tsx)$');
12 | const fileReg = new RegExp('(.test.tsx|demo.tsx)$');
13 |
14 | const compPaths = klawSync(path.resolve(__dirname, '../src/components'), {
15 | nodir: true,
16 | })
17 | .map(item => item.path)
18 | .filter(path => !fileReg.test(path));
19 |
20 | compPaths.forEach(pathItem => {
21 | const targetPath = pathItem.replace('/dantd-demo/src/components', '/dantd/src');
22 | try {
23 | fse.copySync(pathItem, targetPath);
24 | // console.log(`copy ${pathItem} to ${targetPath} succeed!`);
25 | } catch (err) {
26 | console.error(err);
27 | }
28 | });
29 |
30 | console.log('组件同步成功!');
31 |
--------------------------------------------------------------------------------
/scripts/theme/antd-theme-webpack-plugin.js:
--------------------------------------------------------------------------------
1 | const { generateTheme } = require('./antd-theme-generator');
2 | const path = require('path');
3 |
4 | class AntDesignThemePlugin {
5 | constructor(options) {
6 | const defaulOptions = {
7 | varFile: path.join(__dirname, '../../src/styles/variables.less'),
8 | mainLessFile: path.join(__dirname, '../../src/styles/index.less'),
9 | antDir: path.join(__dirname, '../../node_modules/antd'),
10 | stylesDir: path.join(__dirname, '../../src/styles/antd'),
11 | themeVariables: ['@primary-color'],
12 | indexFileName: 'index.html',
13 | generateOnce: false,
14 | lessUrl: 'https://cdnjs.cloudflare.com/ajax/libs/less.js/2.7.2/less.min.js',
15 | publicPath: '',
16 | };
17 | this.options = Object.assign(defaulOptions, options);
18 | this.generated = false;
19 | }
20 |
21 | apply(compiler) {
22 | const options = this.options;
23 | compiler.hooks.emit.tapAsync('AntDesignThemePlugin', (compilation, callback) => {
24 | const less = `
25 |
26 |
32 |
33 | `;
34 | console.info('My AntDesignThemePlugin works.');
35 | if (options.indexFileName && options.indexFileName in compilation.assets) {
36 | const index = compilation.assets[options.indexFileName];
37 | let content = index.source();
38 |
39 | if (!content.match(/\/color\.less/g)) {
40 | index.source = () => content.replace(less, '').replace(//gi, `${less}`);
41 | content = index.source();
42 | index.size = () => content.length;
43 | }
44 | }
45 | if (options.generateOnce && this.colors) {
46 | compilation.assets['color.less'] = {
47 | source: () => this.colors,
48 | size: () => this.colors.length,
49 | };
50 | return callback();
51 | }
52 | generateTheme(options)
53 | .then(css => {
54 | if (options.generateOnce) {
55 | this.colors = css;
56 | }
57 | compilation.assets['color.less'] = {
58 | source: () => css,
59 | size: () => css.length,
60 | };
61 | callback();
62 | })
63 | .catch(err => {
64 | callback(err);
65 | });
66 | });
67 | }
68 | }
69 |
70 | module.exports = AntDesignThemePlugin;
71 |
--------------------------------------------------------------------------------
/site/bisheng.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | port: 8078,
5 | theme: './site/theme',
6 | root: '/dantd/',
7 | // root: '/',
8 | themeConfig: {
9 | logo: 'https://thedicode.github.io/dantd/static/logo.png',
10 | sitename: 'Dantd基础UI组件库',
11 | tagline: 'The one theme for bisheng',
12 | categoryOrder: {
13 | 'Ant Design': 0,
14 | 全局样式: 1,
15 | 'Global Styles': 1,
16 | 设计模式: 2,
17 | 'Design Patterns': 2,
18 | 其他: 6,
19 | Other: 6,
20 | Components: 100,
21 | 组件: 100,
22 | },
23 | typeOrder: {
24 | // Component
25 | General: 0,
26 | Layout: 1,
27 | Navigation: 2,
28 | 'Data Entry': 3,
29 | 'Data Display': 4,
30 | Feedback: 5,
31 | Other: 6,
32 | Deprecated: 7,
33 | 通用: 0,
34 | 布局: 1,
35 | 导航: 2,
36 | 数据录入: 3,
37 | 数据展示: 4,
38 | 反馈: 5,
39 | 其他: 6,
40 | 废弃: 7,
41 | // Design
42 | 原则: 1,
43 | Principles: 1,
44 | 全局规则: 2,
45 | 'Global Rules': 2,
46 | 模板文档: 3,
47 | 'Template Document': 3,
48 | },
49 | },
50 | source: {
51 | components: './components',
52 | docs: './docs',
53 | },
54 | lessConfig: {
55 | javascriptEnabled: true,
56 | },
57 | webpackConfig(config) {
58 | // const NODE_MODULES_PATH = path.resolve(__dirname, 'node_modules');
59 | config.resolve.alias = {
60 | 'antd-advanced': path.join(process.cwd(), 'components'),
61 | 'react-router': 'react-router/umd/ReactRouter',
62 | // jquery: path.resolve(NODE_MODULES_PATH, 'jquery'),
63 | };
64 | config.performance = {
65 | hints: false,
66 | maxEntrypointSize: 512000,
67 | maxAssetSize: 512000,
68 | };
69 | return config;
70 | },
71 | };
72 |
--------------------------------------------------------------------------------
/site/theme/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const homeTmpl = './template/Home/index';
4 | const contentTmpl = './template/Content/index';
5 |
6 | function pickerGenerator() {
7 | const tester = new RegExp('^docs');
8 | return markdownData => {
9 | const { filename } = markdownData.meta;
10 | if (tester.test(filename) && !/\/demo$/.test(path.dirname(filename))) {
11 | return {
12 | meta: markdownData.meta,
13 | };
14 | }
15 | return null;
16 | };
17 | }
18 |
19 | module.exports = {
20 | lazyLoad(nodePath, nodeValue) {
21 | if (typeof nodeValue === 'string') {
22 | return true;
23 | }
24 | return nodePath.endsWith('/demo');
25 | },
26 | pick: {
27 | components(markdownData) {
28 | const { filename } = markdownData.meta;
29 | if (!/^components/.test(filename) || /[/\\]demo$/.test(path.dirname(filename))) {
30 | return null;
31 | }
32 | return {
33 | meta: markdownData.meta,
34 | };
35 | },
36 | docs: pickerGenerator(),
37 | },
38 | plugins: [
39 | 'bisheng-plugin-description',
40 | 'bisheng-plugin-toc?maxDepth=2&keepElem',
41 | 'bisheng-plugin-antd',
42 | 'bisheng-plugin-react?lang=__react',
43 | ],
44 | routes: [
45 | {
46 | path: '/',
47 | component: './template/Layout/index',
48 | indexRoute: { component: homeTmpl },
49 | childRoutes: [
50 | {
51 | path: 'changelog',
52 | component: contentTmpl,
53 | },
54 | {
55 | path: 'components/:children/',
56 | component: contentTmpl,
57 | },
58 | {
59 | path: 'docs/:children',
60 | component: contentTmpl,
61 | },
62 | ],
63 | },
64 | ],
65 | };
66 |
--------------------------------------------------------------------------------
/site/theme/static/docsearch.less:
--------------------------------------------------------------------------------
1 | .algolia-autocomplete {
2 | .ds-dropdown-menu {
3 | border: none;
4 | box-shadow: @box-shadow-base;
5 |
6 | [class^='ds-dataset-'] {
7 | border: none;
8 | }
9 |
10 | &::before {
11 | display: none;
12 | }
13 | }
14 |
15 | .algolia-docsearch-suggestion--title {
16 | color: @site-text-color;
17 | }
18 |
19 | .algolia-docsearch-suggestion--highlight {
20 | color: @primary-color;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/site/theme/static/footer.less:
--------------------------------------------------------------------------------
1 |
2 | footer.dark {
3 | background-color: #000;
4 | color: rgba(255, 255, 255, 0.65);
5 | a {
6 | color: rgba(255, 255, 255, 0.9);
7 | }
8 | h2 {
9 | color: rgba(255, 255, 255, 1);
10 | & > span {
11 | color: rgba(255, 255, 255, 1);
12 | }
13 | }
14 | .bottom-bar {
15 | border-top: 1px solid rgba(255, 255, 255, 0.25);
16 | overflow: hidden;
17 | }
18 | }
19 |
20 | footer {
21 | border-top: 1px solid #e5e7eb;
22 | clear: both;
23 | font-size: 14px;
24 | background: #fff;
25 | position: relative;
26 | z-index: 100;
27 | color: #314659;
28 | box-shadow: 0 1000px 0 1000px #fff;
29 | .ant-row {
30 | text-align: center;
31 | .footer-center {
32 | display: inline-block;
33 | text-align: left;
34 | > h2 {
35 | font-size: 16px;
36 | margin: 0 auto 24px;
37 | font-weight: 500;
38 | position: relative;
39 |
40 | > .title-icon {
41 | width: 27px;
42 | margin-right: 16px;
43 | }
44 | > .anticon {
45 | font-size: 16px;
46 | position: absolute;
47 | left: -22px;
48 | top: 3px;
49 | color: #aaa;
50 | }
51 | }
52 | > div {
53 | margin: 12px 0;
54 | }
55 | }
56 | }
57 | .footer-wrap {
58 | position: relative;
59 | padding: 86px 144px 93px;
60 | }
61 |
62 | .bottom-bar {
63 | border-top: 1px solid rgba(255, 255, 255, 0.25);
64 | text-align: center;
65 | padding: 16px 144px;
66 | margin: 0;
67 | line-height: 32px;
68 | a {
69 | color: rgba(255, 255, 255, 0.65);
70 | &:hover {
71 | color: #fff;
72 | }
73 | }
74 | .translate-button {
75 | text-align: left;
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/site/theme/static/highlight.less:
--------------------------------------------------------------------------------
1 | /**
2 | * prism.js default theme for JavaScript, CSS and HTML
3 | * Based on dabblet (http://dabblet.com)
4 | * @author Lea Verou
5 | */
6 |
7 | pre code {
8 | display: block;
9 | padding: 16px 32px;
10 | color: @site-text-color;
11 | font-size: @font-size-base;
12 | font-family: 'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
13 | line-height: 2;
14 | white-space: pre;
15 | background: white;
16 | border: 1px solid #e9e9e9;
17 | border-radius: @border-radius-sm;
18 | }
19 |
20 | code[class*='language-'],
21 | pre[class*='language-'] {
22 | color: black;
23 | font-family: 'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
24 | line-height: 1.5;
25 | direction: ltr;
26 | white-space: pre;
27 | text-align: left;
28 | word-wrap: normal;
29 | word-break: normal;
30 | word-spacing: normal;
31 | -moz-tab-size: 4;
32 | -o-tab-size: 4;
33 | tab-size: 4;
34 | -webkit-hyphens: none;
35 | -moz-hyphens: none;
36 | -ms-hyphens: none;
37 | hyphens: none;
38 | background: none;
39 | }
40 |
41 | pre[class*='language-']::-moz-selection,
42 | pre[class*='language-'] ::-moz-selection,
43 | code[class*='language-']::-moz-selection,
44 | code[class*='language-'] ::-moz-selection {
45 | text-shadow: none;
46 | background: #b3d4fc;
47 | }
48 |
49 | pre[class*='language-']::selection,
50 | pre[class*='language-'] ::selection,
51 | code[class*='language-']::selection,
52 | code[class*='language-'] ::selection {
53 | text-shadow: none;
54 | background: #b3d4fc;
55 | }
56 |
57 | @media print {
58 | code[class*='language-'],
59 | pre[class*='language-'] {
60 | text-shadow: none;
61 | }
62 | }
63 |
64 | /* Code blocks */
65 | pre[class*='language-'] {
66 | margin: 16px 0;
67 | padding: 12px 20px;
68 | overflow: auto;
69 | }
70 |
71 | :not(pre) > code[class*='language-'],
72 | pre[class*='language-'] {
73 | background: #f2f4f5;
74 | }
75 |
76 | /* Inline code */
77 | :not(pre) > code[class*='language-'] {
78 | padding: 0.1em;
79 | white-space: normal;
80 | border-radius: 0.3em;
81 | }
82 |
83 | .token.comment,
84 | .token.prolog,
85 | .token.doctype,
86 | .token.cdata {
87 | color: slategray;
88 | }
89 |
90 | .token.punctuation {
91 | color: #999;
92 | }
93 |
94 | .namespace {
95 | opacity: 0.7;
96 | }
97 |
98 | .token.property,
99 | .token.tag,
100 | .token.boolean,
101 | .token.number,
102 | .token.constant,
103 | .token.symbol,
104 | .token.deleted {
105 | color: #f81d22;
106 | }
107 |
108 | .token.selector,
109 | .token.attr-name,
110 | .token.string,
111 | .token.char,
112 | .token.builtin,
113 | .token.inserted {
114 | color: #0b8235;
115 | }
116 |
117 | .token.operator,
118 | .token.entity,
119 | .token.url,
120 | .language-css .token.string,
121 | .style .token.string {
122 | color: #0b8235;
123 | }
124 |
125 | .token.atrule,
126 | .token.attr-value,
127 | .token.keyword {
128 | color: #008dff;
129 | }
130 |
131 | .token.function {
132 | color: #f81d22;
133 | }
134 |
135 | .token.regex,
136 | .token.important,
137 | .token.variable {
138 | color: #e90;
139 | }
140 |
141 | .token.important,
142 | .token.bold {
143 | font-weight: bold;
144 | }
145 | .token.italic {
146 | font-style: italic;
147 | }
148 |
149 | .token.entity {
150 | cursor: help;
151 | }
152 |
--------------------------------------------------------------------------------
/site/theme/static/icons.less:
--------------------------------------------------------------------------------
1 | ul.anticons-list {
2 | margin: 10px 0;
3 | overflow: hidden;
4 | list-style: none;
5 | li {
6 | position: relative;
7 | float: left;
8 | width: 16.66%;
9 | height: 100px;
10 | margin: 3px 0;
11 | padding: 10px 0 0;
12 | overflow: hidden;
13 | color: #555;
14 | text-align: center;
15 | list-style: none;
16 | background-color: #fff;
17 | border-radius: 4px;
18 | cursor: pointer;
19 | transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out;
20 | .anticon {
21 | margin: 12px 0 8px;
22 | font-size: 36px;
23 | transition: transform 0.3s ease-in-out;
24 | will-change: transform;
25 | }
26 |
27 | .anticon-class {
28 | display: block;
29 | font-family: 'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
30 | white-space: nowrap;
31 | text-align: center;
32 | transform: scale(0.83);
33 | .ant-badge {
34 | transition: color 0.3s ease-in-out;
35 | }
36 | }
37 |
38 | &:hover {
39 | color: #fff;
40 | background-color: @primary-color;
41 | .anticon {
42 | transform: scale(1.4);
43 | }
44 | .ant-badge {
45 | color: #fff;
46 | }
47 | }
48 |
49 | &.outlined:hover {
50 | background-color: #8ecafe;
51 | }
52 |
53 | &.copied:hover {
54 | color: rgba(255, 255, 255, 0.2);
55 | }
56 |
57 | &::after {
58 | position: absolute;
59 | top: 0;
60 | left: 0;
61 | width: 100%;
62 | height: 100%;
63 | color: #fff;
64 | line-height: 110px;
65 | text-align: center;
66 | opacity: 0;
67 | transition: all 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28);
68 | content: 'Copied!';
69 | }
70 |
71 | &.copied::after {
72 | top: -10px;
73 | opacity: 1;
74 | }
75 | }
76 | }
77 |
78 | .copied-code {
79 | padding: 2px 4px 2px;
80 | font-size: 12px;
81 | background: #f5f5f5;
82 | border-radius: 2px;
83 | }
84 |
--------------------------------------------------------------------------------
/site/theme/static/index.less:
--------------------------------------------------------------------------------
1 |
2 | @import '~antd/dist/antd.less';
3 | // @import './customer/antd-variables.less';
4 | @import './common';
5 | @import './header';
6 | @import './footer';
7 | @import './home';
8 | @import './page-nav';
9 | @import './markdown';
10 | @import './resource';
11 | @import './preview-img';
12 | @import './toc';
13 | @import './not-found';
14 | @import './highlight';
15 | @import './demo';
16 | @import './colors';
17 | @import './icons';
18 | @import './mock-browser';
19 | @import './new-version-info-modal';
20 | @import './motion';
21 | @import './responsive';
22 | @import './theme';
23 | @import './docsearch';
24 | @import './nprogress';
25 | @import './santa';
26 |
--------------------------------------------------------------------------------
/site/theme/static/mock-browser.less:
--------------------------------------------------------------------------------
1 | /* Browser mockup code
2 | * Contribute: https://gist.github.com/jarthod/8719db9fef8deb937f4f
3 | * Live example: https://updown.io
4 | */
5 |
6 | .browser-mockup {
7 | position: relative;
8 | border-top: 2em solid rgba(230, 230, 230, 0.7);
9 | border-radius: 3px 3px 0 0;
10 | box-shadow: 0 0.1em 0.5em 0 rgba(0, 0, 0, 0.28);
11 | }
12 |
13 | .browser-mockup::before {
14 | position: absolute;
15 | top: -1.25em;
16 | left: 1em;
17 | display: block;
18 | width: 0.5em;
19 | height: 0.5em;
20 | background-color: #f44;
21 | border-radius: 50%;
22 | box-shadow: 0 0 0 2px #f44, 1.5em 0 0 2px #9b3, 3em 0 0 2px #fb5;
23 | content: '';
24 | }
25 |
26 | .browser-mockup.with-tab::after {
27 | position: absolute;
28 | top: -2em;
29 | left: 5.5em;
30 | display: block;
31 | width: 20%;
32 | height: 0;
33 | border-right: 0.8em solid transparent;
34 | border-bottom: 2em solid white;
35 | border-left: 0.8em solid transparent;
36 | content: '';
37 | }
38 |
39 | .browser-mockup.with-url::after {
40 | position: absolute;
41 | top: -1.6em;
42 | left: 5.5em;
43 | display: block;
44 | width: ~'calc(100% - 6em)';
45 | height: 1.2em;
46 | background-color: white;
47 | border-radius: 2px;
48 | content: '';
49 | }
50 |
51 | .browser-mockup > * {
52 | display: block;
53 | }
54 |
--------------------------------------------------------------------------------
/site/theme/static/motion.less:
--------------------------------------------------------------------------------
1 | .motion-container {
2 | height: 190px;
3 | margin: 40px 0 20px;
4 | line-height: 190px;
5 | text-align: center;
6 | }
7 |
8 | .motion-example {
9 | display: inline-block !important;
10 | width: 180px;
11 | height: 180px;
12 | color: #fff;
13 | font-weight: bold;
14 | font-size: 18px;
15 | line-height: 180px;
16 | text-align: center;
17 | background: url(https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg) center/180px;
18 | border-radius: 8px;
19 | animation-duration: 0.5s !important;
20 | }
21 |
22 | .motion-select-wrapper {
23 | margin-bottom: 40px;
24 | text-align: center;
25 | }
26 |
27 | .motion-select {
28 | width: 180px;
29 | text-align: left;
30 | }
31 |
32 | .video-player {
33 | position: relative;
34 | max-width: 800px;
35 | &-right {
36 | float: right;
37 | width: 616px;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/site/theme/static/new-version-info-modal.less:
--------------------------------------------------------------------------------
1 | .new-version-info-modal {
2 | img {
3 | position: absolute;
4 | top: 36px;
5 | left: 34px;
6 | width: 100px;
7 | }
8 | p {
9 | margin-top: 1em;
10 | }
11 | .anticon {
12 | display: none;
13 | }
14 | .ant-confirm-body {
15 | .ant-confirm-title {
16 | font-size: 18px;
17 | }
18 |
19 | margin-left: 120px;
20 | .ant-confirm-content {
21 | margin-left: 0;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/site/theme/static/not-found.less:
--------------------------------------------------------------------------------
1 | #page-404 {
2 | position: relative;
3 | z-index: 100;
4 | width: 100%;
5 | height: ~'calc(100vh - 130px)';
6 | background-image: url('https://os.alipayobjects.com/rmsportal/NOAjOBbnYCrNzrW.jpg');
7 | background-repeat: no-repeat;
8 | background-position: center;
9 | background-size: 100%;
10 |
11 | section {
12 | position: absolute;
13 | top: 48%;
14 | left: 55%;
15 | margin: -103px 0 0 -120px;
16 | text-align: center;
17 | }
18 |
19 | h1 {
20 | color: @primary-color;
21 | font-weight: 500;
22 | font-size: 120px;
23 | }
24 |
25 | p {
26 | color: @site-text-color;
27 | font-size: 18px;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/site/theme/static/nprogress.less:
--------------------------------------------------------------------------------
1 | #nprogress {
2 | .bar {
3 | background: @primary-color;
4 | }
5 |
6 | .peg {
7 | box-shadow: 0 0 10px @primary-color, 0 0 5px @primary-color;
8 | }
9 |
10 | .spinner-icon {
11 | border-top-color: @primary-color;
12 | border-left-color: @primary-color;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/site/theme/static/page-nav.less:
--------------------------------------------------------------------------------
1 | .prev-next-nav {
2 | margin-right: 64px;
3 | margin-left: 64px;
4 | overflow: hidden;
5 | font-size: 14px;
6 | border-top: 1px solid @site-border-color-split;
7 |
8 | > .prev-page,
9 | > .next-page {
10 | float: left;
11 | width: 50%;
12 | height: 72px;
13 | line-height: 72px;
14 | text-decoration: none;
15 | }
16 |
17 | > a.prev-page {
18 | .footer-nav-icon-before {
19 | position: relative;
20 | left: 0;
21 | margin-right: 1em;
22 | color: @site-text-color-secondary;
23 | font-size: 12px;
24 | transition: all 0.3s;
25 | }
26 |
27 | .footer-nav-icon-after {
28 | display: none;
29 | }
30 |
31 | &:hover .footer-nav-icon-before {
32 | left: -3px;
33 | color: @primary-color;
34 | }
35 | }
36 |
37 | > .next-page {
38 | float: right;
39 | text-align: right;
40 |
41 | .footer-nav-icon-after {
42 | position: relative;
43 | right: 0;
44 | margin-left: 1em;
45 | color: @site-text-color-secondary;
46 | font-size: 12px;
47 | transition: all 0.3s;
48 | }
49 |
50 | .footer-nav-icon-before {
51 | display: none;
52 | }
53 |
54 | &:hover .footer-nav-icon-after {
55 | right: -3px;
56 | color: @primary-color;
57 | }
58 | }
59 |
60 | .chinese {
61 | margin-left: 0.5em;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/site/theme/static/resource.less:
--------------------------------------------------------------------------------
1 | .resource-cards {
2 | display: flex;
3 | flex-flow: wrap;
4 | width: 100%;
5 | }
6 |
7 | .resource-card {
8 | position: relative;
9 | display: flex;
10 | width: ~'calc(50% - 24px)';
11 | min-width: 400px;
12 | max-width: 500px;
13 | height: 130px;
14 | margin: 24px 24px 0 0;
15 | overflow: hidden;
16 | color: #777;
17 | font-size: 12px;
18 | border: 1px solid @border-color-base;
19 | border-radius: @border-radius-base;
20 | transition: all 0.3s ease;
21 | }
22 |
23 | .resource-card:hover {
24 | border-color: transparent;
25 | box-shadow: 0 3px 8px #d3ddeb;
26 | }
27 |
28 | .resource-card:hover .resource-card-title {
29 | color: @primary-color;
30 | }
31 |
32 | .resource-card.disabled {
33 | opacity: 0.45;
34 | pointer-events: none;
35 | }
36 |
37 | .resource-card-cover,
38 | .resource-card-icon {
39 | display: flex;
40 | flex-shrink: 0;
41 | align-items: center;
42 | justify-content: center;
43 | width: 130px;
44 | }
45 |
46 | .resource-card-cover img {
47 | width: 68px;
48 | }
49 |
50 | .resource-card-content {
51 | display: flex;
52 | flex-flow: column;
53 | justify-content: center;
54 | }
55 |
56 | .resource-card-title {
57 | display: block;
58 | margin-bottom: 6px;
59 | overflow: hidden;
60 | color: @site-text-color;
61 | font-size: 16px;
62 | line-height: 1.2;
63 | white-space: nowrap;
64 | text-overflow: ellipsis;
65 | }
66 |
67 | .resource-card-hot-badge {
68 | margin-left: 2px;
69 | padding: 0 3px;
70 | color: #fff;
71 | font-size: 12px;
72 | line-height: 20px;
73 | vertical-align: top;
74 | background: #f50;
75 | border-radius: 2px;
76 | }
77 |
78 | .resource-card-description {
79 | display: block;
80 | padding-right: 16px;
81 | color: #697b8c;
82 | }
83 |
--------------------------------------------------------------------------------
/site/theme/static/style.js:
--------------------------------------------------------------------------------
1 |
2 | import './index.less';
3 |
--------------------------------------------------------------------------------
/site/theme/static/theme.less:
--------------------------------------------------------------------------------
1 | @site-heading-color: #0d1a26;
2 | @site-text-color: #314659;
3 | @site-text-color-secondary: #697b8c;
4 | @site-border-color-split: #ebedf0;
5 |
--------------------------------------------------------------------------------
/site/theme/static/toc.less:
--------------------------------------------------------------------------------
1 | .toc {
2 | margin: 16px 0;
3 | padding-left: 0;
4 | font-size: 12px;
5 | list-style: none;
6 | border-left: 1px solid @site-border-color-split;
7 | }
8 |
9 | ul.toc > li {
10 | margin-left: 0;
11 | padding-left: 0;
12 | line-height: 1.5;
13 | list-style: none;
14 | &:not(:last-child) {
15 | margin-bottom: 4px;
16 | }
17 | }
18 |
19 | .toc li > ul {
20 | display: none;
21 | font-size: 12px;
22 | text-indent: 8px;
23 | }
24 |
25 | .toc a {
26 | display: block;
27 | width: 110px;
28 | margin-left: -1px;
29 | padding-left: 16px;
30 | overflow: hidden;
31 | color: @site-text-color;
32 | white-space: nowrap;
33 | text-overflow: ellipsis;
34 | border-left: 1px solid transparent;
35 | transition: all 0.3s ease;
36 | }
37 |
38 | .toc a:hover {
39 | color: @primary-color;
40 | }
41 |
42 | .toc a.current {
43 | color: @primary-color;
44 | border-color: @primary-color;
45 | }
46 |
47 | .toc-affix {
48 | position: absolute;
49 | top: 8px;
50 | right: 20px;
51 | .ant-affix {
52 | z-index: 9;
53 | max-height: ~'calc(100vh - 16px)';
54 | overflow: auto;
55 | background: #fff;
56 | }
57 | }
58 |
59 | .toc-affix-bottom {
60 | position: absolute;
61 | right: 20px;
62 | bottom: 88px;
63 | .ant-affix {
64 | background: #fff;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/site/theme/template/Content/PrevAndNext.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({ prev, next }) => (
4 |
5 | {prev
6 | ? React.cloneElement(prev.props.children || prev.children[0], {
7 | className: 'prev-page',
8 | })
9 | : null}
10 | {next
11 | ? React.cloneElement(next.props.children || next.children[0], {
12 | className: 'next-page',
13 | })
14 | : null}
15 |
16 | );
17 |
18 |
--------------------------------------------------------------------------------
/site/theme/template/Content/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import collect from 'bisheng/collect';
3 | import MainContent from './MainContent';
4 |
5 | // 通过 collect 将 router、markdown 数据通过 props 传给页面组件
6 | export default collect(async nextProps => {
7 | const { pathname } = nextProps.location;
8 | const pageDataPath = pathname.split('/').filter(path => !!path);
9 | // get 方法来自 https://github.com/benjycui/exist.js
10 | // 会根据访问的路径结构,去获取相同的文件夹结构
11 | // 由于路径总是小写,而文件名可能大写,这里将文件名转为小写
12 | const pageData = nextProps.utils.get(nextProps.data, pageDataPath);
13 | if (!pageData) {
14 | throw 404; // eslint-disable-line no-throw-literal
15 | }
16 | // const locale = 'zh-CN';
17 | const pageDataPromise = typeof pageData === 'function' ? pageData() : pageData.index();
18 | // 尝试读取 demo 文件夹
19 | // 由于 babel 只能在服务端运行,所以通过插件来完成
20 | const demosFetcher = nextProps.utils.get(nextProps.data, [...pageDataPath, 'demo']);
21 | if (demosFetcher) {
22 | const [localizedPageData, demos] = await Promise.all([pageDataPromise, demosFetcher()]);
23 | return { localizedPageData, demos };
24 | }
25 | return { localizedPageData: await pageDataPromise };
26 | })(MainContent);
27 |
--------------------------------------------------------------------------------
/site/theme/template/Home/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DocumentTitle from 'react-document-title';
3 | import { Link } from 'bisheng/router';
4 | import { injectIntl } from 'react-intl';
5 | import { Popover, Button, Row, Col } from 'antd';
6 | import landing from './landing.png';
7 | import quncode from './qun.jpg';
8 | import personalcode from './personal.jpg';
9 | function getStyle() {
10 | return `
11 | .main-wrapper {
12 | padding: 0;
13 | }
14 | #header {
15 | box-shadow: none;
16 | width: 100%;
17 | }
18 | #header,
19 | #header .ant-select-selection,
20 | #header .ant-menu {
21 | background: transparent;
22 | }
23 | `;
24 | }
25 |
26 | const HomePage = (props) => {
27 | const addSeparater = (str) => {
28 | const arr = str.split('|');
29 | // arr.splice(1, 0 |)
30 | return [arr[0], , arr[1]];
31 | };
32 |
33 | return (
34 |
35 |
36 |
37 |
41 |
42 |
Dantd
43 |
44 | 一个基于
45 | Antd-v3
46 | 所封装的业务组件库
47 |
48 |
49 |
50 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | 🌈 赋能业务开发,提供业务常用,而 Antd 没有直接提供的组件
60 |
61 |
62 |
63 |
联系我们
64 |
欢迎扫描下方二维码,加入我们的用户交流群,若二维码过期,可添加小客服微信zj80900111拉你入群,暗号:dantd
65 |
66 |

67 |
68 |
69 |
70 |
71 |
72 |
73 | );
74 | };
75 |
76 | export default injectIntl(HomePage);
77 |
--------------------------------------------------------------------------------
/site/theme/template/Home/landing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thedicode/dantd/169b8d30a018221f525ed69dafb633d28e1f8ea4/site/theme/template/Home/landing.png
--------------------------------------------------------------------------------
/site/theme/template/Home/personal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thedicode/dantd/169b8d30a018221f525ed69dafb633d28e1f8ea4/site/theme/template/Home/personal.jpg
--------------------------------------------------------------------------------
/site/theme/template/Home/qun.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thedicode/dantd/169b8d30a018221f525ed69dafb633d28e1f8ea4/site/theme/template/Home/qun.jpg
--------------------------------------------------------------------------------
/site/theme/template/Layout/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Row, Col } from 'antd';
3 |
4 | const Footer = () => (
5 |
10 | );
11 |
12 | export default Footer;
13 |
--------------------------------------------------------------------------------
/site/theme/template/Layout/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thedicode/dantd/169b8d30a018221f525ed69dafb633d28e1f8ea4/site/theme/template/Layout/github.png
--------------------------------------------------------------------------------
/site/theme/template/Layout/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { addLocaleData, IntlProvider } from 'react-intl';
4 | import 'moment/locale/zh-cn';
5 | import { LocaleProvider } from 'antd';
6 | import zhCN from 'antd/lib/locale-provider/zh_CN';
7 | import 'antd/dist/antd.css';
8 |
9 | import Header from './Header';
10 | import Footer from './Footer';
11 | import * as utils from '../utils';
12 | import enLocale from '../../en-US';
13 | import cnLocale from '../../zh-CN';
14 | import '../../static/style';
15 | if (typeof window !== 'undefined') {
16 | // eslint-disable-next-line global-require
17 | require('../../static/style');
18 |
19 | // Expose to iframe
20 | window.react = React;
21 | window['react-dom'] = ReactDOM;
22 | // eslint-disable-next-line global-require
23 | window.antd = require('antd');
24 | }
25 | export default class BasicLayout extends React.Component {
26 | constructor(props) {
27 | super(props);
28 | const { pathname } = props.location;
29 | const appLocale = cnLocale;
30 | addLocaleData(appLocale.data);
31 |
32 | this.state = {
33 | appLocale,
34 | };
35 | }
36 |
37 | render() {
38 | const { appLocale } = this.state;
39 | const { children, ...restProps } = this.props;
40 | return (
41 |
42 |
43 |
44 |
45 | {children}
46 |
47 |
48 |
49 |
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/site/theme/template/NotFound.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/no-danger */
2 | import React from 'react';
3 | import { Link } from 'bisheng/router';
4 | // import * as utils from './utils';
5 |
6 | export default function NotFound({ location }) {
7 | return (
8 |
9 |
10 | 404
11 |
12 | 你要找的页面不存在
13 | {/*
14 | 返回首页
15 | */}
16 |
17 |
18 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/site/theme/template/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * MarkdownData : {
3 | * meta: {
4 | * category,
5 | * type,
6 | * title,
7 | * subtitle,
8 | * filename,
9 | * }
10 | * }
11 | */
12 | /**
13 | * 生成侧边菜单
14 | * @param {Array} moduleData - 组件数据
15 | * @param {string} locale - zh-CN | en-US
16 | * @param {Object} categoryOrder - 分类及顺序,即 bisheng.config.js 中的 categoryOrder
17 | * @param {Object} typeOrder - 组件分类及顺序,即 bisheng.config.js 中的 typeOrder
18 | * @return {Array}
19 | */
20 | export function getMenuItems(moduleData, locale, categoryOrder, typeOrder) {
21 |
22 | const menuMeta = moduleData.map(item => ({
23 | ...item.meta,
24 | filename: item.meta.menuFilename || item.meta.filename,
25 | }));
26 | const menuItems = [];
27 | const sortFn = (a, b) => (a.order || 0) - (b.order || 0);
28 | // console.info('util:getMenuItems', menuMeta, moduleData, locale, categoryOrder, typeOrder)
29 | menuMeta.sort(sortFn).forEach((meta) => {
30 | if (!meta.category) {
31 | menuItems.push(meta);
32 | } else {
33 | const category = meta.category[locale] || meta.category;
34 | let group = menuItems.filter(i => i.title === category)[0];
35 | if (!group) {
36 | group = {
37 | type: 'category',
38 | title: category,
39 | children: [],
40 | order: categoryOrder[category],
41 | };
42 | menuItems.push(group);
43 | }
44 | if (meta.type) {
45 | let type = group.children.filter(i => i.title === meta.type)[0];
46 | if (!type) {
47 | type = {
48 | type: 'type',
49 | title: meta.type,
50 | children: [],
51 | order: typeOrder[meta.type],
52 | };
53 | group.children.push(type);
54 | }
55 | type.children.push(meta);
56 | } else {
57 | group.children.push(meta);
58 | }
59 | }
60 | });
61 | return menuItems
62 | .map((i) => {
63 | const res = { ...i };
64 | if (i.children) {
65 | res.children = i.children.sort(sortFn);
66 | }
67 | return res;
68 | })
69 | .sort(sortFn);
70 | }
71 |
72 | export function isZhCN(pathname) {
73 | return /-cn\/?$/.test(pathname);
74 | }
75 |
76 | /**
77 | * 将传入的路径增加语言后缀比如 button 变成 button-cn
78 | * @param {Url} path - 路径
79 | * @param {boolean} zhCN - 是否为中文
80 | * @return {string}
81 | */
82 | export function getLocalizedPathname(path, zhCN) {
83 | return path;
84 | // const pathname = path.startsWith('/') ? path : `/${path}`;
85 | // if (!zhCN) {
86 | // // to enUS
87 | // return /\/?index-cn/.test(pathname) ? '/' : pathname.replace('-cn', '');
88 | // }
89 | // if (pathname === '/') {
90 | // return '/index-cn';
91 | // }
92 | // if (pathname.endsWith('/')) {
93 | // return pathname.replace(/\/$/, '-cn/');
94 | // }
95 | // return `${pathname}-cn`;
96 | }
97 |
98 | export function isLocalStorageNameSupported() {
99 | const testKey = 'test';
100 | const storage = window.localStorage;
101 | try {
102 | storage.setItem(testKey, '1');
103 | storage.removeItem(testKey);
104 | return true;
105 | } catch (error) {
106 | return false;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/site/theme/zh-CN.js:
--------------------------------------------------------------------------------
1 | const appLocaleData = require('react-intl/locale-data/zh');
2 |
3 | module.exports = {
4 | locale: 'zh-CN',
5 | data: appLocaleData,
6 | messages: {
7 | 'app.header.search': '全文本搜索...',
8 | 'app.header.menu.home': '首页',
9 | 'app.header.menu.practice': '实践',
10 | 'app.header.menu.pattern': '模式',
11 | 'app.header.menu.components': '组件',
12 | 'app.header.menu.spec': '设计语言',
13 | 'app.header.menu.resource': '资源',
14 | 'app.header.menu.mobile': '移动版',
15 | 'app.header.menu.ecosystem': '生态',
16 | 'app.header.lang': 'English',
17 | 'app.content.edit-page': '在 GitHub 上编辑此页!',
18 | 'app.content.edit-demo': '在 GitHub 上编辑此示例!',
19 | 'app.component.examples': '代码演示',
20 | 'app.component.examples.expand': '展开全部代码',
21 | 'app.component.examples.collpse': '收起全部代码',
22 | 'app.demo.debug': '此演示仅供调试,线上不会展示',
23 | 'app.demo.copy': '复制代码',
24 | 'app.demo.copied': '复制成功',
25 | 'app.demo.code.show': '显示代码',
26 | 'app.demo.code.hide': '收起代码',
27 | 'app.demo.codepen': '在 CodePen 中打开',
28 | 'app.demo.codesandbox': '在 CodeSandbox 中打开',
29 | 'app.demo.riddle': '在 Riddle 中打开',
30 | 'app.home.slogan': '一套基于Antd@3.x开发的组件扩展包',
31 | 'app.publish.title': 'antd@3.0.0 发布!🎉 🎉 🎉',
32 | 'app.publish.greeting': '你好,',
33 | 'app.publish.intro': ' 已正式发布,欢迎升级。',
34 | 'app.publish.old-version-guide': '如果您还需要使用旧版,请查阅 ',
35 | 'app.publish.old-version-tips': ',也可通过页面右上角的文档版本选择框进行切换。',
36 | 'app.docs.color.pick-primary': '选择你的主色',
37 | 'app.docs.components.icon.search.placeholder': '在此搜索图标,点击图标可复制代码',
38 | 'app.docs.components.icon.outlined': '线框风格',
39 | 'app.docs.components.icon.filled': '实底风格',
40 | 'app.docs.components.icon.two-tone': '双色风格',
41 | 'app.docs.components.icon.category.direction': '方向性图标',
42 | 'app.docs.components.icon.category.suggestion': '提示建议性图标',
43 | 'app.docs.components.icon.category.editor': '编辑类图标',
44 | 'app.docs.components.icon.category.data': '数据类图标',
45 | 'app.docs.components.icon.category.other': '网站通用图标',
46 | 'app.docs.components.icon.category.logo': '品牌和标识',
47 | },
48 | };
49 |
--------------------------------------------------------------------------------
/static/landing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thedicode/dantd/169b8d30a018221f525ed69dafb633d28e1f8ea4/static/landing.png
--------------------------------------------------------------------------------
/static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thedicode/dantd/169b8d30a018221f525ed69dafb633d28e1f8ea4/static/logo.png
--------------------------------------------------------------------------------
/tsconfig.father.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "es6",
6 | "dom"
7 | ],
8 | "downlevelIteration": true,
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "strict": false,
13 | "noImplicitAny": false,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "noEmit": true,
19 | "jsx": "preserve",
20 | "sourceMap": true,
21 | "allowSyntheticDefaultImports": true,
22 | "moduleResolution": "node"
23 | },
24 | "exclude": [
25 | "node_modules"
26 | ],
27 | "include": [
28 | "src"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "es6",
6 | "dom"
7 | ],
8 | "downlevelIteration": true,
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "strict": false,
13 | "noImplicitAny": false,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "noEmit": true,
19 | "jsx": "preserve",
20 | "sourceMap": true,
21 | "allowSyntheticDefaultImports": true,
22 | "moduleResolution": "node"
23 | },
24 | "exclude": [
25 | "node_modules"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "es6",
6 | "dom"
7 | ],
8 | "downlevelIteration": true,
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "strict": false,
13 | "noImplicitAny": false,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "noEmit": true,
19 | "jsx": "preserve",
20 | "sourceMap": true,
21 | "allowSyntheticDefaultImports": true,
22 | "moduleResolution": "node"
23 | },
24 | "exclude": [
25 | "node_modules"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------