├── .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 |
--------------------------------------------------------------------------------