├── .gitignore ├── README.md ├── config ├── env.js ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── modules.js ├── paths.js ├── pnpTs.js ├── webpack.config.js └── webpackDevServer.config.js ├── debug.log ├── doczrc.js ├── gatsby-config.js ├── package.json ├── public ├── favicon.ico ├── iconLibrary.js ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── rollup.config.js ├── scripts ├── build.js ├── start.js └── test.js ├── src ├── App.scss ├── App.test.tsx ├── App.tsx ├── components │ ├── Radio │ │ ├── Api.tsx │ │ ├── Radio.mdx │ │ ├── Radio.scss │ │ ├── Radio.tsx │ │ └── RadioGroup.tsx │ ├── affix │ │ ├── Affix.mdx │ │ ├── Affix.scss │ │ ├── Affix.tsx │ │ └── Api.tsx │ ├── button │ │ ├── Api.tsx │ │ ├── Button.mdx │ │ ├── Button.scss │ │ ├── Button.tsx │ │ └── ButtonGroup.tsx │ ├── carousel │ │ ├── Api.tsx │ │ ├── Carousel.mdx │ │ ├── Carousel.scss │ │ └── Carousel.tsx │ ├── checkBox │ │ ├── Api.tsx │ │ ├── CheckBox.mdx │ │ ├── CheckBox.scss │ │ ├── CheckBox.tsx │ │ └── CheckBoxGroup.tsx │ ├── collapse │ │ ├── Api.tsx │ │ ├── Collapse.mdx │ │ ├── Collapse.scss │ │ ├── Collapse.tsx │ │ └── CollapseItem.tsx │ ├── drawer │ │ ├── Api.tsx │ │ ├── Demo.tsx │ │ ├── Drawer.mdx │ │ ├── Drawer.scss │ │ └── Drawer.tsx │ ├── gettingStarted │ │ └── Getting-Started.mdx │ ├── homePage │ │ ├── HomePage.scss │ │ └── HomePage.tsx │ ├── icon │ │ ├── AHref.tsx │ │ ├── Api.tsx │ │ ├── Icon.mdx │ │ ├── Icon.scss │ │ └── Icon.tsx │ ├── index.tsx │ ├── input │ │ ├── Api.tsx │ │ ├── Input.mdx │ │ ├── Input.scss │ │ └── Input.tsx │ ├── layout │ │ ├── Content.tsx │ │ ├── Footer.tsx │ │ ├── Header.tsx │ │ ├── Layout.mdx │ │ ├── Layout.tsx │ │ ├── Side.tsx │ │ ├── index.scss │ │ └── index.ts │ ├── message │ │ ├── Api.tsx │ │ ├── Demo.tsx │ │ ├── Message.mdx │ │ ├── Message.scss │ │ └── Message.tsx │ ├── modal │ │ ├── Api.tsx │ │ ├── Demo.tsx │ │ ├── Modal.mdx │ │ ├── Modal.scss │ │ └── Modal.tsx │ ├── notification │ │ ├── Api.tsx │ │ ├── Demo.tsx │ │ ├── Notification.mdx │ │ ├── Notification.scss │ │ └── Notification.tsx │ ├── pager │ │ ├── Api.tsx │ │ ├── Pager.mdx │ │ ├── Pager.scss │ │ └── Pager.tsx │ ├── popover │ │ ├── Api.tsx │ │ ├── Popover.mdx │ │ ├── Popover.scss │ │ └── Popover.tsx │ ├── rate │ │ ├── Api.tsx │ │ ├── Rate.mdx │ │ ├── Rate.scss │ │ └── Rate.tsx │ ├── spin │ │ ├── Api.tsx │ │ ├── Spin.mdx │ │ ├── Spin.scss │ │ └── Spin.tsx │ ├── switch │ │ ├── Api.tsx │ │ ├── Switch.mdx │ │ ├── Switch.scss │ │ └── Switch.tsx │ └── table │ │ ├── Table.scss │ │ └── Table.tsx ├── gatsby-theme-docz │ ├── components │ │ └── Logo │ │ │ └── index.js │ └── images │ │ ├── logo.png │ │ └── react-back.jpg ├── hooks │ └── useIsValidChildren.ts ├── index.css ├── index.mdx ├── index.tsx ├── logo.svg ├── react-app-env.d.ts ├── serviceWorker.ts ├── setupTests.ts ├── static │ ├── react-back.jpg │ ├── vueBack.png │ └── vueBack2.png ├── styles │ ├── _base.scss │ ├── _demo.scss │ ├── _normalize.scss │ └── index.scss ├── svg.js └── utils.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .docz 26 | 27 | .idea/* 28 | 29 | /dist/* 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | ### le-react-ui 8 | > le-react-ui是一款基于 React Hook 和 TypeScript 编写的 UI 组件库。 9 | 10 | 本组件库旨在学习React hooks以及熟悉TypeScript语法,可以作为学习项目,因此不建议在生产环境中使用。 11 | 12 | ### 特性 13 | * 基于React hooks编写 14 | * ES2015+的支持以及严格的使用TypeScript编写 15 | * 采用Rollup配置打包 16 | * 友好的 API ,自由灵活地使用空间 17 | * 源码简单简洁,阅读无障碍 18 | * 细致、漂亮的 UI 19 | 20 | ### 版本 21 | * 0.0.4 beta 22 | 23 | ### 兼容性 24 | * 基于React 17+ 25 | * 现代浏览器及IE10+ 26 | * ES2015+ 27 | 28 | ### 相关链接 29 | * [React](https://zh-hans.reactjs.org/) 30 | * [Rollup.js](https://www.rollupjs.com/) 31 | * [TypeScript](https://www.typescriptlang.org/) 32 | 33 | ### License 34 | * MIT 35 | -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | 7 | // Make sure that including paths.js after env.js will read .env variables. 8 | delete require.cache[require.resolve('./paths')]; 9 | 10 | const NODE_ENV = process.env.NODE_ENV; 11 | if (!NODE_ENV) { 12 | throw new Error( 13 | 'The NODE_ENV environment variable is required but was not specified.' 14 | ); 15 | } 16 | 17 | // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use 18 | const dotenvFiles = [ 19 | `${paths.dotenv}.${NODE_ENV}.local`, 20 | `${paths.dotenv}.${NODE_ENV}`, 21 | // Don't include `.env.local` for `test` environment 22 | // since normally you expect tests to produce the same 23 | // results for everyone 24 | NODE_ENV !== 'test' && `${paths.dotenv}.local`, 25 | paths.dotenv, 26 | ].filter(Boolean); 27 | 28 | // Load environment variables from .env* files. Suppress warnings using silent 29 | // if this file is missing. dotenv will never modify any environment variables 30 | // that have already been set. Variable expansion is supported in .env files. 31 | // https://github.com/motdotla/dotenv 32 | // https://github.com/motdotla/dotenv-expand 33 | dotenvFiles.forEach(dotenvFile => { 34 | if (fs.existsSync(dotenvFile)) { 35 | require('dotenv-expand')( 36 | require('dotenv').config({ 37 | path: dotenvFile, 38 | }) 39 | ); 40 | } 41 | }); 42 | 43 | // We support resolving modules according to `NODE_PATH`. 44 | // This lets you use absolute paths in imports inside large monorepos: 45 | // https://github.com/facebook/create-react-app/issues/253. 46 | // It works similar to `NODE_PATH` in Node itself: 47 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 48 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 49 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 50 | // https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 51 | // We also resolve them to make sure all tools using them work consistently. 52 | const appDirectory = fs.realpathSync(process.cwd()); 53 | process.env.NODE_PATH = (process.env.NODE_PATH || '') 54 | .split(path.delimiter) 55 | .filter(folder => folder && !path.isAbsolute(folder)) 56 | .map(folder => path.resolve(appDirectory, folder)) 57 | .join(path.delimiter); 58 | 59 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 60 | // injected into the application via DefinePlugin in Webpack configuration. 61 | const REACT_APP = /^REACT_APP_/i; 62 | 63 | function getClientEnvironment(publicUrl) { 64 | const raw = Object.keys(process.env) 65 | .filter(key => REACT_APP.test(key)) 66 | .reduce( 67 | (env, key) => { 68 | env[key] = process.env[key]; 69 | return env; 70 | }, 71 | { 72 | // Useful for determining whether we’re running in production mode. 73 | // Most importantly, it switches React into the correct mode. 74 | NODE_ENV: process.env.NODE_ENV || 'development', 75 | // Useful for resolving the correct path to static assets in `public`. 76 | // For example, . 77 | // This should only be used as an escape hatch. Normally you would put 78 | // images into the `src` and `import` them in code to get their paths. 79 | PUBLIC_URL: publicUrl, 80 | } 81 | ); 82 | // Stringify all values so we can feed into Webpack DefinePlugin 83 | const stringified = { 84 | 'process.env': Object.keys(raw).reduce((env, key) => { 85 | env[key] = JSON.stringify(raw[key]); 86 | return env; 87 | }, {}), 88 | }; 89 | 90 | return { raw, stringified }; 91 | } 92 | 93 | module.exports = getClientEnvironment; 94 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/en/webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const camelcase = require('camelcase'); 5 | 6 | // This is a custom Jest transformer turning file imports into filenames. 7 | // http://facebook.github.io/jest/docs/en/webpack.html 8 | 9 | module.exports = { 10 | process(src, filename) { 11 | const assetFilename = JSON.stringify(path.basename(filename)); 12 | 13 | if (filename.match(/\.svg$/)) { 14 | // Based on how SVGR generates a component name: 15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6 16 | const pascalCaseFilename = camelcase(path.parse(filename).name, { 17 | pascalCase: true, 18 | }); 19 | const componentName = `Svg${pascalCaseFilename}`; 20 | return `const React = require('react'); 21 | module.exports = { 22 | __esModule: true, 23 | default: ${assetFilename}, 24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) { 25 | return { 26 | $$typeof: Symbol.for('react.element'), 27 | type: 'svg', 28 | ref: ref, 29 | key: null, 30 | props: Object.assign({}, props, { 31 | children: ${assetFilename} 32 | }) 33 | }; 34 | }), 35 | };`; 36 | } 37 | 38 | return `module.exports = ${assetFilename};`; 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /config/modules.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const paths = require('./paths'); 6 | const chalk = require('react-dev-utils/chalk'); 7 | const resolve = require('resolve'); 8 | 9 | /** 10 | * Get additional module paths based on the baseUrl of a compilerOptions object. 11 | * 12 | * @param {Object} options 13 | */ 14 | function getAdditionalModulePaths(options = {}) { 15 | const baseUrl = options.baseUrl; 16 | 17 | // We need to explicitly check for null and undefined (and not a falsy value) because 18 | // TypeScript treats an empty string as `.`. 19 | if (baseUrl == null) { 20 | // If there's no baseUrl set we respect NODE_PATH 21 | // Note that NODE_PATH is deprecated and will be removed 22 | // in the next major release of create-react-app. 23 | 24 | const nodePath = process.env.NODE_PATH || ''; 25 | return nodePath.split(path.delimiter).filter(Boolean); 26 | } 27 | 28 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 29 | 30 | // We don't need to do anything if `baseUrl` is set to `node_modules`. This is 31 | // the default behavior. 32 | if (path.relative(paths.appNodeModules, baseUrlResolved) === '') { 33 | return null; 34 | } 35 | 36 | // Allow the user set the `baseUrl` to `appSrc`. 37 | if (path.relative(paths.appSrc, baseUrlResolved) === '') { 38 | return [paths.appSrc]; 39 | } 40 | 41 | // If the path is equal to the root directory we ignore it here. 42 | // We don't want to allow importing from the root directly as source files are 43 | // not transpiled outside of `src`. We do allow importing them with the 44 | // absolute path (e.g. `src/Components/Button.js`) but we set that up with 45 | // an alias. 46 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 47 | return null; 48 | } 49 | 50 | // Otherwise, throw an error. 51 | throw new Error( 52 | chalk.red.bold( 53 | "Your project's `baseUrl` can only be set to `src` or `node_modules`." + 54 | ' Create React App does not support other values at this time.' 55 | ) 56 | ); 57 | } 58 | 59 | /** 60 | * Get webpack aliases based on the baseUrl of a compilerOptions object. 61 | * 62 | * @param {*} options 63 | */ 64 | function getWebpackAliases(options = {}) { 65 | const baseUrl = options.baseUrl; 66 | 67 | if (!baseUrl) { 68 | return {}; 69 | } 70 | 71 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 72 | 73 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 74 | return { 75 | src: paths.appSrc, 76 | }; 77 | } 78 | } 79 | 80 | /** 81 | * Get jest aliases based on the baseUrl of a compilerOptions object. 82 | * 83 | * @param {*} options 84 | */ 85 | function getJestAliases(options = {}) { 86 | const baseUrl = options.baseUrl; 87 | 88 | if (!baseUrl) { 89 | return {}; 90 | } 91 | 92 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl); 93 | 94 | if (path.relative(paths.appPath, baseUrlResolved) === '') { 95 | return { 96 | '^src/(.*)$': '/src/$1', 97 | }; 98 | } 99 | } 100 | 101 | function getModules() { 102 | // Check if TypeScript is setup 103 | const hasTsConfig = fs.existsSync(paths.appTsConfig); 104 | const hasJsConfig = fs.existsSync(paths.appJsConfig); 105 | 106 | if (hasTsConfig && hasJsConfig) { 107 | throw new Error( 108 | 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.' 109 | ); 110 | } 111 | 112 | let config; 113 | 114 | // If there's a tsconfig.json we assume it's a 115 | // TypeScript project and set up the config 116 | // based on tsconfig.json 117 | if (hasTsConfig) { 118 | const ts = require(resolve.sync('typescript', { 119 | basedir: paths.appNodeModules, 120 | })); 121 | config = ts.readConfigFile(paths.appTsConfig, ts.sys.readFile).config; 122 | // Otherwise we'll check if there is jsconfig.json 123 | // for non TS projects. 124 | } else if (hasJsConfig) { 125 | config = require(paths.appJsConfig); 126 | } 127 | 128 | config = config || {}; 129 | const options = config.compilerOptions || {}; 130 | 131 | const additionalModulePaths = getAdditionalModulePaths(options); 132 | 133 | return { 134 | additionalModulePaths: additionalModulePaths, 135 | webpackAliases: getWebpackAliases(options), 136 | jestAliases: getJestAliases(options), 137 | hasTsConfig, 138 | }; 139 | } 140 | 141 | module.exports = getModules(); 142 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebook/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(inputPath, needsSlash) { 15 | const hasSlash = inputPath.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return inputPath.substr(0, inputPath.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${inputPath}/`; 20 | } else { 21 | return inputPath; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right 44 | 45 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mywebc/le-react/e136024161e160290b45bdafbe8036f6697d14bf/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mywebc/le-react/e136024161e160290b45bdafbe8036f6697d14bf/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve'; 2 | import postcss from 'rollup-plugin-postcss'; 3 | import commonjs from 'rollup-plugin-commonjs'; 4 | import babel from 'rollup-plugin-babel'; 5 | import typescript from '@rollup/plugin-typescript'; 6 | 7 | const extensions = ['.js', '.jsx', '.ts', '.tsx']; 8 | 9 | export default { 10 |   input: 'src/components/index.tsx', 11 |   output: [ 12 |     { 13 |       name: 'IndexList', 14 |       format: 'es', 15 |       file: 'dist/lib/leReactUI.esm.js' 16 |     }, { 17 |       name: 'IndexList', 18 |       format: 'umd', 19 |       file: 'dist/lib/leReactUI.js' 20 |     } 21 |   ], 22 |   external: ['react', 'react-dom'], 23 |   globals: { 24 |     react: 'React', 25 |     "react-dom": "ReactDOM", 26 |   }, 27 |   plugins: [ 28 |     resolve({ 29 |       mainFields: ['module', 'main', 'jsnext:main', 'browser'], 30 |       extensions 31 |     }), 32 |     babel({ 33 |       exclude: '**/node_modules/**', 34 |       runtimeHelpers: true 35 |     }), 36 |     commonjs({ 37 |       include: "node_modules/**" 38 |     }), 39 |     postcss({ 40 |       extract: true, 41 |       extensions: ['.scss'] 42 |     }), 43 |     typescript() 44 |   ], 45 | }; -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | 19 | const jest = require('jest'); 20 | const execSync = require('child_process').execSync; 21 | let argv = process.argv.slice(2); 22 | 23 | function isInGitRepository() { 24 | try { 25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); 26 | return true; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function isInMercurialRepository() { 33 | try { 34 | execSync('hg --cwd . root', { stdio: 'ignore' }); 35 | return true; 36 | } catch (e) { 37 | return false; 38 | } 39 | } 40 | 41 | // Watch unless on CI or explicitly running all tests 42 | if ( 43 | !process.env.CI && 44 | argv.indexOf('--watchAll') === -1 && 45 | argv.indexOf('--watchAll=false') === -1 46 | ) { 47 | // https://github.com/facebook/create-react-app/issues/5210 48 | const hasSourceControl = isInGitRepository() || isInMercurialRepository(); 49 | argv.push(hasSourceControl ? '--watch' : '--watchAll'); 50 | } 51 | 52 | 53 | jest.run(argv); 54 | -------------------------------------------------------------------------------- /src/App.scss: -------------------------------------------------------------------------------- 1 | .App { 2 | padding: 20px; 3 | width: 100%; 4 | height: 100%; 5 | box-sizing: border-box; 6 | } -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Button from "./components/button/Button"; 3 | import "./styles/index.scss" 4 | import "./App.scss" 5 | import Icon from "./components/icon/Icon" 6 | import Modal from "./components/modal/Modal" 7 | import Notification from "./components/notification/Notification" 8 | import Affix from "./components/affix/Affix" 9 | import Drawer from "./components/drawer/Drawer" 10 | import { Layout, Header, Footer, Content, Side } from "./components/layout" 11 | import Carousel from "./components/carousel/Carousel" 12 | import Input from "./components/input/Input" 13 | import Radio from "./components/radio/Radio"; 14 | import RadioGroup from "./components/radio/RadioGroup" 15 | import CheckBox from "./components/checkBox/CheckBox" 16 | import CheckBoxGroup from "./components/checkBox/CheckBoxGroup" 17 | import Collapse from "./components/collapse/Collapse" 18 | import CollapseItem from "./components/collapse/CollapseItem" 19 | import Pager from "./components/pager/Pager" 20 | import Popover from "./components/popover/Popover" 21 | 22 | type notificationType = "info" | "success" | "error" | "warning" | "open" 23 | 24 | const App = () => { 25 | const [visible, setVisible] = useState(false); 26 | const checkBoxOptions = ["备选项A", "备选项B", "备选项C", "备选项D"] 27 | const [defaultOptions, setDefaultOptions] = useState(["备选项A", "备选项C"]) 28 | 29 | const onClose = () => { 30 | setVisible(false) 31 | } 32 | 33 | const handleClick = () => { 34 | setVisible(true) 35 | } 36 | 37 | const handleNotification = () => { 38 | Notification.open({ 39 | message: "这是一条test" 40 | }) 41 | } 42 | 43 | const handleNotification1 = () => { 44 | Notification.success({ 45 | message: "这是一条test" 46 | }) 47 | } 48 | 49 | const handleChange = (label: string, checked: boolean) => { 50 | if (checked) { 51 | setDefaultOptions(checkBoxOptions) 52 | } else { 53 | setDefaultOptions([]) 54 | } 55 | } 56 | useEffect(() => { 57 | // console.log("defaultOptions", defaultOptions) 58 | }, [defaultOptions]) 59 | 60 | 61 | const [currentPage, setCurrentPage] = useState(1) 62 | 63 | return ( 64 |
65 | 66 | 67 | 68 | 69 | {/* { console.log("index", i) }}> 70 |
1
71 |
2
72 |
3
73 |
4
74 |
*/} 75 |

icon

76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | { console.log("change", e) }} 91 | // type={"textarea"} 92 | // prefix={"sa"} 93 | clearable 94 | /> 95 | 96 | 97 | 98 | 99 | 100 |

