├── .env ├── .env.library ├── .eslintrc ├── .prettierrc ├── src ├── components │ ├── Spinner │ │ ├── Spinner.module.css │ │ ├── Readme.md │ │ ├── index.js │ │ └── assets │ │ │ └── iOS.svg │ └── Button │ │ ├── Readme.md │ │ ├── index.js │ │ └── Button.module.css ├── index.js └── utils │ ├── classNames.js │ └── getCssModules.js ├── styleguide.config.js ├── .gitignore ├── public └── index.html ├── postcss.config.js ├── config-overrides.js ├── README.md ├── scripts ├── reactLibraryConfig.js └── cssModuleConfig.js └── package.json /.env: -------------------------------------------------------------------------------- 1 | LIB_NAMESPACE = "woo" -------------------------------------------------------------------------------- /.env.library: -------------------------------------------------------------------------------- 1 | REACT_APP_NODE_ENV = "library" -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["react-app", "plugin:prettier/recommended"] 3 | } -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "printWidth": 90, 4 | "singleQuote": true, 5 | "semi": true 6 | } -------------------------------------------------------------------------------- /src/components/Spinner/Spinner.module.css: -------------------------------------------------------------------------------- 1 | .main { 2 | width: 1em; 3 | height: 1em; 4 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Button from './components/Button'; 2 | import Spinner from './components/Spinner'; 3 | export { Button, Spinner }; 4 | -------------------------------------------------------------------------------- /src/utils/classNames.js: -------------------------------------------------------------------------------- 1 | export default function classNames(...classes) { 2 | return classes.filter(item => !!item).join(' '); 3 | } 4 | -------------------------------------------------------------------------------- /src/components/Spinner/Readme.md: -------------------------------------------------------------------------------- 1 | ## 基础用法 2 | 3 | ```jsx 4 | 5 | ``` 6 | 7 | ## 大小 8 | 9 | ```jsx 10 | 11 | ``` 12 | 13 | ## 颜色 14 | 15 | ```jsx 16 | 17 | ``` -------------------------------------------------------------------------------- /styleguide.config.js: -------------------------------------------------------------------------------- 1 | const { paths } = require('react-app-rewired'); 2 | const overrides = require('react-app-rewired/config-overrides'); 3 | const config = require(paths.scriptVersion + '/config/webpack.config'); 4 | 5 | module.exports = { 6 | webpackConfig: overrides.webpack(config(process.env.NODE_ENV), process.env.NODE_ENV) 7 | }; 8 | -------------------------------------------------------------------------------- /src/utils/getCssModules.js: -------------------------------------------------------------------------------- 1 | function isObject(val) { 2 | return val != null && typeof val === 'object' && Array.isArray(val) === false; 3 | } 4 | 5 | export default function getCssModule(classes, module) { 6 | if (isObject(module) && Array.isArray(classes)) { 7 | return classes.map(item => module[item]).join(' '); 8 | } 9 | return classes; 10 | } 11 | -------------------------------------------------------------------------------- /.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 | # styleguide 15 | /styleguide 16 | 17 | # misc 18 | .DS_Store 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | WooUI React 8 | 9 | 10 |
11 | 12 | 24 | 25 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-each': { 4 | plugins: { 5 | afterEach: [require('postcss-at-rules-variables')] 6 | } 7 | }, 8 | 'postcss-nested': {}, 9 | 'postcss-units': { 10 | size: 16, 11 | precision: 6 12 | }, 13 | 'postcss-pxtorem': { 14 | rootValue: 16, 15 | propWhiteList: [ 16 | '*', 17 | '!border', 18 | '!border-top', 19 | '!border-right', 20 | '!border-bottom', 21 | '!border-left', 22 | '!border-width' 23 | ], 24 | selectorBlackList: ['html'], 25 | mediaQuery: false 26 | } 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/components/Spinner/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { ReactComponent as Icon } from './assets/iOS.svg'; 4 | import classNames from '../../utils/classNames'; 5 | import Style from './Spinner.module.css'; 6 | 7 | const propTypes = { 8 | className: PropTypes.string, 9 | color: PropTypes.string, 10 | size: PropTypes.string 11 | }; 12 | 13 | function Spinner(props) { 14 | return ( 15 | 19 | ); 20 | } 21 | 22 | Spinner.propTypes = propTypes; 23 | 24 | export default Spinner; 25 | -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | const cssModuleConfig = require('./scripts/cssModuleConfig'); 2 | const loaderUtils = require('loader-utils'); 3 | const reactLibraryConfig = require('./scripts/reactLibraryConfig'); 4 | const rewirePostcss = require('react-app-rewire-postcss'); 5 | 6 | module.exports = { 7 | webpack: function(config, env) { 8 | config = cssModuleConfig(config, env, { 9 | modules: { 10 | getLocalIdent: (context, localIdentName, localName, options) => { 11 | const folderName = loaderUtils.interpolateName(context, '[folder]', options); 12 | const className = 13 | process.env.LIB_NAMESPACE + '-' + folderName + '-' + localName; 14 | return className.toLowerCase(); 15 | } 16 | } 17 | }); 18 | config = rewirePostcss(config, true); 19 | config = reactLibraryConfig(config, process.env.REACT_APP_NODE_ENV); 20 | return config; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/Button/Readme.md: -------------------------------------------------------------------------------- 1 | ## 基础用法 2 | ```jsx 3 | const [count, setCount] = React.useState(0); 4 |
5 |

Clicked {count} times

6 | 7 |
8 | ``` 9 | 10 | ## 按钮类型 11 | ```jsx 12 | const kinds = ['primary', 'secondary', 'success', 'danger', 'default']; 13 | const sorts = ['line', 'flat']; 14 | 15 | sorts.map( 16 | sort => { 17 | return ( 18 |
19 |

sort: {sort}

20 | {kinds.map(kind => )} 21 |
22 | ); 23 | } 24 | ); 25 | ``` 26 | 31 | 32 | ## 按钮大小 33 | ```jsx 34 | const sizes = ['s', 'm', 'l']; 35 | 36 | sizes.map(size => ); 37 | ``` 38 | 39 | ## 不可用 40 | ```jsx 41 | 42 | 43 | ``` 44 | 45 | ## 加载中 46 | ```jsx 47 | 48 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Intro 2 | 3 | This project was mainly bootstrapped with [Create React App](https://github.com/facebook/create-react-app)、[React App Rewired](https://github.com/timarney/react-app-rewired) and [React Styleguidist](https://github.com/styleguidist/react-styleguidist)。Developing and building a React UI Component Library will be much more easier. 4 | 5 | ## Available Scripts Now 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.
12 | Open [http://localhost:6060](http://localhost:6060) to view it in the browser. 13 | 14 | The page will reload if you make edits.
15 | You will also see any lint errors in the console.yarn 16 | 17 | ### `yarn styleguide:build` 18 | 19 | Builds the UI Component Library for style guide to the `styleguide` folder.
20 | It correctly bundles React in production mode and optimizes the build for the best performance. 21 | 22 | The build is minified and the filenames include the hashes. 23 | 24 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 25 | 26 | ### `yarn build:library` 27 | 28 | Builds the UI Components for library to the `build` folder 29 | -------------------------------------------------------------------------------- /scripts/reactLibraryConfig.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 3 | 4 | module.exports = function(config, env, options) { 5 | if (env === 'library') { 6 | const srcFile = process.env.npm_package_module || options.module; 7 | const libName = process.env.npm_package_name || options.name; 8 | config.entry = srcFile; 9 | config.output = { 10 | path: path.resolve('./', 'build'), 11 | filename: libName + '.js', 12 | library: libName, 13 | libraryTarget: 'umd' 14 | }; 15 | delete config.optimization.splitChunks; 16 | delete config.optimization.runtimeChunk; 17 | config.plugins = []; 18 | config.plugins.push( 19 | new MiniCssExtractPlugin({ 20 | filename: libName + '.css' 21 | }) 22 | ); 23 | 24 | let externals = {}; 25 | Object.keys(process.env).forEach(key => { 26 | if (key.includes('npm_package_dependencies_')) { 27 | let pkgName = key.replace('npm_package_dependencies_', ''); 28 | pkgName = pkgName.replace(/_/g, '-'); 29 | // below if condition addresses scoped packages : eg: @storybook/react 30 | if (pkgName.startsWith('-')) { 31 | const scopeName = pkgName.substr(1, pkgName.indexOf('-', 1) - 1); 32 | const remainingPackageName = pkgName.substr( 33 | pkgName.indexOf('-', 1) + 1, 34 | pkgName.length 35 | ); 36 | pkgName = `@${scopeName}/${remainingPackageName}`; 37 | } 38 | externals[pkgName] = `${pkgName}`; 39 | } 40 | }); 41 | config.externals = externals; 42 | } 43 | return config; 44 | }; 45 | -------------------------------------------------------------------------------- /src/components/Button/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classNames from '../../utils/classNames'; 4 | import getCssModules from '../../utils/getCssModules'; 5 | import Style from './Button.module.css'; 6 | import Spinner from '../Spinner'; 7 | 8 | class Button extends Component { 9 | static propTypes = { 10 | children: PropTypes.string.isRequired, 11 | className: PropTypes.string, 12 | style: PropTypes.object, 13 | size: PropTypes.oneOf(['s', 'm', 'l']), 14 | sort: PropTypes.oneOf(['line', 'flat']), 15 | kind: PropTypes.oneOf(['primary', 'secondary', 'success', 'danger', 'default']), 16 | disabled: PropTypes.bool, 17 | onClick: PropTypes.func 18 | }; 19 | static defaultProps = { 20 | size: 'm', 21 | sort: 'line', 22 | kind: 'primary' 23 | }; 24 | 25 | handleClick(e) { 26 | if (this.props.onClick) { 27 | this.props.onClick(e); 28 | } 29 | } 30 | 31 | render() { 32 | const { 33 | sort, 34 | size, 35 | kind, 36 | loading, 37 | disabled, 38 | children, 39 | className, 40 | style 41 | } = this.props; 42 | return ( 43 | 55 | ); 56 | } 57 | } 58 | 59 | export default Button; 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wooui-react", 3 | "version": "0.1.0", 4 | "private": true, 5 | "module": "./src/index.js", 6 | "main": "./build/wooui-react.js", 7 | "dependencies": { 8 | "@testing-library/jest-dom": "^4.2.4", 9 | "@testing-library/react": "^9.3.2", 10 | "@testing-library/user-event": "^7.1.2", 11 | "react": "^16.12.0", 12 | "react-dom": "^16.12.0", 13 | "react-scripts": "3.3.0" 14 | }, 15 | "scripts": { 16 | "start": "styleguidist server", 17 | "styleguide:build": "styleguidist build", 18 | "build:library": "rm -rf build && env-cmd -f .env.library react-app-rewired build" 19 | }, 20 | "browserslist": { 21 | "production": [ 22 | ">0.2%", 23 | "not dead", 24 | "not op_mini all" 25 | ], 26 | "development": [ 27 | "last 1 chrome version", 28 | "last 1 firefox version", 29 | "last 1 safari version" 30 | ] 31 | }, 32 | "devDependencies": { 33 | "env-cmd": "^10.0.1", 34 | "eslint-config-prettier": "^6.9.0", 35 | "eslint-plugin-prettier": "^3.1.2", 36 | "husky": "^4.0.6", 37 | "lint-staged": "^9.5.0", 38 | "postcss-at-rules-variables": "^0.1.10", 39 | "postcss-each": "^0.10.0", 40 | "postcss-nested": "^4.2.1", 41 | "postcss-pxtorem": "^4.0.1", 42 | "postcss-units": "^1.2.1", 43 | "prettier": "^1.19.1", 44 | "react-app-rewire-postcss": "^3.0.2", 45 | "react-app-rewired": "^2.1.5", 46 | "react-styleguidist": "^10.4.1" 47 | }, 48 | "husky": { 49 | "hooks": { 50 | "pre-commit": "lint-staged" 51 | } 52 | }, 53 | "lint-staged": { 54 | "src/**/*.{js,jsx,json,css,md}": [ 55 | "prettier --write", 56 | "git add" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /scripts/cssModuleConfig.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const ruleChildren = loader => 3 | loader.use || loader.oneOf || (Array.isArray(loader.loader) && loader.loader) || []; 4 | const findIndexAndRules = (rulesSource, ruleMatcher) => { 5 | let result = undefined; 6 | const rules = Array.isArray(rulesSource) ? rulesSource : ruleChildren(rulesSource); 7 | rules.some( 8 | (rule, index) => 9 | (result = ruleMatcher(rule) 10 | ? { index, rules } 11 | : findIndexAndRules(ruleChildren(rule), ruleMatcher)) 12 | ); 13 | return result; 14 | }; 15 | const findRule = (rulesSource, ruleMatcher) => { 16 | const { index, rules } = findIndexAndRules(rulesSource, ruleMatcher); 17 | return rules[index]; 18 | }; 19 | const cssRuleMatcher = rule => 20 | rule.test && String(rule.test) === String(/\.module\.css$/); 21 | const sassRuleMatcher = rule => 22 | rule.test && String(rule.test) === String(/\.module\.(scss|sass)$/); 23 | 24 | const createLoaderMatcher = loader => rule => 25 | rule.loader && rule.loader.indexOf(`${path.sep}${loader}${path.sep}`) !== -1; 26 | const cssLoaderMatcher = createLoaderMatcher('css-loader'); 27 | const sassLoaderMatcher = createLoaderMatcher('sass-loader'); 28 | 29 | module.exports = function(config, env, options) { 30 | const cssRule = findRule(config.module.rules, cssRuleMatcher); 31 | let cssModulesRuleCssLoader = findRule(cssRule, cssLoaderMatcher); 32 | const sassRule = findRule(config.module.rules, sassRuleMatcher); 33 | let sassModulesRuleCssLoader = findRule(sassRule, sassLoaderMatcher); 34 | cssModulesRuleCssLoader.options = { ...cssModulesRuleCssLoader.options, ...options }; 35 | sassModulesRuleCssLoader.options = { ...sassModulesRuleCssLoader.options, ...options }; 36 | return config; 37 | }; 38 | -------------------------------------------------------------------------------- /src/components/Button/Button.module.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --button-color-schemes: primary, secondary, success, danger, default; 3 | --button-color-primary: #ff7f00; 4 | --button-color-secondary: #1b98e0; 5 | --button-color-success: #37af79; 6 | --button-color-danger: #ff1654; 7 | --button-color-default: #666; 8 | 9 | --button-sizes: l, m, s; 10 | --button-paddings: 12px 24px, 8px 16px, 6px 12px; 11 | --button-font-sizes: 18px, 14px, 12px; 12 | --button-border-radius: 7px, 5px, 3px; 13 | 14 | --button-hover-opacity: 0.75; 15 | --button-disabled-opacity: 0.5; 16 | } 17 | 18 | .main { 19 | -webkit-touch-callout: none; 20 | -webkit-user-select: none; 21 | -webkit-tap-highlight-color: #fff0; 22 | background-color: transparent; 23 | outline: 0; 24 | border: 0; 25 | box-sizing: border-box; 26 | cursor: pointer; 27 | position: relative; 28 | line-height: 1; 29 | 30 | &:not(:disabled):hover { 31 | opacity: var(--button-hover-opacity); 32 | } 33 | 34 | &:disabled { 35 | opacity: var(--button-disabled-opacity); 36 | cursor: not-allowed; 37 | } 38 | } 39 | 40 | .line { 41 | border: 1px solid; 42 | /*button colors loop*/ 43 | @each $val in (var(--button-color-schemes)) { 44 | &.$(val) { 45 | color: var(--button-color-$(val)); 46 | } 47 | } 48 | } 49 | 50 | .flat { 51 | color: #fff; 52 | border: 1px solid; 53 | /*button colors loop*/ 54 | @each $val in (var(--button-color-schemes)) { 55 | &.$(val) { 56 | border-color: var(--button-color-$(val)); 57 | background-color: var(--button-color-$(val)); 58 | } 59 | } 60 | } 61 | 62 | /*button sizes loop*/ 63 | @each $size, $padding, $fontSize, $borderRadius in (var(--button-sizes)), (var(--button-paddings)), (var(--button-font-sizes)), (var(--button-border-radius)) { 64 | .$(size) { 65 | padding: $(padding); 66 | font-size: $(fontSize); 67 | border-radius: $(borderRadius); 68 | } 69 | } 70 | 71 | .icon { 72 | vertical-align: middle; 73 | } 74 | 75 | .icon + .text { 76 | margin-left: em(5px); 77 | } -------------------------------------------------------------------------------- /src/components/Spinner/assets/iOS.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 8 | 9 | 10 | 12 | 14 | 15 | 16 | 18 | 20 | 21 | 22 | 24 | 26 | 27 | 28 | 30 | 32 | 33 | 34 | 36 | 38 | 39 | 40 | 42 | 44 | 45 | 46 | 48 | 50 | 51 | 52 | 54 | 56 | 57 | 58 | 60 | 62 | 63 | 64 | 66 | 68 | 69 | 70 | 72 | 74 | 75 | 76 | --------------------------------------------------------------------------------