├── .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 |
112 | 113 | , 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 | {record.title} 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 |
105 |
106 | , 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 | {record.title} 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 | {record.title} 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 | {record.title} 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 | {record.title} 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 |
77 | 84 | 98 |
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 | {record.title} 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 |
93 | 100 |
104 | 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 |
{list.map((ele, index) => Row(index, ele))} 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 |