101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |
116 | ); 117 | } 118 | 119 | export default App; 120 | -------------------------------------------------------------------------------- /src/components/Radio/Api.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Table from "../table/Table" 3 | 4 | const columns = [ 5 | { 6 | title: '参数', 7 | dataIndex: 'name', 8 | key: 'param', 9 | }, 10 | { 11 | title: '说明', 12 | dataIndex: 'address', 13 | key: 'explain', 14 | }, 15 | { 16 | title: '类型', 17 | dataIndex: 'age', 18 | key: 'type', 19 | }, 20 | { 21 | title: '默认值', 22 | dataIndex: 'address', 23 | key: 'defaultValue', 24 | }, 25 | ]; 26 | 27 | 28 | export const Api1 = () => { 29 | 30 | const dataSource = [ 31 | { 32 | key: '1', 33 | param: 'label', 34 | explain: 'radio文本说明', 35 | type: `string`, 36 | defaultValue: "-" 37 | }, 38 | { 39 | key: '2', 40 | param: 'value', 41 | explain: 'radio的值', 42 | type: `any`, 43 | defaultValue: "-" 44 | }, 45 | { 46 | key: '3', 47 | param: 'defaultChecked ', 48 | explain: 'radio默认选中状态', 49 | type: `boolean`, 50 | defaultValue: "false" 51 | }, 52 | { 53 | key: '4', 54 | param: 'disabled', 55 | explain: '是否禁用', 56 | type: `boolean`, 57 | defaultValue: "false" 58 | }, 59 | { 60 | key: '5', 61 | param: 'name', 62 | explain: '用于分组', 63 | type: 'string', 64 | defaultValue: "-" 65 | }, 66 | { 67 | key: '6', 68 | param: 'onChange', 69 | explain: 'radio改变的回调', 70 | type: '(e: React.ChangeEvent) => void', 71 | defaultValue: "-" 72 | }, 73 | { 74 | key: '7', 75 | param: 'className', 76 | explain: '自定义 radio 类名', 77 | type: 'string', 78 | defaultValue: "-" 79 | }, 80 | { 81 | key: '8', 82 | param: 'style', 83 | explain: '自定义 radio 样式', 84 | type: 'React.CSSProperties', 85 | defaultValue: "-" 86 | }, 87 | ]; 88 | return ( 89 | 90 | ) 91 | } 92 | 93 | 94 | export const Api2 = () => { 95 | 96 | const dataSource = [ 97 | { 98 | key: '1', 99 | param: 'value', 100 | explain: 'radioGroup的默认值', 101 | type: `any`, 102 | defaultValue: "-" 103 | }, 104 | { 105 | key: '2', 106 | param: 'onChange', 107 | explain: 'radio改变的回调', 108 | type: '(e: React.ChangeEvent) => void', 109 | defaultValue: "-" 110 | }, 111 | { 112 | key: '3', 113 | param: 'className', 114 | explain: '自定义 radioGroup 类名', 115 | type: 'string', 116 | defaultValue: "-" 117 | }, 118 | { 119 | key: '4', 120 | param: 'style', 121 | explain: '自定义 radioGroup 样式', 122 | type: 'React.CSSProperties', 123 | defaultValue: "-" 124 | }, 125 | ]; 126 | return ( 127 |
128 | ) 129 | } 130 | -------------------------------------------------------------------------------- /src/components/Radio/Radio.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Radio 3 | menu: 数据录入 4 | --- 5 | 6 | import { Playground, Props } from 'docz' 7 | import Radio from "./Radio" 8 | import RadioGroup from "./RadioGroup" 9 | import { Api1, Api2 } from "./Api" 10 | 11 | ### Radio 单选框 12 |

用于在多个备选项中选中单个状态。

13 | 14 | ## 基本用法 15 |

最简单的用法

16 | 17 | 18 |
19 | 20 | 21 |
22 |
23 | 24 | ## 禁用状态 25 |

设置disabled可以禁用

26 | 27 | 28 |
29 | 30 | 31 |
32 |
33 | 34 | ## 单选项组 35 |

一组互斥的 Radio 配合使用。

36 | 37 | 38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | 48 | ## APi 49 | #### Radio 50 | 51 | 52 | #### RadioGroup 53 | 54 | -------------------------------------------------------------------------------- /src/components/Radio/Radio.scss: -------------------------------------------------------------------------------- 1 | .le-radio { 2 | display: inline-flex; 3 | justify-content: center; 4 | align-items: center; 5 | cursor: pointer; 6 | margin-right: 10px; 7 | label { 8 | display: inline-flex; 9 | padding-left: 10px; 10 | font-size: 14px; 11 | } 12 | &.le-radio-disabled { 13 | cursor: not-allowed; 14 | opacity: 0.4; 15 | input { 16 | cursor: not-allowed; 17 | pointer-events: none; 18 | } 19 | label { 20 | cursor: not-allowed; 21 | pointer-events: none; 22 | } 23 | } 24 | } 25 | 26 | .le-radio-group { 27 | 28 | } -------------------------------------------------------------------------------- /src/components/Radio/Radio.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, useCallback } from "react"; 2 | import classnames from "classnames"; 3 | 4 | import "./Radio.scss" 5 | 6 | interface IRadioProps { 7 | label: string; 8 | value?: any; 9 | defaultValue?: any; 10 | name?: string; 11 | disabled?: boolean; 12 | defaultChecked?: boolean; 13 | onChange?: (e: React.ChangeEvent) => void; 14 | className?: string; 15 | style?: React.CSSProperties; 16 | } 17 | 18 | 19 | const Radio: React.FC = memo(({ label, defaultChecked = false, disabled = false, name, onChange, value, defaultValue, className, style }) => { 20 | 21 | const classes = classnames('le-radio', className, { 22 | 'le-radio-disabled': disabled 23 | }) 24 | 25 | const handleOnChange = useCallback((e: React.ChangeEvent) => { 26 | onChange && onChange(e) 27 | }, [onChange]) 28 | 29 | return ( 30 |
31 | 32 | 33 |
34 | ) 35 | }) 36 | 37 | export default Radio -------------------------------------------------------------------------------- /src/components/Radio/RadioGroup.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, useCallback, useEffect, useState } from "react"; 2 | import classnames from "classnames"; 3 | import Radio from "./Radio" 4 | 5 | import "./Radio.scss" 6 | import { useIsValidChildren } from "../../hooks/useIsValidChildren"; 7 | 8 | interface IRadioGroupProps { 9 | value?: any; 10 | onChange?: (e: React.ChangeEvent) => void; 11 | className?: string; 12 | style?: React.CSSProperties; 13 | } 14 | 15 | 16 | const RadioGroup: React.FC = memo(({ value, onChange, children, className, style }) => { 17 | 18 | const classes = classnames('le-radio-group', className) 19 | 20 | const handleOnChange = useCallback((e: React.ChangeEvent) => { 21 | onChange && onChange(e) 22 | }, [onChange]) 23 | 24 | const { isValidChildren } = useIsValidChildren(children, Radio) 25 | 26 | return isValidChildren ? ( 27 |
28 | {React.Children.map(children, _ => { 29 | const childProps = { 30 | ...(_ as any).props, 31 | name: "group", 32 | defaultValue: value, 33 | onChange: handleOnChange 34 | } 35 | return React.cloneElement(_ as any, childProps) 36 | })} 37 |
38 | ) : null 39 | }) 40 | 41 | export default RadioGroup -------------------------------------------------------------------------------- /src/components/affix/Affix.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Affix 3 | menu: 导航 4 | --- 5 | 6 | import { Playground, Props } from 'docz' 7 | import Affix from "./Affix" 8 | import Button from "../button/Button" 9 | import Api from "./Api" 10 | 11 | ### Affix 固钉 12 |

将页面元素钉在可视范围。

13 | 14 | ## 基础用法 15 |

最基本的用法。

16 | 17 | 18 |
19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | ## 指定距离 27 |

使用offsetTop可以指定距离顶部的偏移量。

28 | 29 | 30 |
31 | 32 | 33 | 34 |
35 |
36 | 37 | ## APi 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/components/affix/Affix.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/index.scss"; 2 | 3 | .le-affix { 4 | z-index: $affixZIndex; 5 | } -------------------------------------------------------------------------------- /src/components/affix/Affix.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, memo, useCallback } from "react" 2 | import classnames from "classnames" 3 | import "./Affix.scss" 4 | 5 | interface IAffixProps { 6 | offsetTop?: number; 7 | className?: string; 8 | style?: React.CSSProperties 9 | } 10 | 11 | const Affix: React.FC = memo(({ offsetTop = 0, className, style, children }) => { 12 | const originTopRef = useRef(0) 13 | const affixRef = useRef(null) 14 | 15 | const classes = classnames("le-affix", className, {}) 16 | 17 | useEffect(() => { 18 | originTopPosition() 19 | window.addEventListener('scroll', handleScroll); 20 | return () => { 21 | window.removeEventListener('scroll', handleScroll) 22 | } 23 | }, []) 24 | 25 | const originTopPosition = useCallback(() => { 26 | if (window.scrollY === 0) { 27 | originTopRef.current = (affixRef.current as HTMLDivElement).getBoundingClientRect().top 28 | } else { 29 | const { scrollX, scrollY } = window 30 | window.scrollTo(scrollX, 0) 31 | originTopRef.current = (affixRef.current as HTMLDivElement).getBoundingClientRect().top 32 | window.scrollTo(scrollX, scrollY) 33 | } 34 | }, []) 35 | 36 | const handleScroll = useCallback(() => { 37 | const { top, bottom, left, right } = (affixRef.current as HTMLDivElement).getBoundingClientRect() 38 | if (offsetTop !== undefined && window.scrollY >= (originTopRef.current - offsetTop)) { 39 | (affixRef.current as HTMLDivElement).style.position = "fixed"; 40 | (affixRef.current as HTMLDivElement).style.width = right - left + 'px'; 41 | (affixRef.current as HTMLDivElement).style.height = bottom - top + 'px'; 42 | (affixRef.current as HTMLDivElement).style.left = left + 'px'; 43 | (affixRef.current as HTMLDivElement).style.top = offsetTop + 'px'; 44 | } else { 45 | (affixRef.current as HTMLDivElement).style.position = "static"; 46 | } 47 | }, []) 48 | 49 | return ( 50 |
51 | {children} 52 |
53 | ) 54 | }) 55 | 56 | export default Affix -------------------------------------------------------------------------------- /src/components/affix/Api.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Table from "../table/Table" 3 | 4 | const Api = () => { 5 | const columns = [ 6 | { 7 | title: '参数', 8 | dataIndex: 'name', 9 | key: 'param', 10 | }, 11 | { 12 | title: '说明', 13 | dataIndex: 'address', 14 | key: 'explain', 15 | }, 16 | { 17 | title: '类型', 18 | dataIndex: 'age', 19 | key: 'type', 20 | }, 21 | { 22 | title: '默认值', 23 | dataIndex: 'address', 24 | key: 'defaultValue', 25 | }, 26 | ]; 27 | const dataSource = [ 28 | { 29 | key: '1', 30 | param: 'offsetTop', 31 | explain: '距离窗口顶部达到指定偏移量后触发', 32 | type: `number`, 33 | defaultValue: "0" 34 | }, 35 | { 36 | key: '5', 37 | param: 'className', 38 | explain: '自定义 Affix 类名', 39 | type: 'string', 40 | defaultValue: "-" 41 | }, 42 | { 43 | key: '6', 44 | param: 'style', 45 | explain: '自定义 Affix 样式', 46 | type: 'React.CSSProperties', 47 | defaultValue: "-" 48 | }, 49 | ]; 50 | return ( 51 |
52 | ) 53 | } 54 | 55 | export default Api -------------------------------------------------------------------------------- /src/components/button/Api.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Table from "../table/Table" 3 | 4 | const Api = () => { 5 | const columns = [ 6 | { 7 | title: '参数', 8 | dataIndex: 'name', 9 | key: 'param', 10 | }, 11 | { 12 | title: '说明', 13 | dataIndex: 'address', 14 | key: 'explain', 15 | }, 16 | { 17 | title: '类型', 18 | dataIndex: 'age', 19 | key: 'type', 20 | }, 21 | { 22 | title: '默认值', 23 | dataIndex: 'address', 24 | key: 'defaultValue', 25 | }, 26 | ]; 27 | const dataSource = [ 28 | { 29 | key: '1', 30 | param: 'type', 31 | explain: '按钮类型', 32 | type: `"primary" | "dashed" | "text" | "info" | "success" | "warning" | "error" | "text" | "info"`, 33 | defaultValue: "-" 34 | }, 35 | { 36 | key: '2', 37 | param: 'disabled', 38 | explain: '禁用状态', 39 | type: `boolean`, 40 | defaultValue: "false" 41 | }, 42 | { 43 | key: '3', 44 | param: 'size', 45 | explain: '按钮尺寸大小', 46 | type: `large | small`, 47 | defaultValue: "-" 48 | }, 49 | { 50 | key: '4', 51 | param: 'loading', 52 | explain: '加载状态', 53 | type: 'boolean', 54 | defaultValue: "false" 55 | }, 56 | { 57 | key: '5', 58 | param: 'className', 59 | explain: '自定义 Button 类名', 60 | type: 'string', 61 | defaultValue: "-" 62 | }, 63 | { 64 | key: '6', 65 | param: 'style', 66 | explain: '自定义 Button 样式', 67 | type: 'React.CSSProperties', 68 | defaultValue: "-" 69 | }, 70 | ]; 71 | return ( 72 |
73 | ) 74 | } 75 | 76 | export default Api -------------------------------------------------------------------------------- /src/components/button/Button.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Button 3 | menu: 通用 4 | --- 5 | 6 | import { Playground, Props } from 'docz' 7 | import Button from "./Button" 8 | import ButtonGroup from "./ButtonGroup" 9 | import Api from "./Api" 10 | 11 | ### Button 按钮 12 |

基础组件,触发业务逻辑时使用。

13 | 14 | ## 基础用法 15 |

按钮类型有:默认按钮、主按钮、虚线按钮、文字按钮以及四种颜色按钮。通过设置type为primary、dashed、text、info、success、warning、error创建不同样式的按钮,不设置为默认样式。

16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 |
29 | 30 | ## 禁用状态 31 |

按钮不可用状态。

32 | 33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 |
45 | 46 | ## 按钮尺寸 47 |

按钮有三种尺寸:大、默认(中)、小;通过设置size为large和small将按钮设置为大和小尺寸,不设置为默认(中)尺寸。

48 | 49 | 50 |
51 | 52 | 53 | 54 |
55 |
56 | 57 | ## 加载中状态 58 |

通过添加loading属性可以让按钮处于加载中状态,后两个按钮在点击时进入加载状态

59 | 60 | 61 |
62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
72 |
73 | 74 | ## 按钮组合 75 |

将多个Button放入ButtonGroup内,可实现按钮组合的效果。

76 | 77 | 78 |
79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
89 |
90 | 91 | ## APi 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/components/button/Button.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/index.scss"; 2 | 3 | // loading公共样式 4 | @mixin commonLoading { 5 | .le-loadingIndicator { 6 | width: 10px; 7 | height: 10px; 8 | display: inline-block; 9 | margin-right: 4px; 10 | border-radius: 8px; 11 | border-color: #fff #fff #fff transparent; 12 | border-style: solid; 13 | border-width: 2px; 14 | animation: le-spin 1s infinite linear; 15 | } 16 | } 17 | 18 | .le-button { 19 | box-sizing: border-box; 20 | line-height: $lineHeight; 21 | display: inline-flex; 22 | justify-content: center; 23 | align-items: center; 24 | font-size: $fontSize; 25 | border: 1px solid $border; 26 | color: $text; 27 | padding: 0.5em 1em; 28 | background-color: #fff; 29 | border-radius: $borderRadiusLight; 30 | cursor: pointer; 31 | outline: none; 32 | height: $btnHeight; 33 | 34 | &:hover { 35 | color: $primary; 36 | border: 1px solid $primary; 37 | opacity: 0.9; 38 | } 39 | 40 | &.le-btn-disabled { 41 | &:hover { 42 | border: 1px solid $disabledColor; 43 | color: $text; 44 | } 45 | } 46 | 47 | @include commonLoading; 48 | .le-loadingIndicator { 49 | border-color: $primary $primary $primary transparent; 50 | } 51 | } 52 | 53 | .le-btn-primary { 54 | background-color: $primary; 55 | color: #fff; 56 | 57 | &:hover { 58 | color: #fff; 59 | } 60 | 61 | &.le-btn-disabled { 62 | border: 1px solid $disabled; 63 | 64 | &:hover { 65 | border: 1px solid $disabled; 66 | color: #fff; 67 | } 68 | } 69 | .le-loadingIndicator { 70 | border-color: #fff #fff #fff transparent; 71 | } 72 | } 73 | 74 | .le-btn-dashed { 75 | border: 1px dashed $border; 76 | 77 | &:hover { 78 | color: $primary; 79 | border: 1px dashed $border; 80 | } 81 | 82 | &.le-btn-disabled { 83 | border: 1px dashed $disabledColor; 84 | 85 | &:hover { 86 | border: 1px dashed $disabledColor; 87 | color: $text; 88 | } 89 | } 90 | } 91 | 92 | .le-btn-success { 93 | background-color: $success; 94 | color: #fff; 95 | 96 | &:hover { 97 | color: #fff; 98 | border: 1px solid $success; 99 | } 100 | 101 | &.le-btn-disabled { 102 | border: 1px solid $disabled; 103 | 104 | &:hover { 105 | border: 1px solid $disabled; 106 | 107 | color: #fff; 108 | } 109 | } 110 | .le-loadingIndicator { 111 | border-color: #fff #fff #fff transparent; 112 | } 113 | } 114 | 115 | .le-btn-warning { 116 | background-color: $warning; 117 | color: #fff; 118 | 119 | &:hover { 120 | color: #fff; 121 | border: 1px solid $warning; 122 | } 123 | 124 | &.le-btn-disabled { 125 | border: 1px solid $disabled; 126 | 127 | &:hover { 128 | border: 1px solid $disabled; 129 | 130 | color: #fff; 131 | } 132 | } 133 | .le-loadingIndicator { 134 | border-color: #fff #fff #fff transparent; 135 | } 136 | } 137 | 138 | .le-btn-error { 139 | background-color: $error; 140 | color: #fff; 141 | 142 | &:hover { 143 | color: #fff; 144 | border: 1px solid $error; 145 | } 146 | 147 | &.le-btn-disabled { 148 | border: 1px solid $disabled; 149 | 150 | &:hover { 151 | border: 1px solid $disabled; 152 | 153 | color: #fff; 154 | } 155 | } 156 | .le-loadingIndicator { 157 | border-color: #fff #fff #fff transparent; 158 | } 159 | } 160 | 161 | .le-btn-text { 162 | background-color: #fff; 163 | border: none; 164 | 165 | &:hover { 166 | color: $primary; 167 | border: none; 168 | } 169 | 170 | &.le-btn-disabled { 171 | color: $disabled; 172 | 173 | &.le-btn-disabled { 174 | color: $text; 175 | 176 | &:hover { 177 | color: $text; 178 | border: none; 179 | } 180 | } 181 | } 182 | } 183 | 184 | .le-btn-info { 185 | background-color: $info; 186 | color: #fff; 187 | 188 | &:hover { 189 | color: #fff; 190 | border: 1px solid $info; 191 | } 192 | 193 | &.le-btn-disabled { 194 | border: 1px solid $disabled; 195 | 196 | &:hover { 197 | border: 1px solid $disabled; 198 | color: #fff; 199 | } 200 | } 201 | .le-loadingIndicator { 202 | border-color: #fff #fff #fff transparent; 203 | } 204 | } 205 | 206 | .le-btn-disabled { 207 | opacity: 0.4; 208 | cursor: no-drop; 209 | 210 | &:hover { 211 | opacity: 0.4; 212 | } 213 | 214 | > * { 215 | pointer-events: none; 216 | } 217 | } 218 | 219 | .le-btn-small { 220 | font-size: $fontSizeSmall; 221 | line-height: $lineHeightSmall; 222 | height: $btnHeightSmall; 223 | } 224 | 225 | .le-btn-large { 226 | font-size: $fontSizeLarge; 227 | line-height: $lineHeightLarge; 228 | height: $btnHeightLarge; 229 | } 230 | 231 | .le-btn-group { 232 | display: inline-block; 233 | & > .le-button { 234 | border-top-left-radius: 0; 235 | border-bottom-left-radius: 0; 236 | border-top-right-radius: 0; 237 | border-bottom-right-radius: 0; 238 | } 239 | & > :first-child { 240 | border-top-left-radius: $borderRadiusLarge; 241 | border-bottom-left-radius: $borderRadiusLarge; 242 | border-top-right-radius: 0; 243 | border-bottom-right-radius: 0; 244 | } 245 | & > :last-child { 246 | border-top-right-radius: $borderRadiusLarge; 247 | border-bottom-right-radius: $borderRadiusLarge; 248 | border-top-left-radius: 0; 249 | border-bottom-left-radius: 0; 250 | } 251 | } 252 | 253 | .le-loadingIndicator { 254 | border-color: #fff #fff #fff transparent; 255 | } 256 | -------------------------------------------------------------------------------- /src/components/button/Button.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from "react" 2 | import classnames from "classnames" 3 | import "./Button.scss" 4 | 5 | type btnType = "primary" | "dashed" | "text" | "info" | "success" | "warning" | "error" | "text" | "info"; 6 | type btnSize = "large" | "small"; 7 | 8 | interface IButtonProps { 9 | type?: btnType; 10 | size?: btnSize; 11 | disabled?: boolean; 12 | loading?: boolean; 13 | onClick?: React.MouseEventHandler 14 | onMouseEnter?: React.MouseEventHandler 15 | onMouseLeave?: React.MouseEventHandler 16 | onFocus?: React.FocusEventHandler 17 | onBlur?: React.FocusEventHandler 18 | className?: string; 19 | style?: React.CSSProperties 20 | } 21 | 22 | const Button: React.FC = memo(({ type, className, disabled = false, size, loading = false, style, children, ...rest }) => { 23 | const classes = classnames("le-button", className, { 24 | [`le-btn-${type}`]: type, 25 | "le-btn-disabled": disabled, 26 | [`le-btn-${size}`]: size, 27 | }) 28 | const loadingClasses = classnames({ 29 | "le-loadingIndicator": loading 30 | }) 31 | 32 | return ( 33 | 37 | ) 38 | }) 39 | 40 | export default Button; -------------------------------------------------------------------------------- /src/components/button/ButtonGroup.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import "./Button.scss" 3 | 4 | const ButtonGroup: React.FC = (props) => { 5 | 6 | return ( 7 |
8 | {props.children} 9 |
10 | ) 11 | } 12 | 13 | export default ButtonGroup; -------------------------------------------------------------------------------- /src/components/carousel/Api.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Table from "../table/Table" 3 | 4 | export const Api = () => { 5 | const columns = [ 6 | { 7 | title: '参数', 8 | dataIndex: 'name', 9 | key: 'param', 10 | }, 11 | { 12 | title: '说明', 13 | dataIndex: 'address', 14 | key: 'explain', 15 | }, 16 | { 17 | title: '类型', 18 | dataIndex: 'age', 19 | key: 'type', 20 | }, 21 | { 22 | title: '默认值', 23 | dataIndex: 'address', 24 | key: 'defaultValue', 25 | }, 26 | ]; 27 | const dataSource = [ 28 | { 29 | key: '1', 30 | param: 'dots', 31 | explain: '是否显示面板指示点', 32 | type: `boolean`, 33 | defaultValue: "true" 34 | }, 35 | { 36 | key: '2', 37 | param: 'duration', 38 | explain: '自动播放间隔,设置为 0 时不自动播放', 39 | type: `number`, 40 | defaultValue: "0" 41 | }, 42 | { 43 | key: '3', 44 | param: 'afterChange', 45 | explain: '面板切换完成(动画结束)时的回调', 46 | type: `(index: number) => void`, 47 | defaultValue: "-" 48 | }, 49 | 50 | { 51 | key: '4', 52 | param: 'className', 53 | explain: '自定义 Carousel 类名', 54 | type: 'string', 55 | defaultValue: "-" 56 | }, 57 | { 58 | key: '5', 59 | param: 'style', 60 | explain: '自定义 Carousel 样式', 61 | type: 'React.CSSProperties', 62 | defaultValue: "-" 63 | }, 64 | ]; 65 | return ( 66 |
67 | ) 68 | } 69 | 70 | export const Methods = () => { 71 | const columns = [ 72 | { 73 | title: '名称', 74 | dataIndex: 'name', 75 | key: 'name', 76 | }, 77 | { 78 | title: '描述', 79 | dataIndex: 'describe', 80 | key: 'describe', 81 | }, 82 | { 83 | title: '参数', 84 | dataIndex: 'param', 85 | key: 'param', 86 | }, 87 | ]; 88 | const dataSource = [ 89 | { 90 | key: '1', 91 | name: 'goTo()', 92 | describe: '切换到指定面板', 93 | param: "slideNumber:number" 94 | }, 95 | { 96 | key: '2', 97 | name: 'next()', 98 | describe: '切换到下一面板', 99 | param: "--" 100 | }, 101 | { 102 | key: '3', 103 | name: 'prev()', 104 | describe: '切换到上一面板', 105 | param: "--" 106 | }, 107 | ]; 108 | return ( 109 |
110 | ) 111 | } 112 | -------------------------------------------------------------------------------- /src/components/carousel/Carousel.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Carousel 3 | menu: 数据展示 4 | --- 5 | 6 | import { Playground, Props } from 'docz' 7 | import Carousel from "./Carousel" 8 | import { Api, Methods } from "./Api" 9 | 10 | ### Carousel走马灯 11 |

旋转木马,一组轮播的区域。

12 | 13 | ## 基本用法 14 |

基本的用法

15 | 16 | 17 |
18 | 19 |
1
20 |
2
21 |
3
22 |
4
23 |
24 |
25 |
26 | 27 | ## 自动播放 28 |

设置 duration 开始自动播放。

29 | 30 | 31 |
32 | 33 |
1
34 |
2
35 |
3
36 |
4
37 |
38 |
39 |
40 | 41 | 42 | ## APi 43 | 44 | 45 | 46 | ## Method 47 | -------------------------------------------------------------------------------- /src/components/carousel/Carousel.scss: -------------------------------------------------------------------------------- 1 | .le-carousel { 2 | display: flex; 3 | overflow: hidden; 4 | position: relative; 5 | width: 100%; 6 | .le-carousel-container { 7 | display: flex; 8 | &.transitionTime { 9 | transition: transform 0.5s ease; 10 | } 11 | } 12 | .arrow_left { 13 | position: absolute; 14 | left: 0; 15 | top: 50%; 16 | cursor: pointer; 17 | } 18 | .arrow_right { 19 | position: absolute; 20 | right: 0; 21 | top: 50%; 22 | cursor: pointer; 23 | } 24 | .dotsWrapper { 25 | position: absolute; 26 | bottom: 15px; 27 | display: flex; 28 | left: 50%; 29 | transform: translate(-50%); 30 | .dot { 31 | display: block; 32 | background-color: #fff; 33 | opacity: 0.3; 34 | width: 16px; 35 | height: 3px; 36 | border-radius: 1px; 37 | margin: 0 2px; 38 | transition: all 0.3s ease; 39 | cursor: pointer; 40 | &.active { 41 | width: 24px; 42 | opacity: 0.9; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/components/carousel/Carousel.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, useCallback, useEffect, useRef, useState, forwardRef, useImperativeHandle } from "react" 2 | import classnames from "classnames" 3 | import "./Carousel.scss" 4 | 5 | interface ICarouselProps { 6 | dots?: boolean; 7 | duration?: number; 8 | afterChange?: (index: number) => void; 9 | className?: string; 10 | style?: React.CSSProperties; 11 | children?: React.ReactNode; 12 | } 13 | 14 | interface CarouselRef { 15 | goTo: (slide: number) => void; 16 | next: () => void; 17 | prev: () => void; 18 | } 19 | 20 | const Carousel = forwardRef(({ afterChange, dots = true, duration = 0, children, className, style }, ref) => { 21 | 22 | const [current, setCurrent] = useState(1) 23 | 24 | const [transitionTime, setTransitionTime] = useState(true) 25 | 26 | const [isTransitionRunning, setIsTransitionRunning] = useState(false) 27 | 28 | const leCarouselContainerRef = useRef(null) 29 | 30 | const currentRef = useRef(0) 31 | 32 | let autoTimer = useRef() 33 | 34 | useImperativeHandle(ref, () => ({ 35 | goTo: (number) => { goto(number) }, 36 | next: () => { goto(currentRef.current + 1) }, 37 | prev: () => { goto(currentRef.current - 1) } 38 | })); 39 | 40 | useEffect(() => { 41 | cloneNode(); 42 | goto(1); 43 | const containerRef = (leCarouselContainerRef.current as HTMLDivElement); 44 | containerRef.addEventListener('transitionend', judgeExitTransition); 45 | containerRef.addEventListener('transitionrun', judgeTransitionRun); 46 | return () => { 47 | containerRef.removeEventListener('transitionend', judgeExitTransition); 48 | containerRef.addEventListener('transitionrun', judgeTransitionRun); 49 | } 50 | }, []) 51 | 52 | useEffect(() => { 53 | duration !== 0 && autoPlay() 54 | return () => { autoTimer.current && duration !== 0 && clearInterval(autoTimer.current) } 55 | }, []) 56 | 57 | useEffect(() => { 58 | if (!children) return 59 | currentRef.current = current 60 | goto(current) 61 | if (current === 2 || current >= (children as any).length - 1) { 62 | setTransitionTime(true) 63 | } 64 | }, [current]) 65 | 66 | useEffect(() => { 67 | if (!transitionTime) { 68 | if (current >= (children as any).length + 1) { 69 | setCurrent(1) 70 | } else if (current === 0) { 71 | setCurrent((children as any).length) 72 | } 73 | } 74 | }, [transitionTime]) 75 | 76 | const judgeExitTransition = useCallback(() => { 77 | let afterIndex = currentRef.current 78 | if (currentRef.current > (children as any).length) { 79 | afterIndex = 1 80 | } 81 | afterChange && afterChange(afterIndex); 82 | setIsTransitionRunning(false) 83 | if ((currentRef.current >= (children as any).length + 1) || (currentRef.current === 0)) { 84 | setTransitionTime(false) 85 | } 86 | }, []) 87 | 88 | const judgeTransitionRun = useCallback(() => { 89 | setIsTransitionRunning(true) 90 | }, []) 91 | 92 | const cloneNode = useCallback(() => { 93 | const nodeList: HTMLElement[] = []; 94 | const containerRef = (leCarouselContainerRef.current as HTMLDivElement); 95 | containerRef.childNodes.forEach(node => { 96 | if (node.nodeType === 1) { 97 | const eleNode = node as HTMLElement 98 | nodeList.push(eleNode) 99 | } 100 | }); 101 | containerRef.append(nodeList[0].cloneNode(true)); 102 | containerRef.prepend(nodeList[nodeList.length - 1].cloneNode(true)); 103 | }, [leCarouselContainerRef.current]) 104 | 105 | const goto = useCallback((target: number) => { 106 | if (children) { 107 | (leCarouselContainerRef.current as HTMLDivElement).style.transform = `translateX(${-(100 / ((children as any).length + 2)) * target}%)` 108 | } 109 | }, []) 110 | 111 | const handleGoToTarget = useCallback((e: React.MouseEvent) => { 112 | if (isTransitionRunning) return 113 | const target = (e.target as HTMLSpanElement).getAttribute("data-id") 114 | setCurrent(Number(target)) 115 | }, []) 116 | 117 | const autoPlay = useCallback(() => { 118 | if (duration !== 0) { 119 | autoTimer.current = setInterval(() => { 120 | setCurrent(_ => _ + 1) 121 | }, duration) 122 | } 123 | }, []) 124 | 125 | const handleOnMouseEnter = useCallback(() => { 126 | if (autoTimer.current && duration !== 0) { 127 | clearInterval(autoTimer.current) 128 | autoTimer.current = undefined 129 | } 130 | }, []) 131 | const handleOnMouseLeave = useCallback(() => { 132 | autoPlay() 133 | }, []) 134 | 135 | return ( 136 |
137 |
140 | {React.Children.map(children, (Child) => Child)} 141 |
142 | {dots &&
143 | {React.Children.map(children, (_, i) => )} 149 |
} 150 |
151 | ) 152 | }) 153 | 154 | export default Carousel 155 | -------------------------------------------------------------------------------- /src/components/checkBox/Api.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Table from "../table/Table" 3 | 4 | const columns = [ 5 | { 6 | title: '参数', 7 | dataIndex: 'name', 8 | key: 'param', 9 | }, 10 | { 11 | title: '说明', 12 | dataIndex: 'address', 13 | key: 'explain', 14 | }, 15 | { 16 | title: '类型', 17 | dataIndex: 'age', 18 | key: 'type', 19 | }, 20 | { 21 | title: '默认值', 22 | dataIndex: 'address', 23 | key: 'defaultValue', 24 | }, 25 | ]; 26 | 27 | 28 | export const Api1 = () => { 29 | 30 | const dataSource = [ 31 | { 32 | key: '1', 33 | param: 'label', 34 | explain: 'checkBox文本说明', 35 | type: `string`, 36 | defaultValue: "-" 37 | }, 38 | { 39 | key: '2', 40 | param: 'defaultChecked ', 41 | explain: 'checkBox默认选中状态', 42 | type: `boolean`, 43 | defaultValue: "false" 44 | }, 45 | { 46 | key: '3', 47 | param: 'disabled', 48 | explain: '是否禁用', 49 | type: `boolean`, 50 | defaultValue: "false" 51 | }, 52 | { 53 | key: '4', 54 | param: 'indeterminate', 55 | explain: '是否为中间状态', 56 | type: 'boolean', 57 | defaultValue: "false" 58 | }, 59 | { 60 | key: '5', 61 | param: 'onChange', 62 | explain: 'checkBox改变的回调', 63 | type: '(label: string, checked: boolean) => void', 64 | defaultValue: "-" 65 | }, 66 | { 67 | key: '6', 68 | param: 'className', 69 | explain: '自定义 checkBox 类名', 70 | type: 'string', 71 | defaultValue: "-" 72 | }, 73 | { 74 | key: '7', 75 | param: 'style', 76 | explain: '自定义 checkBox 样式', 77 | type: 'React.CSSProperties', 78 | defaultValue: "-" 79 | }, 80 | ]; 81 | return ( 82 |
83 | ) 84 | } 85 | 86 | 87 | export const Api2 = () => { 88 | 89 | const dataSource = [ 90 | { 91 | key: '1', 92 | param: 'defaultValue', 93 | explain: 'checkBox的默认值', 94 | type: `string[]`, 95 | defaultValue: "-" 96 | }, 97 | { 98 | key: '2', 99 | param: 'onChange', 100 | explain: 'checkBoxGroup改变的回调', 101 | type: '(value: string[] => void', 102 | defaultValue: "-" 103 | }, 104 | { 105 | key: '3', 106 | param: 'className', 107 | explain: '自定义 checkBoxGroup 类名', 108 | type: 'string', 109 | defaultValue: "-" 110 | }, 111 | { 112 | key: '4', 113 | param: 'style', 114 | explain: '自定义 checkBoxGroup 样式', 115 | type: 'React.CSSProperties', 116 | defaultValue: "-" 117 | }, 118 | ]; 119 | return ( 120 |
121 | ) 122 | } 123 | -------------------------------------------------------------------------------- /src/components/checkBox/CheckBox.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: CheckBox 3 | menu: 数据录入 4 | --- 5 | 6 | import { Playground, Props } from 'docz' 7 | import CheckBox from "./CheckBox" 8 | import CheckBoxGroup from "./CheckBoxGroup" 9 | import { Api1, Api2 } from "./Api" 10 | 11 | 12 | ### CheckBox 复选框 13 |

在一组可选项中进行单项/多项选择时。

14 | 15 | ## 基本用法 16 |

最简单的用法

17 | 18 | 19 |
20 | 21 | 22 |
23 |
24 | 25 | ## 禁用状态 26 |

设置disabled可以禁用

27 | 28 | 29 |
30 | 31 | 32 |
33 |
34 | 35 | ## 多选框组 36 |

一组互斥的 CheckBox 配合使用。

37 | 38 | 39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 |
48 | 49 | ## APi 50 | #### Radio 51 | 52 | 53 | #### RadioGroup 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/components/checkBox/CheckBox.scss: -------------------------------------------------------------------------------- 1 | .le-checkBox { 2 | display: inline-flex; 3 | justify-content: center; 4 | align-items: center; 5 | cursor: pointer; 6 | margin-right: 10px; 7 | label { 8 | display: inline-flex; 9 | padding-left: 10px; 10 | font-size: 14px; 11 | } 12 | &.le-checkBox-disabled { 13 | cursor: not-allowed; 14 | opacity: 0.4; 15 | input { 16 | cursor: not-allowed; 17 | pointer-events: none; 18 | } 19 | label { 20 | cursor: not-allowed; 21 | pointer-events: none; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/components/checkBox/CheckBox.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, useCallback, useRef, useEffect, useContext, useState } from "react" 2 | import classnames from "classnames" 3 | import { checkBoxGroupContext } from "./CheckBoxGroup" 4 | 5 | import "./CheckBox.scss" 6 | 7 | interface ICheckBoxProps { 8 | label: string; 9 | disabled?: boolean; 10 | indeterminate?: boolean; 11 | defaultChecked?: boolean; 12 | onChange?: (label: string, checked: boolean) => void; 13 | className?: string; 14 | style?: React.CSSProperties; 15 | } 16 | 17 | const CheckBox: React.FC = memo(({ label, disabled = false, defaultChecked = false, indeterminate = false, onChange, className, style }) => { 18 | 19 | const inputRef = useRef(null) 20 | 21 | const groupDefaultValue = useContext(checkBoxGroupContext) 22 | 23 | const [isChecked, setIsChecked] = useState(defaultChecked) 24 | 25 | const classes = classnames("le-checkBox", className, { 26 | 'le-checkBox-disabled': disabled 27 | }) 28 | 29 | const handleChange = useCallback(() => { 30 | const checked = (inputRef.current as HTMLInputElement).checked; 31 | onChange && onChange(label, checked) 32 | }, []) 33 | 34 | useEffect(() => { 35 | (inputRef.current as HTMLInputElement).indeterminate = indeterminate 36 | }, [indeterminate]) 37 | 38 | useEffect(() => { 39 | if (groupDefaultValue.length !== 0) { 40 | if (groupDefaultValue.includes(label)) { 41 | setIsChecked(true) 42 | } 43 | } else { 44 | setIsChecked(false) 45 | } 46 | }, [groupDefaultValue, label, setIsChecked]) 47 | 48 | return ( 49 |
50 | 51 | 52 |
53 | ) 54 | }) 55 | 56 | export default CheckBox -------------------------------------------------------------------------------- /src/components/checkBox/CheckBoxGroup.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, useCallback, useRef } from "react" 2 | import classnames from "classnames" 3 | import CheckBox from "./CheckBox" 4 | 5 | import "./CheckBox.scss" 6 | import { useIsValidChildren } from "../../hooks/useIsValidChildren" 7 | 8 | interface ICCheckBoxGroupProps { 9 | defaultValue?: string[]; 10 | onChange?: (checked: string[]) => void; 11 | className?: string; 12 | style?: React.CSSProperties; 13 | } 14 | 15 | export const checkBoxGroupContext = React.createContext([]); 16 | 17 | const CheckBoxGroup: React.FC = memo(({ defaultValue = [], onChange, children, className, style }) => { 18 | 19 | const checkedValueRef = useRef(defaultValue) 20 | 21 | const { isValidChildren } = useIsValidChildren(children, CheckBox) 22 | 23 | const classes = classnames("le-checkBox-group", className) 24 | 25 | const handleChange = useCallback((label: string, checked: boolean) => { 26 | if (checked) { 27 | if (checkedValueRef.current.indexOf(label) === -1) { 28 | checkedValueRef.current.push(label); 29 | } 30 | } else { 31 | if (checkedValueRef.current.indexOf(label) !== -1) { 32 | checkedValueRef.current.splice( 33 | checkedValueRef.current.findIndex((_) => _ === label), 1 34 | ); 35 | } 36 | } 37 | onChange && onChange(checkedValueRef.current) 38 | }, [checkedValueRef.current]) 39 | 40 | return isValidChildren ? ( 41 | 42 |
43 | {React.Children.map(children, _ => { 44 | const childProps = { 45 | ...(_ as any).props, 46 | name: "group", 47 | defaultChecked: defaultValue?.includes((_ as any).props.label), 48 | onChange: handleChange 49 | } 50 | return React.cloneElement(_ as any, childProps) 51 | })} 52 |
53 |
54 | ) : null 55 | }) 56 | 57 | export default CheckBoxGroup 58 | -------------------------------------------------------------------------------- /src/components/collapse/Api.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Table from "../table/Table" 3 | 4 | const columns = [ 5 | { 6 | title: '参数', 7 | dataIndex: 'name', 8 | key: 'param', 9 | }, 10 | { 11 | title: '说明', 12 | dataIndex: 'address', 13 | key: 'explain', 14 | }, 15 | { 16 | title: '类型', 17 | dataIndex: 'age', 18 | key: 'type', 19 | }, 20 | { 21 | title: '默认值', 22 | dataIndex: 'address', 23 | key: 'defaultValue', 24 | }, 25 | ]; 26 | 27 | 28 | export const Api1 = () => { 29 | 30 | const dataSource = [ 31 | { 32 | key: '1', 33 | param: 'defaultSpread', 34 | explain: '默认展开选项', 35 | type: `number[],number`, 36 | defaultValue: "-" 37 | }, 38 | { 39 | key: '2', 40 | param: 'accordion', 41 | explain: '是否为手风琴模式', 42 | type: 'boolean', 43 | defaultValue: "false" 44 | }, 45 | { 46 | key: '3', 47 | param: 'onChange', 48 | explain: '激活面板时的回调', 49 | type: '(value: number[]) => void', 50 | defaultValue: "-" 51 | }, 52 | { 53 | key: '4', 54 | param: 'className', 55 | explain: '自定义 collapse 类名', 56 | type: 'string', 57 | defaultValue: "-" 58 | }, 59 | { 60 | key: '5', 61 | param: 'style', 62 | explain: '自定义 collapse 样式', 63 | type: 'React.CSSProperties', 64 | defaultValue: "-" 65 | }, 66 | ]; 67 | return ( 68 |
69 | ) 70 | } 71 | 72 | 73 | export const Api2 = () => { 74 | 75 | const dataSource = [ 76 | { 77 | key: '1', 78 | param: 'title', 79 | explain: '面板标题', 80 | type: `string`, 81 | defaultValue: "-" 82 | }, 83 | { 84 | key: '2', 85 | param: 'disabled', 86 | explain: '是否禁用', 87 | type: `boolean`, 88 | defaultValue: "false" 89 | }, 90 | { 91 | key: '3', 92 | param: 'onChange', 93 | explain: '激活面板时的回调', 94 | type: '(key: number, state: boolean) => void', 95 | defaultValue: "-" 96 | }, 97 | { 98 | key: '3', 99 | param: 'className', 100 | explain: '自定义 collapseItem 类名', 101 | type: 'string', 102 | defaultValue: "-" 103 | }, 104 | { 105 | key: '4', 106 | param: 'style', 107 | explain: '自定义 collapseItem 样式', 108 | type: 'React.CSSProperties', 109 | defaultValue: "-" 110 | }, 111 | ]; 112 | return ( 113 |
114 | ) 115 | } 116 | -------------------------------------------------------------------------------- /src/components/collapse/Collapse.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Collapse 3 | menu: 数据展示 4 | --- 5 | 6 | import { Playground, Props } from 'docz' 7 | import Collapse from "./Collapse" 8 | import CollapseItem from "./CollapseItem" 9 | import { Api1, Api2 } from "./Api" 10 | 11 | 12 | ### Collapse 折叠面板 13 |

对复杂区域进行分组和隐藏时。 手风琴是一种特殊的折叠面板,只允许单个面板展开。

14 | 15 | ## 基本用法 16 |

最简单的用法

17 | 18 | 19 |
20 | 21 | 22 | A dog is a type of domesticated animal. 23 | Known for its loyalty and faithfulness, 24 | it can be found as a welcome guest in many households across the world. 25 | 26 | 27 | A dog is a type of domesticated animal. 28 | Known for its loyalty and faithfulness, 29 | it can be found as a welcome guest in many households across the world. 30 | 31 | 32 | A dog is a type of domesticated animal. 33 | Known for its loyalty and faithfulness, 34 | it can be found as a welcome guest in many households across the world. 35 | 36 | 37 |
38 |
39 | 40 | ## 禁用状态 41 |

设置disabled可以禁用

42 | 43 | 44 |
45 | 46 | 47 | A dog is a type of domesticated animal. 48 | Known for its loyalty and faithfulness, 49 | it can be found as a welcome guest in many households across the world. 50 | 51 | 52 | A dog is a type of domesticated animal. 53 | Known for its loyalty and faithfulness, 54 | it can be found as a welcome guest in many households across the world. 55 | 56 | 57 | A dog is a type of domesticated animal. 58 | Known for its loyalty and faithfulness, 59 | it can be found as a welcome guest in many households across the world. 60 | 61 | 62 |
63 |
64 | 65 | ## 手风琴模式 66 |

手风琴模式只允许单个面板一次展开。

67 | 68 | 69 |
70 | 71 | 72 | A dog is a type of domesticated animal. 73 | Known for its loyalty and faithfulness, 74 | it can be found as a welcome guest in many households across the world. 75 | 76 | 77 | A dog is a type of domesticated animal. 78 | Known for its loyalty and faithfulness, 79 | it can be found as a welcome guest in many households across the world. 80 | 81 | 82 | A dog is a type of domesticated animal. 83 | Known for its loyalty and faithfulness, 84 | it can be found as a welcome guest in many households across the world. 85 | 86 | 87 |
88 |
89 | 90 | ## APi 91 | #### Collapse 92 | 93 | 94 | #### CollapseItem 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/components/collapse/Collapse.scss: -------------------------------------------------------------------------------- 1 | .le-collapse { 2 | border: 1px solid rgba(0, 0, 0, 0.3); 3 | border-radius: 8px; 4 | overflow: hidden; 5 | 6 | .le-collapse-item { 7 | 8 | .collapse-item-title { 9 | display: flex; 10 | justify-content: flex-start; 11 | align-items: center; 12 | line-height: 30px; 13 | padding: 12px 16px 12px 40px; 14 | background-color: rgba(0, 0, 0, 0.02); 15 | position: relative; 16 | border-bottom: 1px solid rgba(0, 0, 0, 0.3); 17 | 18 | .collapse-icon { 19 | position: absolute; 20 | left: 10px; 21 | display: inline-flex; 22 | cursor: pointer; 23 | 24 | &>svg { 25 | width: 20px; 26 | height: 20px; 27 | } 28 | } 29 | 30 | &.isLastChild { 31 | border-bottom: 0; 32 | } 33 | 34 | &.collapse-item-title-disabled { 35 | cursor: not-allowed; 36 | opacity: 0.4; 37 | 38 | .collapse-icon { 39 | cursor: not-allowed; 40 | pointer-events: none; 41 | } 42 | } 43 | } 44 | 45 | .collapse-item-content { 46 | overflow: hidden; 47 | 48 | .collapse-item-content-box { 49 | padding: 16px; 50 | color: rgba(0, 0, 0, 0.65); 51 | background-color: #fff; 52 | } 53 | 54 | &.collapse-item-content-active { 55 | animation: collapseFadeIn ease-out 1000ms; 56 | } 57 | 58 | &.collapse-item-content-leave { 59 | max-height: 0; 60 | } 61 | 62 | @keyframes collapseFadeIn { 63 | 0% { 64 | max-height: 0; 65 | } 66 | 67 | 100% { 68 | max-height: 500px; 69 | } 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/components/collapse/Collapse.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, useCallback, useState, useRef } from "react" 2 | import classnames from "classnames" 3 | import "./Collapse.scss" 4 | import { useIsValidChildren } from "../../hooks/useIsValidChildren" 5 | import CollapseItem from "./CollapseItem" 6 | 7 | interface ICollapseProps { 8 | accordion?: boolean; 9 | defaultSpread?: number[] | number; 10 | onChange?: (value: number[]) => void; 11 | style?: React.CSSProperties; 12 | className?: string; 13 | } 14 | 15 | export const CollapseContext = React.createContext(null); 16 | 17 | const Collapse: React.FC = memo(({ children, accordion = false, defaultSpread = [], onChange, className, style }) => { 18 | 19 | const changeSpread = useRef([].concat(defaultSpread as any)) 20 | const [currentSpread, setCurrentSpread] = useState(-1) 21 | 22 | const { isValidChildren } = useIsValidChildren(children, CollapseItem) 23 | 24 | const classes = classnames("le-collapse", className, { 25 | 26 | }) 27 | 28 | const handleOnChange = useCallback((key: number, status: boolean) => { 29 | if (status) { 30 | setCurrentSpread(key) 31 | if (!changeSpread.current.includes(key)) { 32 | changeSpread.current.push(key) 33 | } 34 | } else { 35 | if (changeSpread.current.includes(key)) { 36 | changeSpread.current.splice(changeSpread.current.indexOf(key), 1); 37 | } 38 | } 39 | onChange && onChange(changeSpread.current) 40 | }, []) 41 | 42 | return (isValidChildren ? 43 | (
44 | 49 | {React.Children.map(children, (_, i) => { 50 | const childProps = { 51 | ...(_ as any).props, 52 | isLastChild: i === (children as any).length - 1, 53 | itemKey: i, 54 | onChange: handleOnChange 55 | } 56 | return React.cloneElement(_ as any, childProps) 57 | })} 58 | 59 |
) : null 60 | ) 61 | }) 62 | 63 | export default Collapse -------------------------------------------------------------------------------- /src/components/collapse/CollapseItem.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, useCallback, useState, useContext, useEffect } from "react" 2 | import classnames from "classnames" 3 | import Icon from "../icon/Icon" 4 | import { CollapseContext } from "./Collapse" 5 | 6 | interface ICollapseItemProps { 7 | title: string; 8 | disabled?: boolean; 9 | isLastChild?: boolean; 10 | itemKey?: number; 11 | onChange?: (key: number, status: boolean) => void; 12 | style?: React.CSSProperties; 13 | className?: string; 14 | } 15 | 16 | 17 | const CollapseItem: React.FC = memo(({ children, title, isLastChild = false, disabled = false, itemKey = -1, onChange, style, className }) => { 18 | 19 | const [itemContentDisplay, setItemContentDisplay] = useState(false) 20 | const CollapseConfig = useContext<{ accordion: boolean, currentSpread: number, defaultSpread: number[] }>(CollapseContext); 21 | 22 | const titleClasses = classnames("collapse-item-title", className, { 23 | 'collapse-item-title-disabled': disabled, 24 | isLastChild: isLastChild && !itemContentDisplay, 25 | }) 26 | 27 | const contentClasses = classnames("collapse-item-content", { 28 | 'collapse-item-content-active': itemContentDisplay, 29 | 'collapse-item-content-leave': !itemContentDisplay, 30 | }) 31 | 32 | useEffect(() => { 33 | if (CollapseConfig.accordion) { 34 | if (CollapseConfig.defaultSpread.includes(itemKey) && CollapseConfig.defaultSpread.length === 1) { 35 | setItemContentDisplay(true) 36 | } 37 | } else if (!CollapseConfig.accordion && CollapseConfig.defaultSpread.includes(itemKey)) { 38 | setItemContentDisplay(true) 39 | } 40 | }, []) 41 | 42 | useEffect(() => { 43 | if (CollapseConfig.accordion && CollapseConfig.currentSpread !== -1) { 44 | if (CollapseConfig.currentSpread === itemKey) { 45 | setItemContentDisplay(true) 46 | } else { 47 | setItemContentDisplay(false) 48 | } 49 | } 50 | }, [CollapseConfig]) 51 | 52 | const handleClickPanel = useCallback(() => { 53 | setItemContentDisplay(_ => { 54 | onChange && onChange(itemKey, !_) 55 | return !_ 56 | }) 57 | 58 | }, []) 59 | 60 | return ( 61 |
62 |
63 | 64 | 65 | 66 | {title} 67 |
68 |
69 |
70 | {children} 71 |
72 |
73 |
74 | ) 75 | }) 76 | 77 | export default CollapseItem -------------------------------------------------------------------------------- /src/components/drawer/Api.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Table from "../table/Table" 3 | 4 | const Api = () => { 5 | const columns = [ 6 | { 7 | title: '参数', 8 | dataIndex: 'name', 9 | key: 'param', 10 | }, 11 | { 12 | title: '说明', 13 | dataIndex: 'address', 14 | key: 'explain', 15 | }, 16 | { 17 | title: '类型', 18 | dataIndex: 'age', 19 | key: 'type', 20 | }, 21 | { 22 | title: '默认值', 23 | dataIndex: 'address', 24 | key: 'defaultValue', 25 | }, 26 | ]; 27 | const dataSource = [ 28 | { 29 | key: '1', 30 | param: 'title', 31 | explain: 'drawer标题', 32 | type: `string`, 33 | defaultValue: "-" 34 | }, 35 | { 36 | key: '2', 37 | param: 'visible', 38 | explain: 'Drawer是否可见', 39 | type: `boolean`, 40 | defaultValue: "false" 41 | }, 42 | { 43 | key: '3', 44 | param: 'closable', 45 | explain: '是否显示关闭图标', 46 | type: `boolean`, 47 | defaultValue: "true" 48 | }, 49 | { 50 | key: '4', 51 | param: 'mask', 52 | explain: '是否显示遮罩层', 53 | type: 'boolean', 54 | defaultValue: "true" 55 | }, 56 | { 57 | key: '5', 58 | param: 'maskClosable', 59 | explain: '遮罩层是否支持关闭', 60 | type: 'boolean', 61 | defaultValue: "true" 62 | }, 63 | { 64 | key: '6', 65 | param: 'placement', 66 | explain: 'Drawer的方位,可选 top right bottom left', 67 | type: 'string', 68 | defaultValue: "right" 69 | }, 70 | { 71 | key: '7', 72 | param: 'onClose', 73 | explain: 'Drawer关闭回调', 74 | type: '() => void', 75 | defaultValue: "-" 76 | }, 77 | { 78 | key: '8', 79 | param: 'className', 80 | explain: '自定义 Drawer 类名', 81 | type: 'string', 82 | defaultValue: "-" 83 | }, 84 | { 85 | key: '9', 86 | param: 'style', 87 | explain: '自定义 Drawer 样式', 88 | type: 'React.CSSProperties', 89 | defaultValue: "-" 90 | }, 91 | ]; 92 | return ( 93 |
94 | ) 95 | } 96 | 97 | export default Api -------------------------------------------------------------------------------- /src/components/drawer/Demo.tsx: -------------------------------------------------------------------------------- 1 | import Button from "../button/Button" 2 | import Drawer from "./Drawer" 3 | import React, { useState } from "react" 4 | 5 | interface DemoProps { 6 | placement?: "top" | "right" | "bottom" | "left" 7 | } 8 | 9 | export const Demo: React.FC = React.memo(({ placement, children }) => { 10 | 11 | const [visible, setVisible] = useState(false) 12 | 13 | const onClose = () => { 14 | setVisible(false) 15 | } 16 | 17 | const open = () => { 18 | setVisible(true) 19 | } 20 | 21 | return ( 22 | <> 23 | 24 | 30 |

Some contents...

31 |

Some contents...

32 |

Some contents...

33 |
34 | 35 | ) 36 | }) 37 | -------------------------------------------------------------------------------- /src/components/drawer/Drawer.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Drawer 3 | menu: 反馈 4 | --- 5 | 6 | import { Playground, Props } from 'docz' 7 | import Drawer from "./Drawer" 8 | import { Demo as Button } from "./Demo" 9 | import Api from "./Api" 10 | 11 | ### Drawer 抽屉 12 |

屏幕边缘滑出的浮层面板。

13 | 14 | ## 基础用法 15 |

基础抽屉,点击触发按钮抽屉从右滑出,点击遮罩区关闭

16 | 17 | 18 |
19 | 20 | {/*...doclose*/}} 23 | visible={false} 24 | > 25 |

Some contents...

26 |

Some contents...

27 |

Some contents...

28 |
29 |
30 |
31 | 32 | ## 自定义位置 33 |

通过placement自定义设置滑出位置,可选有right,bottom,left,top,默认为right

34 | 35 | 36 |
37 | 38 | 39 | 40 | 41 | {/*...doclose*/}} 44 | visible={false} 45 | placement={"right"} 46 | > 47 |

Some contents...

48 |

Some contents...

49 |

Some contents...

50 |
51 |
52 |
53 | 54 | ## Api 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/components/drawer/Drawer.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/index.scss"; 2 | 3 | .le-drawer { 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | width: 100%; 8 | height: 100%; 9 | display: flex; 10 | justify-content: center; 11 | align-items: flex-start; 12 | z-index: $drawerZIndex; 13 | 14 | .le-drawer-content { 15 | min-width: 300px; 16 | background-color: #fff; 17 | border: 0; 18 | pointer-events: auto; 19 | box-shadow: $drawerShadow; 20 | position: absolute; 21 | animation: fadeInRight linear 300ms; 22 | .le-drawer-header { 23 | display: flex; 24 | justify-content: space-between; 25 | align-items: center; 26 | position: relative; 27 | padding: 16px 24px; 28 | border-bottom: 1px solid #f0f0f0; 29 | border-radius: 2px 2px 0 0; 30 | .le-icon { 31 | cursor: pointer; 32 | } 33 | } 34 | .le-drawer-body { 35 | -webkit-box-flex: 1; 36 | -ms-flex-positive: 1; 37 | flex-grow: 1; 38 | padding: 24px; 39 | overflow: auto; 40 | font-size: 14px; 41 | line-height: 1.5715; 42 | word-wrap: break-word; 43 | } 44 | } 45 | &.le-drawer-mask { 46 | background-color: rgba(0, 0, 0, 0.45); 47 | } 48 | &.le-drawer-right { 49 | .le-drawer-content { 50 | right: 0; 51 | height: 100%; 52 | } 53 | } 54 | &.le-drawer-left { 55 | .le-drawer-content { 56 | left: 0; 57 | height: 100%; 58 | animation: fadeInLeft linear 300ms; 59 | } 60 | } 61 | &.le-drawer-top { 62 | .le-drawer-content { 63 | top: 0; 64 | width: 100%; 65 | min-height: 200px; 66 | animation: fadeInTop linear 300ms; 67 | } 68 | } 69 | &.le-drawer-bottom { 70 | .le-drawer-content { 71 | bottom: 0; 72 | width: 100%; 73 | min-height: 200px; 74 | animation: fadeInBottom linear 300ms; 75 | } 76 | } 77 | @keyframes fadeInRight { 78 | 0% { 79 | transform: translateX(100%); 80 | } 81 | 82 | 100% { 83 | transform: translateX(0); 84 | } 85 | } 86 | @keyframes fadeInLeft { 87 | 0% { 88 | transform: translateX(-100%); 89 | } 90 | 91 | 100% { 92 | transform: translateX(0); 93 | } 94 | } 95 | @keyframes fadeInTop { 96 | 0% { 97 | transform: translateY(-100%); 98 | } 99 | 100 | 100% { 101 | transform: translateY(0); 102 | } 103 | } 104 | @keyframes fadeInBottom { 105 | 0% { 106 | transform: translateY(100%); 107 | } 108 | 109 | 100% { 110 | transform: translateY(0); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/components/drawer/Drawer.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, memo } from "react" 2 | import { judgeDOMExitAndCreateDOM } from "../../utils"; 3 | import classnames from "classnames" 4 | import ReactDOM from "react-dom"; 5 | import "./Drawer.scss" 6 | import Icon from "../icon/Icon"; 7 | 8 | interface IDrawerProps { 9 | title: string; 10 | visible: boolean; 11 | placement?: "top" | "right" | "bottom" | "left"; 12 | closable?: boolean; 13 | onClose?: () => any; 14 | mask?: boolean; 15 | maskClosable?: boolean; 16 | style?: React.CSSProperties; 17 | className?: string; 18 | } 19 | 20 | const Drawer: React.FC = memo(({ 21 | title, 22 | visible, 23 | onClose, 24 | maskClosable = true, 25 | mask = true, 26 | closable = true, 27 | placement = "right", 28 | style, 29 | className, 30 | children 31 | }) => { 32 | 33 | if(typeof document === "undefined") return
34 | 35 | const drawerRef = useRef(judgeDOMExitAndCreateDOM("le-drawer-wrapper") as HTMLDivElement); 36 | 37 | const classes = classnames("le-drawer", className, { 38 | "le-drawer-mask": mask, 39 | [`le-drawer-${placement}`]: placement 40 | }) 41 | 42 | const handleClickMask = () => { 43 | if (!maskClosable) return; 44 | onClose && onClose() 45 | } 46 | 47 | const handleClose = () => { 48 | onClose && onClose() 49 | } 50 | 51 | return visible ? ReactDOM.createPortal( 52 |
53 |
54 |
55 |
{title}
56 | {closable &&
} 57 |
58 |
{children}
59 |
60 |
, 61 | drawerRef.current 62 | ) : null 63 | }) 64 | 65 | export default Drawer 66 | -------------------------------------------------------------------------------- /src/components/gettingStarted/Getting-Started.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: 快速开始 3 | route: /gettingStarted 4 | order: 1 5 | # menu: 快速开始 6 | --- 7 | 8 | ### 安装 9 | 10 | 打开终端运行下列命令: 11 | 12 | ```js 13 | npm install le-react-ui 14 | // 或者 15 | yarn add le-react-ui 16 | ``` 17 | 18 | ### 引入组件 19 | 20 | ```js 21 | import { Button, Switch, Radio } from "le-react-ui" 22 | import "../node_modules/le-react-ui/dist/lib/leReactUI.css" 23 | ``` 24 | 25 | ### 使用 26 | 27 | ```js 28 | function App() { 29 | return ( 30 |
31 | 32 |
33 | ); 34 | } 35 | ``` 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/components/homePage/HomePage.scss: -------------------------------------------------------------------------------- 1 | .le-homePage { 2 | position: fixed; 3 | width: 100%; 4 | height: 100%; 5 | top: 0; 6 | left: 0; 7 | background: #fff; 8 | z-index: 9999; 9 | background: url("../../static/react-back.jpg"); 10 | background-size: 100% 100%; 11 | } 12 | -------------------------------------------------------------------------------- /src/components/homePage/HomePage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from "react" 2 | import "./HomePage.scss" 3 | import ReactDOM from "react-dom" 4 | 5 | type HomePageStaticMethods = { 6 | // open: Function; 7 | } 8 | 9 | const HomePage: React.FC & HomePageStaticMethods = () => { 10 | 11 | if(typeof document === "undefined") return
12 | 13 | const homePageWrapper = useRef(judgeDOMExitAndCreateDOMInner("le-home-page-wrapper")) as React.MutableRefObject 14 | 15 | const toggleHome = () => { 16 | window.location.href = window.location.origin + "/gettingStarted" 17 | // document.querySelector("#le-home-page-wrapper")?.remove() 18 | } 19 | 20 | useEffect(() => { 21 | document.body.style.overflow = "hidden" 22 | return () => { 23 | document.body.style.overflow = "auto" 24 | } 25 | }, []) 26 | 27 | return ReactDOM.createPortal( 28 |
29 | 30 |
, 31 | homePageWrapper.current 32 | ) 33 | } 34 | 35 | // HomePage.open = () => { 36 | // const homePageWrapper = judgeDOMExitAndCreateDOMInner("le-home-page-wrapper"); 37 | // ReactDOM.render(, homePageWrapper) 38 | // } 39 | 40 | 41 | const judgeDOMExitAndCreateDOMInner = (id: string) => { 42 | if(typeof document === "undefined") return 43 | let domWrapper = document.querySelector(`#${id}`); 44 | if (domWrapper === null) { 45 | domWrapper = document.createElement("div"); 46 | domWrapper.setAttribute("id", id); 47 | document.body.append(domWrapper); 48 | } 49 | return domWrapper 50 | } 51 | 52 | export default HomePage 53 | -------------------------------------------------------------------------------- /src/components/icon/AHref.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface IAHrefProps { 4 | name: string; 5 | src: string; 6 | } 7 | 8 | const AHref: React.FC = (props) => { 9 | return {props.name} 10 | } 11 | 12 | export default AHref; -------------------------------------------------------------------------------- /src/components/icon/Api.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Table from "../table/Table" 3 | 4 | const Api = () => { 5 | const columns = [ 6 | { 7 | title: '参数', 8 | dataIndex: 'name', 9 | key: 'param', 10 | }, 11 | { 12 | title: '说明', 13 | dataIndex: 'address', 14 | key: 'explain', 15 | }, 16 | { 17 | title: '类型', 18 | dataIndex: 'age', 19 | key: 'type', 20 | }, 21 | { 22 | title: '默认值', 23 | dataIndex: 'address', 24 | key: 'defaultValue', 25 | }, 26 | ]; 27 | const dataSource = [ 28 | { 29 | key: '1', 30 | param: 'name', 31 | explain: 'icon名称', 32 | type: 'string', 33 | defaultValue: "-" 34 | }, 35 | { 36 | key: '2', 37 | param: 'iconLibrary', 38 | explain: '阿里图标库地址', 39 | type: 'string', 40 | defaultValue: "-" 41 | }, 42 | { 43 | key: '3', 44 | param: 'className', 45 | explain: '自定义 Icon 类名', 46 | type: 'string', 47 | defaultValue: "-" 48 | }, 49 | { 50 | key: '4', 51 | param: 'style', 52 | explain: '自定义 Icon 样式', 53 | type: 'React.CSSProperties', 54 | defaultValue: "-" 55 | }, 56 | ]; 57 | return ( 58 |
59 | ) 60 | } 61 | 62 | export default Api -------------------------------------------------------------------------------- /src/components/icon/Icon.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Icon 3 | menu: 通用 4 | --- 5 | 6 | import { Playground, Props } from 'docz' 7 | import Icon from "./Icon" 8 | import Api from "./Api" 9 | import AHref from "./AHref" 10 | 11 | 12 | ### Icon 图标 13 |

语义化的矢量图形

14 | 15 | ## 如何使用 16 |

内置一套,直接传入指定图标对应的name属性,

17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 | 33 | ## 更改图标库源 34 |

支持替换图标库,可以直接在上选取心仪的图标库,生成symbol地址直接替换即可。

35 | 36 | ## APi 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/components/icon/Icon.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/index.scss"; 2 | 3 | .le-icon { 4 | width: 1.5em; 5 | height: 1.5em; 6 | } -------------------------------------------------------------------------------- /src/components/icon/Icon.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react" 2 | import classnames from "classnames" 3 | import "./Icon.scss" 4 | 5 | interface IIconProps { 6 | name: string; 7 | iconLibrary?: string; 8 | onClick?: React.MouseEventHandler 9 | className?: string; 10 | style?: React.CSSProperties 11 | } 12 | 13 | // '//at.alicdn.com/t/font_2049320_ixovveh7lgf.js' 14 | 15 | 16 | const Icon: React.FC = ({ iconLibrary = "/public/iconLibrary.js", onClick, className, name, style }) => { 17 | 18 | 19 | 20 | useEffect(() => { 21 | const iconScript = document.querySelector("#icon-library") 22 | if (iconScript == null) { 23 | const scriptElem = document.createElement('script'); 24 | scriptElem.id = "icon-library"; 25 | scriptElem.src = iconLibrary 26 | document.body.appendChild(scriptElem); 27 | } 28 | return () => { 29 | const targeEl = document.getElementById("icon-library"); 30 | targeEl?.parentNode?.removeChild(targeEl); 31 | } 32 | }, [iconLibrary]) 33 | 34 | const classes = classnames("le-icon", className) 35 | 36 | return ( 37 | 38 | ) 39 | } 40 | 41 | export default Icon; 42 | 43 | -------------------------------------------------------------------------------- /src/components/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as Button } from "./button/Button" 2 | export { default as ButtonGroup } from "./button/ButtonGroup" 3 | export { default as Affix } from "./affix/Affix" 4 | export { default as Carousel } from "./carousel/Carousel" 5 | export { default as Drawer } from "./drawer/Drawer" 6 | export { default as Icon } from "./icon/Icon" 7 | export { Layout as Layout, Header as Header, Footer as Footer, Content as Content, Side as Side } from "./layout/index" 8 | export { default as Message } from "./message/Message" 9 | export { default as Modal } from "./modal/Modal" 10 | export { default as Notification } from "./notification/Notification" 11 | export { default as Rate } from "./rate/Rate" 12 | export { default as Spin } from "./spin/Spin" 13 | export { default as Switch } from "./switch/Switch" 14 | export { default as Table } from "./table/Table" 15 | export { default as Radio } from "./radio/Radio" 16 | export { default as RadioGroup } from "./radio/RadioGroup" 17 | export { default as CheckBox } from "./checkBox/CheckBox" 18 | export { default as CheckBoxGroup } from "./checkBox/CheckBoxGroup" 19 | export { default as Collapse } from "./collapse/Collapse" 20 | export { default as CollapseItem } from "./collapse/CollapseItem" 21 | export { default as Input } from "./input/Input" 22 | export { default as Pager } from "./pager/Pager" 23 | export { default as Popover } from "./popover/Popover" 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/components/input/Api.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Table from "../table/Table" 3 | 4 | const Api = () => { 5 | const columns = [ 6 | { 7 | title: '参数', 8 | dataIndex: 'name', 9 | key: 'param', 10 | }, 11 | { 12 | title: '说明', 13 | dataIndex: 'address', 14 | key: 'explain', 15 | }, 16 | { 17 | title: '类型', 18 | dataIndex: 'age', 19 | key: 'type', 20 | }, 21 | { 22 | title: '默认值', 23 | dataIndex: 'address', 24 | key: 'defaultValue', 25 | }, 26 | ]; 27 | const dataSource = [ 28 | { 29 | key: '1', 30 | param: 'disabled', 31 | explain: '是否禁止交互', 32 | type: `boolean`, 33 | defaultValue: "false" 34 | }, 35 | { 36 | key: '2', 37 | param: 'clearable', 38 | explain: '是否支持清除', 39 | type: `boolean`, 40 | defaultValue: "false" 41 | }, 42 | { 43 | key: '3', 44 | param: 'type', 45 | explain: 'input类型', 46 | type: "password | textarea | text", 47 | defaultValue: "text" 48 | }, 49 | { 50 | key: '4', 51 | param: 'addonBefore', 52 | explain: '前置标签', 53 | type: 'ReactNode | string', 54 | defaultValue: "-" 55 | }, 56 | { 57 | key: '5', 58 | param: 'addonAfter', 59 | explain: '后置标签', 60 | type: 'ReactNode | string', 61 | defaultValue: "-" 62 | }, 63 | 64 | { 65 | key: '6', 66 | param: 'onChange', 67 | explain: '值改变时的回调', 68 | type: '(val: string | number) => void', 69 | defaultValue: "-" 70 | }, 71 | { 72 | key: '7', 73 | param: 'onPressEnter', 74 | explain: '按下回车键时的回调', 75 | type: '(e: React.KeyboardEvent) => void', 76 | defaultValue: "-" 77 | }, 78 | { 79 | key: '8', 80 | param: 'className', 81 | explain: '自定义 Input 类名', 82 | type: 'string', 83 | defaultValue: "-" 84 | }, 85 | { 86 | key: '9', 87 | param: 'style', 88 | explain: '自定义 Input 样式', 89 | type: 'React.CSSProperties', 90 | defaultValue: "-" 91 | }, 92 | ]; 93 | return ( 94 |
95 | ) 96 | } 97 | 98 | export default Api -------------------------------------------------------------------------------- /src/components/input/Input.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | name: Input 3 | menu: 数据录入 4 | --- 5 | 6 | import { Playground, Props } from 'docz' 7 | import Input from "./Input" 8 | import Api from "./Api" 9 | 10 | ### Input输入框 11 |

通过鼠标或键盘输入内容,是最基础的表单域的包装。

12 | 13 | #### 何时使用 14 | * 需要用户输入表单域内容时。 15 | 16 | * 提供组合型输入框,带搜索的输入框,还可以进行大小选择。 17 | 18 | ## 基本使用 19 |

基本使用

20 | 21 | 22 | 23 | 24 | 25 | ## 禁用状态 26 |

设置disabled为true

27 | 28 | 29 | 30 | 31 | 32 | ## 可清空 33 |

设置clearable为true

34 | 35 | 36 | 37 | 38 | 39 | ## 前置 / 后置标签 40 |

用于配置一些固定组合。

41 | 42 | 43 | 44 | 45 | 46 | ## 文本域 47 |

用于输入多行文本信息,通过将 type 属性的值指定为 textarea。

48 | 49 | 50 | 51 | 52 | 53 | ## Api 54 | -------------------------------------------------------------------------------- /src/components/input/Input.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/index.scss"; 2 | 3 | .le-input-wrapper { 4 | display: flex; 5 | 6 | .le-input-container { 7 | position: relative; 8 | height: 100%; 9 | 10 | .le-input, 11 | .le-textarea { 12 | font-size: 14px; 13 | padding: 4px 11px; 14 | line-height: 20px; 15 | border-radius: 4px; 16 | border: 1px solid rgba(0, 0, 0, 0.15); 17 | transition: all 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86); 18 | color: rgba(0, 0, 0, 0.65); 19 | outline: none; 20 | display: flex; 21 | 22 | &:focus { 23 | border: 1px solid #5db2f7; 24 | } 25 | 26 | &:hover { 27 | border: 1px solid #5db2f7; 28 | } 29 | } 30 | 31 | .le-input-close { 32 | display: inline-flex; 33 | width: 14px; 34 | height: 14px; 35 | justify-content: center; 36 | align-items: center; 37 | position: absolute; 38 | right: 4px; 39 | top: 50%; 40 | transform: translateY(-50%); 41 | cursor: pointer; 42 | } 43 | } 44 | 45 | &.clearable { 46 | .le-input-container { 47 | .le-input { 48 | padding-right: 20px !important; 49 | } 50 | } 51 | } 52 | 53 | 54 | &.has-before-addon { 55 | .le-input-container { 56 | .le-input { 57 | border-top-left-radius: 0px; 58 | border-bottom-left-radius: 0px; 59 | } 60 | } 61 | } 62 | 63 | &.has-after-addon { 64 | .le-input-container { 65 | .le-input { 66 | border-top-right-radius: 0px; 67 | border-bottom-right-radius: 0px; 68 | } 69 | } 70 | } 71 | 72 | &.disabled { 73 | .le-input-container { 74 | .le-input { 75 | cursor: not-allowed; 76 | color: rgba(0, 0, 0, 0.25); 77 | border-color: #d9d9d9; 78 | background-color: #e6e6e6; 79 | } 80 | } 81 | } 82 | 83 | .before-addon { 84 | display: inline-flex; 85 | height: 100%; 86 | border: 1px solid rgba(0, 0, 0, 0.15); 87 | border-right: none; 88 | border-top-left-radius: 4px; 89 | border-bottom-left-radius: 4px; 90 | background-color: #fafafa; 91 | font-size: 14px; 92 | line-height: 20px; 93 | padding: 4px 10px; 94 | color: rgba(0, 0, 0, 0.65); 95 | justify-content: center; 96 | align-items: center; 97 | } 98 | 99 | .after-addon { 100 | display: inline-flex; 101 | height: 100%; 102 | border: 1px solid rgba(0, 0, 0, 0.15); 103 | border-left: none; 104 | border-top-right-radius: 4px; 105 | border-bottom-right-radius: 4px; 106 | background-color: #fafafa; 107 | font-size: 14px; 108 | line-height: 20px; 109 | padding: 4px 10px; 110 | color: rgba(0, 0, 0, 0.65); 111 | justify-content: center; 112 | align-items: center; 113 | } 114 | } -------------------------------------------------------------------------------- /src/components/input/Input.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, ReactNode, useCallback, useRef } from "react" 2 | import classnames from "classnames" 3 | import "./Input.scss" 4 | import Icon from "../icon/Icon" 5 | 6 | interface IInputProps { 7 | disabled?: boolean; 8 | clearable?: boolean; 9 | type?: "password" | "textarea" | "text"; 10 | addonAfter?: ReactNode | string; 11 | addonBefore?: ReactNode | string; 12 | onChange?: (val: string | number) => void; 13 | onPressEnter?: (e: React.KeyboardEvent) => void; 14 | className?: string; 15 | style?: React.CSSProperties; 16 | } 17 | 18 | const Input: React.FC = memo(({ disabled = false, clearable = false, onPressEnter, type = "text", addonAfter, addonBefore, onChange, className, style }) => { 19 | 20 | const inputRef = useRef(null); 21 | 22 | const classes = classnames("le-input-wrapper", className, { 23 | "has-before-addon": addonBefore, 24 | "has-after-addon": addonAfter, 25 | "disabled": disabled, 26 | "clearable": clearable 27 | }) 28 | 29 | const handleOnChange = useCallback((e: React.ChangeEvent) => { 30 | onChange && onChange(e.target.value) 31 | }, [onChange]) 32 | 33 | const clearValue = useCallback(() => { 34 | (inputRef.current as HTMLInputElement).value = ""; 35 | }, [inputRef]) 36 | 37 | const handleOnKeyPress = useCallback((e: React.KeyboardEvent) => { 38 | if (e.charCode === 13) { 39 | onPressEnter && onPressEnter(e) 40 | } 41 | }, [onPressEnter]) 42 | 43 | return ( 44 |
45 | {addonBefore && } 46 | 47 | {type === "textarea" ? ( 48 |