├── example ├── .env ├── src │ ├── components │ │ ├── notice │ │ │ ├── style.scss │ │ │ ├── index.js │ │ │ ├── preview.js │ │ │ └── editor.js │ │ ├── search │ │ │ ├── constants.js │ │ │ ├── style.scss │ │ │ ├── index.js │ │ │ ├── preview.js │ │ │ └── editor.js │ │ └── navigationBar │ │ │ ├── preview.js │ │ │ ├── index.js │ │ │ ├── style.scss │ │ │ └── editor.js │ ├── assets │ │ └── img │ │ │ └── navigation-bar │ │ │ └── bar-bg.png │ ├── setupTests.js │ ├── index.js │ ├── app.css │ ├── App.js │ └── serviceWorker.js ├── .gitignore ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── config │ ├── jest │ │ ├── cssTransform.js │ │ └── fileTransform.js │ ├── pnpTs.js │ ├── paths.js │ ├── env.js │ ├── modules.js │ └── webpackDevServer.config.js ├── scripts │ ├── test.js │ ├── start.js │ └── build.js ├── README.md └── package.json ├── demo └── src │ ├── styles │ ├── _fn.scss │ ├── components │ │ ├── whitespace │ │ │ ├── index.scss │ │ │ └── editor.scss │ │ ├── config │ │ │ ├── index.scss │ │ │ ├── editor.scss │ │ │ └── preview.scss │ │ ├── line │ │ │ ├── index.scss │ │ │ ├── preview.scss │ │ │ └── editor.scss │ │ ├── image-ad │ │ │ ├── index.scss │ │ │ ├── preview.scss │ │ │ └── editor.scss │ │ ├── index.scss │ │ └── richtext │ │ │ ├── index.scss │ │ │ └── editor.scss │ ├── _variable.scss │ ├── _theme-default.scss │ └── index.scss │ ├── components │ ├── config │ │ ├── index.js │ │ ├── ConfigPreview.js │ │ └── ConfigEditor.js │ ├── image-ad │ │ ├── index.js │ │ ├── constants.js │ │ ├── ImageAdPreview.js │ │ ├── ImageEntry.js │ │ └── ImageAdEditor.js │ ├── line │ │ ├── index.js │ │ ├── LinePreview.js │ │ └── LineEditor.js │ └── whitespace │ │ ├── index.js │ │ ├── WhitespacePreview.js │ │ └── WhitespaceEditor.js │ └── index.js ├── tests ├── .eslintrc └── index-test.js ├── src ├── utils │ ├── isBrowser.js │ ├── uuid.js │ ├── offset.js │ ├── LazyMap.js │ ├── reorder.js │ ├── design-type.js │ ├── shallowEqual.js │ ├── storage.js │ ├── component-group.js │ └── scroll.js ├── constants.js ├── DesignWithDnd.js ├── preview │ ├── constants.js │ ├── DesignPreviewItem.js │ ├── DesignPreviewController.js │ └── DesignPreview.js ├── withDnd.js ├── styles │ ├── design │ │ ├── _variable.scss │ │ ├── _fn.scss │ │ ├── header.scss │ │ ├── alert.scss │ │ ├── editor.scss │ │ ├── editor-item.scss │ │ ├── preview.scss │ │ ├── preview-controller.scss │ │ ├── _theme-default.scss │ │ └── editor-add-component.scss │ └── index.scss ├── index.js └── editor │ ├── DesignEditorItem.js │ ├── DesignEditorAddComponent.js │ └── DesignEditor.js ├── .gitignore ├── gh.js ├── scripts └── build-css.sh ├── nwb.config.js ├── .travis.yml ├── LICENSE ├── package.json ├── README_en-US.md └── README.md /example/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /example/src/components/notice/style.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/src/styles/_fn.scss: -------------------------------------------------------------------------------- 1 | @import '../../../src/styles/design/fn'; -------------------------------------------------------------------------------- /demo/src/styles/components/whitespace/index.scss: -------------------------------------------------------------------------------- 1 | @import './editor'; 2 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /demo/src/styles/_variable.scss: -------------------------------------------------------------------------------- 1 | @import '../../../src/styles/design/variable'; -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | npm-debug.log* 3 | .DS_Store 4 | /build -------------------------------------------------------------------------------- /example/src/components/search/constants.js: -------------------------------------------------------------------------------- 1 | 2 | export const PLACEHOLDER = '搜索商品'; 3 | -------------------------------------------------------------------------------- /src/utils/isBrowser.js: -------------------------------------------------------------------------------- 1 | export default !!(typeof window !== 'undefined' && window); 2 | -------------------------------------------------------------------------------- /demo/src/styles/_theme-default.scss: -------------------------------------------------------------------------------- 1 | @import '../../../src/styles/design/theme-default'; 2 | -------------------------------------------------------------------------------- /demo/src/styles/components/config/index.scss: -------------------------------------------------------------------------------- 1 | @import './preview'; 2 | @import './editor'; 3 | -------------------------------------------------------------------------------- /demo/src/styles/components/line/index.scss: -------------------------------------------------------------------------------- 1 | @import './editor'; 2 | @import './preview'; 3 | -------------------------------------------------------------------------------- /example/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /demo/src/styles/components/image-ad/index.scss: -------------------------------------------------------------------------------- 1 | @import './editor'; 2 | @import './preview'; 3 | -------------------------------------------------------------------------------- /demo/src/styles/components/line/preview.scss: -------------------------------------------------------------------------------- 1 | .zent-design-component-line-preview { 2 | padding: 20px 0; 3 | } 4 | -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xjh22222228/micro-design-editor/HEAD/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xjh22222228/micro-design-editor/HEAD/example/public/logo192.png -------------------------------------------------------------------------------- /example/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xjh22222228/micro-design-editor/HEAD/example/public/logo512.png -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const ADD_COMPONENT_OVERLAY_POSITION = { 2 | TOP: 1, 3 | BOTTOM: 2, 4 | UNKNOWN: -1 5 | }; 6 | -------------------------------------------------------------------------------- /src/DesignWithDnd.js: -------------------------------------------------------------------------------- 1 | import Design from './Design'; 2 | import withDnd from './withDnd'; 3 | 4 | export default withDnd(Design); 5 | -------------------------------------------------------------------------------- /src/utils/uuid.js: -------------------------------------------------------------------------------- 1 | import uuidV4 from 'uuid/v4'; 2 | 3 | // Ignore all arguments 4 | export default function uuid() { 5 | return uuidV4(); 6 | } 7 | -------------------------------------------------------------------------------- /src/preview/constants.js: -------------------------------------------------------------------------------- 1 | export const DND_PREVIEW_CONTROLLER = 'zent-design-preview-controller-dnd-type'; 2 | export const DEFAULT_BACKGROUND = '#f9f9f9'; 3 | -------------------------------------------------------------------------------- /src/withDnd.js: -------------------------------------------------------------------------------- 1 | /* For compatibility only, this function does nothing */ 2 | export default function withDnd(component) { 3 | return component; 4 | } 5 | -------------------------------------------------------------------------------- /example/src/assets/img/navigation-bar/bar-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xjh22222228/micro-design-editor/HEAD/example/src/assets/img/navigation-bar/bar-bg.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /demo/dist 3 | /es 4 | /lib 5 | /css 6 | /node_modules 7 | /umd 8 | npm-debug.log* 9 | .history 10 | .DS_Store 11 | *.log 12 | fix.md 13 | npmBuild.js -------------------------------------------------------------------------------- /example/src/components/search/style.scss: -------------------------------------------------------------------------------- 1 | .design-search-preview { 2 | padding: 10px; 3 | 4 | .ant-input { 5 | border-radius: 50px; 6 | pointer-events: none; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/styles/design/_variable.scss: -------------------------------------------------------------------------------- 1 | 2 | // 预览区域头部高度 3 | $preview-header-height: 64px; 4 | 5 | // 头部高度 6 | $header-height: 55px; 7 | 8 | // 编辑器宽度 9 | $editor-width: 330px; 10 | -------------------------------------------------------------------------------- /demo/src/styles/components/index.scss: -------------------------------------------------------------------------------- 1 | @import './config/index'; 2 | @import './whitespace/index'; 3 | @import './line/index'; 4 | @import './richtext/index'; 5 | @import './image-ad/index'; 6 | -------------------------------------------------------------------------------- /gh.js: -------------------------------------------------------------------------------- 1 | const ghpages = require('gh-pages'); 2 | 3 | ghpages.publish('example/build', function(err) { 4 | if (err) { 5 | throw err; 6 | } 7 | 8 | console.log('build gh-pages finished!') 9 | }); 10 | -------------------------------------------------------------------------------- /demo/src/components/config/index.js: -------------------------------------------------------------------------------- 1 | import Editor from './ConfigEditor'; 2 | import Preview from './ConfigPreview'; 3 | 4 | export default { 5 | type: Editor.designType, 6 | editor: Editor, 7 | preview: Preview, 8 | }; 9 | -------------------------------------------------------------------------------- /demo/src/components/image-ad/index.js: -------------------------------------------------------------------------------- 1 | import Editor from './ImageAdEditor'; 2 | import Preview from './ImageAdPreview'; 3 | 4 | export default { 5 | type: Editor.designType, 6 | editor: Editor, 7 | preview: Preview 8 | }; 9 | -------------------------------------------------------------------------------- /example/src/components/notice/index.js: -------------------------------------------------------------------------------- 1 | import Editor from './editor'; 2 | import Preview from './preview'; 3 | import './style.scss'; 4 | 5 | export default { 6 | type: Editor.designType, 7 | editor: Editor, 8 | preview: Preview 9 | }; 10 | -------------------------------------------------------------------------------- /example/src/components/search/index.js: -------------------------------------------------------------------------------- 1 | import Editor from './editor'; 2 | import Preview from './preview'; 3 | import './style.scss'; 4 | 5 | export default { 6 | type: Editor.designType, 7 | editor: Editor, 8 | preview: Preview 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/offset.js: -------------------------------------------------------------------------------- 1 | export default function offset(node) { 2 | const y = window.pageYOffset; 3 | const x = window.pageXOffset; 4 | const bb = node.getBoundingClientRect(); 5 | return { 6 | top: bb.top + y, 7 | left: bb.left + x, 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /demo/src/components/line/index.js: -------------------------------------------------------------------------------- 1 | import Editor from './LineEditor'; 2 | import Preview from './LinePreview'; 3 | 4 | export default { 5 | type: Editor.designType, 6 | editor: Editor, 7 | preview: Preview, 8 | icon: 'https://img.icons8.com/officel/2x/music.png' 9 | }; 10 | -------------------------------------------------------------------------------- /example/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /demo/src/components/whitespace/index.js: -------------------------------------------------------------------------------- 1 | import Editor from './WhitespaceEditor'; 2 | import Preview from './WhitespacePreview'; 3 | 4 | export default { 5 | type: Editor.designType, 6 | editor: Editor, 7 | preview: Preview, 8 | icon: 'https://img.icons8.com/officel/2x/music.png' 9 | }; 10 | -------------------------------------------------------------------------------- /scripts/build-css.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ns="node-sass --importer node_modules/node-sass-magic-importer/dist/cli.js" 4 | 5 | # transpile scss to css 6 | # custom importer for @import '~some-node-module' 7 | $ns src/styles/index.scss css/index.css -q 8 | $ns demo/src/styles/components/index.scss css/components.css -q -------------------------------------------------------------------------------- /demo/src/components/image-ad/constants.js: -------------------------------------------------------------------------------- 1 | export const IMAGE_SIZE = { 2 | SMALL: 1, 3 | LARGE: 2, 4 | }; 5 | 6 | export const IMAGE_AD_LIMIT = 10; 7 | 8 | export const IMAGE_AD_ENTRY_UUID_KEY = '__image-ad-entry-uuid__'; 9 | 10 | export const IMAGE_AD_DND_TYPE = 'zent-design-componentimage-ad-dnd-type'; 11 | -------------------------------------------------------------------------------- /nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'react-component', 3 | npm: { 4 | cjs: false, 5 | esModules: true, 6 | umd: false, 7 | }, 8 | babel: { 9 | cherryPick: 'lodash', 10 | runtime: 'helpers', 11 | removePropTypes: { 12 | mode: 'remove', 13 | removeImport: true, 14 | }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /demo/src/styles/components/line/editor.scss: -------------------------------------------------------------------------------- 1 | @import '../../theme-default'; 2 | 3 | .zent-design-component-line-editor { 4 | .zent-radio-wrap { 5 | display: inline-block; 6 | } 7 | 8 | .zent-design-component-line-editor_color-reset { 9 | font-size: 12px; 10 | color: $theme-primary-2; 11 | cursor: pointer; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/styles/design/_fn.scss: -------------------------------------------------------------------------------- 1 | // sass-lint:disable no-vendor-prefixes 2 | @mixin retina { 3 | @media only screen and (-webkit-min-device-pixel-ratio: 1.5), 4 | only screen and (min--moz-device-pixel-ratio: 1.5), 5 | only screen and (-o-min-device-pixel-ratio: 3 / 2), 6 | only screen and (min-device-pixel-ratio: 1.5) { 7 | @content; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 8 6 | 7 | before_install: 8 | - npm install codecov.io coveralls 9 | 10 | after_success: 11 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js 12 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 13 | 14 | branches: 15 | only: 16 | - master 17 | -------------------------------------------------------------------------------- /src/styles/design/header.scss: -------------------------------------------------------------------------------- 1 | @import "./variable"; 2 | 3 | .design-edit-header { 4 | z-index: 10; 5 | position: fixed; 6 | top: 0; 7 | left: 0; 8 | width: 100%; 9 | display: flex; 10 | align-items: center; 11 | height: $header-height; 12 | background: #fff; 13 | border-bottom: 1px solid #ebedf0; 14 | box-shadow: 0 1px 0 0 rgba(0, 0, 0, .1); 15 | } 16 | -------------------------------------------------------------------------------- /example/src/components/navigationBar/preview.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Preview = function ({ value }) { 4 | 5 | return ( 6 |
7 |
8 | { value.title } 9 |
10 |
11 | ) 12 | }; 13 | 14 | export default React.memo(Preview); 15 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /demo/src/styles/components/whitespace/editor.scss: -------------------------------------------------------------------------------- 1 | .zent-design-component-whitespace-editor { 2 | &__height { 3 | .zent-design-editor__control-group-control { 4 | display: flex; 5 | align-items: center; 6 | 7 | .zent-slider { 8 | flex-grow: 1; 9 | 10 | .zent-slider-main { 11 | border-color: transparent; 12 | } 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Design from './DesignWithDnd'; 2 | import DesignWithoutDnd from './Design'; 3 | import { createGroup } from './utils/component-group'; 4 | import * as designEditor from './editor/DesignEditor'; 5 | 6 | Design.group = createGroup; 7 | Design.DesignWithoutDnd = DesignWithoutDnd; 8 | 9 | export const DesignEditor = designEditor.DesignEditor; 10 | 11 | export const ControlGroup = designEditor.ControlGroup; 12 | 13 | export default Design; 14 | -------------------------------------------------------------------------------- /example/src/components/navigationBar/index.js: -------------------------------------------------------------------------------- 1 | import Editor from './editor'; 2 | import Preview from './preview'; 3 | import './style.scss'; 4 | 5 | export default { 6 | type: Editor.designType, 7 | editor: Editor, 8 | preview: Preview, 9 | // 是否可以拖拽 10 | dragable: false, 11 | 12 | // 是否出现在底部的添加组件区域 13 | appendable: false, 14 | 15 | // 是否可以编辑,UMP里面有些地方config是不能编辑的 16 | editable: true, 17 | 18 | configurable: false, 19 | 20 | highlightWhenSelect: false 21 | }; 22 | -------------------------------------------------------------------------------- /example/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import 'zent/css/index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /example/src/components/notice/preview.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Alert } from 'zent'; 3 | 4 | const Preview = function ({ value }) { 5 | 6 | return ( 7 |
8 | 9 | { value.content } 10 | 11 | 文字链接 12 | 13 | 14 |
15 | ) 16 | }; 17 | 18 | export default React.memo(Preview); 19 | -------------------------------------------------------------------------------- /demo/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './components/index'; 2 | 3 | #demo { 4 | padding: 30px 0; 5 | display: flex; 6 | justify-content: center; 7 | } 8 | 9 | .design-example-actions { 10 | margin-top: 20px; 11 | 12 | .zent-btn { 13 | width: 100px; 14 | margin-right: 10px; 15 | } 16 | } 17 | 18 | .design-page-header { 19 | width: 100%; 20 | display: flex; 21 | justify-content: space-between; 22 | padding: 0 20px; 23 | 24 | &__go-back { 25 | color: #4f4f4f; 26 | cursor: pointer; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /demo/src/components/whitespace/WhitespacePreview.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class WhitespacePreview extends PureComponent { 5 | static propTypes = { 6 | value: PropTypes.object 7 | }; 8 | 9 | render() { 10 | const { value } = this.props; 11 | 12 | return ( 13 |
17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/src/components/search/preview.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input } from 'zent'; 3 | import { PLACEHOLDER } from './constants'; 4 | 5 | const Preview = function (props) { 6 | const { value } = props; 7 | 8 | return ( 9 |
10 | 16 |
17 | ) 18 | }; 19 | 20 | export default React.memo(Preview); 21 | -------------------------------------------------------------------------------- /src/styles/design/alert.scss: -------------------------------------------------------------------------------- 1 | .x-alert-design { 2 | position: relative; 3 | padding: 10px 50px 10px 10px; 4 | margin-bottom: 20px; 5 | background-color: #fffbe6; 6 | border: 1px solid #ffe58f; 7 | color: rgba(0, 0, 0, .65); 8 | overflow: hidden; 9 | 10 | &__restore-cache-alert-use { 11 | text-decoration: none; 12 | cursor: pointer; 13 | color: #1890ff; 14 | } 15 | 16 | &__close { 17 | position: absolute; 18 | top: 10px; 19 | right: 10px; 20 | width: 22px; 21 | height: 22px; 22 | cursor: pointer; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/index-test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | import React from 'react' 3 | import {render, unmountComponentAtNode} from 'react-dom' 4 | 5 | import Component from 'src/' 6 | 7 | describe('Component', () => { 8 | let node 9 | 10 | beforeEach(() => { 11 | node = document.createElement('div') 12 | }) 13 | 14 | afterEach(() => { 15 | unmountComponentAtNode(node) 16 | }) 17 | 18 | it('displays a welcome message', () => { 19 | render(, node, () => { 20 | expect(node.innerHTML).toContain('Welcome to React components') 21 | }) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /demo/src/components/config/ConfigPreview.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class ConfigPreview extends PureComponent { 5 | static propTypes = { 6 | value: PropTypes.object, 7 | 8 | // 用来和 Design 交互 9 | design: PropTypes.object 10 | }; 11 | 12 | render() { 13 | const { value } = this.props; 14 | 15 | return ( 16 |
17 |
18 | {value.title} 19 |
20 |
21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | Example 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /demo/src/components/line/LinePreview.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | 3 | export default class LinePreview extends PureComponent { 4 | render() { 5 | const { value } = this.props; 6 | 7 | return ( 8 |
9 |
10 |
11 | ); 12 | } 13 | } 14 | 15 | function createStyle(value) { 16 | const { color, hasPadding, lineType } = value; 17 | 18 | return { 19 | height: 0, 20 | borderTopWidth: '1px', 21 | margin: hasPadding ? '0 10px' : 0, 22 | borderColor: color, 23 | borderStyle: lineType, 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /example/src/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | .x-alert-design__close { 11 | height: 22px; 12 | } 13 | 14 | .design-page { 15 | padding: 50px 0; 16 | user-select: none; 17 | display: flex; 18 | flex-direction: column; 19 | align-items: center; 20 | } 21 | 22 | .design-page .zent-design { 23 | margin-top: 20px; 24 | } 25 | 26 | .design-page .design-bottom-action { 27 | padding: 20px 0; 28 | } 29 | -------------------------------------------------------------------------------- /src/styles/design/editor.scss: -------------------------------------------------------------------------------- 1 | @import "./theme-default"; 2 | @import "./variable"; 3 | 4 | $control-group-red: $theme-error-2; 5 | 6 | .zent-design-editor__control-group { 7 | &:not(:last-child) { 8 | margin-bottom: 20px; 9 | } 10 | 11 | &-label { 12 | font-size: 14px; 13 | margin-bottom: 15px; 14 | color: #333; 15 | } 16 | 17 | &-help-desc { 18 | color: $theme-stroke-3; 19 | margin-top: 20px; 20 | } 21 | 22 | &-control { 23 | flex-grow: 1; 24 | } 25 | 26 | &-error { 27 | margin-top: 5px; 28 | } 29 | 30 | &-required-star { 31 | color: $control-group-red; 32 | } 33 | 34 | &.has-error { 35 | color: $control-group-red; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/src/components/navigationBar/style.scss: -------------------------------------------------------------------------------- 1 | .zent-design-component-config-preview { 2 | width: 318px; 3 | height: 64px; 4 | background-image: url("../../assets/img/navigation-bar/bar-bg.png"); 5 | background-repeat: no-repeat; 6 | background-size: contain; 7 | 8 | .zent-design-component-config-preview__title { 9 | display: inline-block; 10 | padding: 23px 40px 0 60px; 11 | height: 64px; 12 | line-height: 40px; 13 | width: 100%; 14 | box-sizing: border-box; 15 | text-overflow: ellipsis; 16 | white-space: nowrap; 17 | overflow: hidden; 18 | font-size: 16px; 19 | text-align: center; 20 | vertical-align: middle; 21 | color: #fff; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/config/pnpTs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { resolveModuleName } = require('ts-pnp'); 4 | 5 | exports.resolveModuleName = ( 6 | typescript, 7 | moduleName, 8 | containingFile, 9 | compilerOptions, 10 | resolutionHost 11 | ) => { 12 | return resolveModuleName( 13 | moduleName, 14 | containingFile, 15 | compilerOptions, 16 | resolutionHost, 17 | typescript.resolveModuleName 18 | ); 19 | }; 20 | 21 | exports.resolveTypeReferenceDirective = ( 22 | typescript, 23 | moduleName, 24 | containingFile, 25 | compilerOptions, 26 | resolutionHost 27 | ) => { 28 | return resolveModuleName( 29 | moduleName, 30 | containingFile, 31 | compilerOptions, 32 | resolutionHost, 33 | typescript.resolveTypeReferenceDirective 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /demo/src/styles/components/config/editor.scss: -------------------------------------------------------------------------------- 1 | @import '../../theme-default'; 2 | 3 | .zent-design-component-config-editor { 4 | &__background { 5 | .zent-design-editor__control-group-control { 6 | .zent-design-component-config-editor__background-control { 7 | display: flex; 8 | 9 | .zent-input-wrapper { 10 | flex-grow: 1; 11 | } 12 | 13 | .zent-btn { 14 | margin-left: 10px; 15 | } 16 | } 17 | 18 | .zent-design-component-config-editor__background-hint { 19 | margin-top: 5px; 20 | color: $theme-stroke-3; 21 | } 22 | } 23 | } 24 | 25 | &__tag { 26 | a { 27 | text-decoration: none; 28 | } 29 | 30 | &-create { 31 | margin-left: 5px; 32 | padding-left: 5px; 33 | border-left: 1px solid $theme-stroke-6; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/styles/design/editor-item.scss: -------------------------------------------------------------------------------- 1 | @import './theme-default'; 2 | @import './variable'; 3 | 4 | .zent-design-editor-item { 5 | font-size: 12px; 6 | position: fixed; 7 | top: 0; 8 | right: 0; 9 | bottom: 0; 10 | width: $editor-width; 11 | padding: 80px 20px 50px; 12 | background: #fff; 13 | overflow-y: auto; 14 | box-shadow: 0 0 5px rgba(0, 0, 0, .1); 15 | box-sizing: border-box; 16 | 17 | &::-webkit-scrollbar { 18 | display: none; 19 | } 20 | 21 | a { 22 | color: $theme-primary-4; 23 | text-decoration: none; 24 | cursor: pointer; 25 | } 26 | 27 | &.zent-design-add-component-overlay { 28 | width: 293px; 29 | box-sizing: content-box; 30 | padding: 0 0 10px; 31 | margin-top: -20px; 32 | 33 | &.zent-design-add-component-overlay--bottom { 34 | top: 100%; 35 | } 36 | 37 | &.zent-design-add-component-overlay--top { 38 | top: 0; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/src/components/notice/editor.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input } from 'zent'; 3 | import { DesignEditor, ControlGroup } from 'micro-design-editor/es/editor/DesignEditor'; 4 | 5 | export default class Editor extends DesignEditor { 6 | 7 | static designType = 'notice'; 8 | static designDescription = '公告'; 9 | static getInitialValue() { 10 | return { 11 | content: '通知提示文案' 12 | }; 13 | } 14 | 15 | static validate() { 16 | return new Promise(resolve => { 17 | resolve(); 18 | }); 19 | } 20 | 21 | render() { 22 | const { value } = this.props; 23 | 24 | return ( 25 |
26 | 29 | 35 | 36 |
37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/editor/DesignEditorItem.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import cx from 'classnames'; 3 | import PropTypes from 'prop-types'; 4 | 5 | export default class DesignEditorItem extends PureComponent { 6 | static propTypes = { 7 | children: PropTypes.node.isRequired, 8 | disabled: PropTypes.bool, 9 | className: PropTypes.string 10 | }; 11 | 12 | static defaultProps = { 13 | disabled: false 14 | }; 15 | 16 | constructor(props) { 17 | super(props); 18 | this.node = React.createRef(); 19 | } 20 | 21 | render() { 22 | const { disabled, className } = this.props; 23 | 24 | return ( 25 |
26 | {disabled &&
} 27 | {this.props.children} 28 |
29 | ); 30 | } 31 | 32 | getBoundingBox() { 33 | const node = this.node.current; 34 | return node && node.getBoundingClientRect(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /demo/src/styles/components/config/preview.scss: -------------------------------------------------------------------------------- 1 | @import '../../fn'; 2 | @import '../../theme-default'; 3 | @import '../../variable'; 4 | 5 | .zent-design-component-config-preview { 6 | height: $preview-header-height; 7 | background-image: url('https://img.yzcdn.cn/public_files/2019/02/11/14417a76b49dac2851efaf744f87cdb4.png'); 8 | background-repeat: no-repeat; 9 | background-size: contain; 10 | 11 | &__title { 12 | display: inline-block; 13 | padding: 23px 60px 0 60px; 14 | height: $preview-header-height; 15 | line-height: $preview-header-height - 24px; 16 | width: 100%; 17 | box-sizing: border-box; 18 | text-overflow: ellipsis; 19 | white-space: nowrap; 20 | overflow: hidden; 21 | font-weight: bold; 22 | font-size: 16px; 23 | text-align: center; 24 | vertical-align: middle; 25 | border-bottom: 1px solid #eee; 26 | } 27 | } 28 | 29 | @include retina { 30 | .zent-design-component-config-preview { 31 | background-image: url('https://img.yzcdn.cn/public_files/2017/10/24/5b4715536aaa9c22dc5be27b000f9bed.png'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/utils/LazyMap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A lazy map with default value. 3 | * 4 | * If most of the entries in your map defaults to the same value, you can use LazyMap to save memory. 5 | */ 6 | import { has } from 'lodash'; 7 | 8 | export default class LazyMap { 9 | constructor(defaultValue, map = {}) { 10 | this.defaultValue = defaultValue; 11 | this.map = map; 12 | } 13 | 14 | get(key) { 15 | if (has(this.map, key)) { 16 | return this.map[key]; 17 | } 18 | 19 | return this.defaultValue; 20 | } 21 | 22 | set(key, value) { 23 | this.map[key] = value; 24 | return this; 25 | } 26 | 27 | clone() { 28 | return new LazyMap(this.defaultValue, this.map); 29 | } 30 | 31 | has(key) { 32 | return has(this.map, key); 33 | } 34 | 35 | /* Use this iff value is a number */ 36 | inc(key) { 37 | const oldValue = this.get(key); 38 | this.set(key, oldValue + 1); 39 | return this; 40 | } 41 | 42 | /* Use this iff value is a number */ 43 | dec(key) { 44 | const oldValue = this.get(key); 45 | this.set(key, oldValue - 1); 46 | return this; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 xiejiahe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/styles/design/preview.scss: -------------------------------------------------------------------------------- 1 | @import './variable'; 2 | @import './theme-default'; 3 | 4 | .zent-design-preview { 5 | height: 100%; 6 | position: relative; 7 | user-select: none; 8 | display: flex; 9 | flex: 1; 10 | 11 | .design-preview-main { 12 | display: flex; 13 | flex: 1; 14 | justify-content: center; 15 | padding: 20px 0; 16 | padding-right: $editor-width; 17 | background-color: #f7f8fa; 18 | overflow-y: auto; 19 | 20 | &::-webkit-scrollbar { 21 | display: none; 22 | } 23 | 24 | .zent-design__item-list { 25 | position: relative; 26 | width: 375px; 27 | height: calc(100vh - 150px); 28 | box-shadow: 0 0 14px 0 rgba(0, 0, 0, .1); 29 | overflow-x: hidden; 30 | overflow-y: auto; 31 | 32 | &::-webkit-scrollbar { 33 | display: none; 34 | } 35 | } 36 | } 37 | 38 | .zent-design-preview-item { 39 | position: relative; 40 | } 41 | 42 | .zent-design-add-component-overlay--grouped { 43 | background: $theme-stroke-9; 44 | 45 | &:after { 46 | border-right-color: $theme-stroke-9; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/preview/DesignPreviewItem.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import scroll from '../utils/scroll'; 4 | import offset from '../utils/offset'; 5 | import { isFunction } from 'lodash'; 6 | 7 | function scrollNodeToTop(node, offsets) { 8 | const pos = offset(node); 9 | const top = isFunction(offsets.top) 10 | ? offsets.top(pos.top) 11 | : pos.top + offsets.top; 12 | const left = isFunction(offsets.left) 13 | ? offsets.left(pos.left) 14 | : pos.left + offsets.left; 15 | scroll(document.body, left, top); 16 | } 17 | 18 | export default class DesignPreviewItem extends PureComponent { 19 | static propTypes = { 20 | children: PropTypes.node.isRequired 21 | }; 22 | 23 | constructor(props) { 24 | super(props); 25 | 26 | this.node = React.createRef(); 27 | } 28 | 29 | render() { 30 | const { children } = this.props; 31 | 32 | return
{children}
; 33 | } 34 | 35 | scrollTop(offsets) { 36 | const node = this.node.current; 37 | scrollNodeToTop(node, offsets); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /demo/src/components/whitespace/WhitespaceEditor.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Slider from 'zent/es/slider'; 3 | 4 | import { DesignEditor, ControlGroup } from '../../../../src/editor/DesignEditor'; 5 | 6 | export default class WhitespaceEditor extends DesignEditor { 7 | render() { 8 | const { value, onChange } = this.props; 9 | 10 | return ( 11 |
12 | 16 | onChange({ height: value })} 21 | withInput={false} 22 | /> 23 | {value.height} 像素 24 | 25 |
26 | ); 27 | } 28 | 29 | // 组件的类型 30 | static designType = 'white'; 31 | 32 | // 组件的描述 33 | static designDescription = ( 34 | 35 | 辅助 36 |
37 | 空白 38 |
39 | ); 40 | 41 | // 添加组件时调用,用来获取新组件的初始值 42 | static getInitialValue() { 43 | return { 44 | height: 30, 45 | }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/reorder.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Utility to reorder list 3 | * Scans the list only once. 4 | */ 5 | 6 | export default function reorder( 7 | array, 8 | fromIndex, 9 | toIndex 10 | ) { 11 | const lastIndex = array.length - 1; 12 | const firstIndex = 0; 13 | const result = new Array(array.length); 14 | let tmp; 15 | 16 | if (fromIndex === toIndex) { 17 | return array; 18 | } 19 | 20 | if (fromIndex < toIndex) { 21 | for (let i = firstIndex; i <= lastIndex; i++) { 22 | if (i === fromIndex) { 23 | tmp = array[i]; 24 | } else if (i > fromIndex && i < toIndex) { 25 | result[i - 1] = array[i]; 26 | } else if (i === toIndex) { 27 | result[i - 1] = array[i]; 28 | result[i] = tmp; 29 | } else { 30 | result[i] = array[i]; 31 | } 32 | } 33 | } else { 34 | for (let i = lastIndex; i >= firstIndex; i--) { 35 | if (i === fromIndex) { 36 | tmp = array[i]; 37 | } else if (i < fromIndex && i > toIndex) { 38 | result[i + 1] = array[i]; 39 | } else if (i === toIndex) { 40 | result[i] = tmp; 41 | result[i + 1] = array[i]; 42 | } else { 43 | result[i] = array[i]; 44 | } 45 | } 46 | } 47 | 48 | return result; 49 | } -------------------------------------------------------------------------------- /example/src/components/search/editor.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input } from 'zent'; 3 | import { DesignEditor, ControlGroup } from 'micro-design-editor/es/editor/DesignEditor'; 4 | import { PLACEHOLDER } from './constants'; 5 | 6 | export default class Editor extends DesignEditor { 7 | 8 | static designType = 'search'; 9 | static designDescription = '搜索'; 10 | static getInitialValue(settings, globalConfig) { 11 | return { 12 | placeholder: '' 13 | }; 14 | } 15 | 16 | static validate() { 17 | return new Promise(resolve => { 18 | // 一旦传入对象即抛出异常 19 | resolve(); 20 | }); 21 | } 22 | 23 | render() { 24 | const { value, showError, validation, onChange } = this.props; 25 | 26 | return ( 27 |
28 | 34 | onChange({ placeholder: e.target.value })} 39 | onBlur={this.onInputBlur} 40 | maxLength={50} 41 | /> 42 | 43 |
44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/src/components/navigationBar/editor.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input } from 'zent'; 3 | import { DesignEditor, ControlGroup } from 'micro-design-editor/es/editor/DesignEditor'; 4 | 5 | export default class Editor extends DesignEditor { 6 | render() { 7 | const { value, showError, validation } = this.props; 8 | 9 | return ( 10 |
11 | 17 | 23 | 24 |
25 | ); 26 | } 27 | 28 | static designType = 'navigationBar'; 29 | static designDescription = '页面标题'; 30 | static getInitialValue() { 31 | return { 32 | title: '标题' 33 | }; 34 | } 35 | 36 | static validate({ title }) { 37 | return new Promise(resolve => { 38 | const errors = {}; 39 | if (!title || !title.trim()) { 40 | errors.title = '请填写页面标题'; 41 | } else if (title.length > 10) { 42 | errors.title = '页面标题长度不能多于 10 个字'; 43 | } 44 | 45 | resolve(errors); 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/utils/design-type.js: -------------------------------------------------------------------------------- 1 | import { isString, isArray, isNumber, isFunction } from 'lodash'; 2 | 3 | export function getDesignType(editor, defaultType) { 4 | const { designType } = editor; 5 | 6 | if (isString(designType)) { 7 | if (isFunction(defaultType)) { 8 | return defaultType(designType); 9 | } 10 | return designType; 11 | } 12 | 13 | if (isArray(designType) && designType.length > 0) { 14 | if (isNumber(defaultType)) { 15 | return designType[defaultType || 0]; 16 | } 17 | 18 | if (isFunction(defaultType)) { 19 | return defaultType(designType); 20 | } 21 | 22 | return designType[0]; 23 | } 24 | 25 | throw new TypeError('designType should be a string or an array of strings'); 26 | } 27 | 28 | export function isExpectedDesignType(component, expected) { 29 | const { type } = component; 30 | 31 | if (isString(type)) { 32 | return expected === type; 33 | } 34 | 35 | if (isArray(type)) { 36 | return type.indexOf(expected) !== -1; 37 | } 38 | 39 | return false; 40 | } 41 | 42 | export function serializeDesignType(designType) { 43 | if (isString(designType)) { 44 | return designType; 45 | } 46 | if (isArray(designType)) { 47 | return designType.join(' | '); 48 | } 49 | 50 | throw new TypeError('designType should be a string or an array of strings'); 51 | } 52 | 53 | export const COMPONENT_GROUP_DESIGN_TYPE = '__zent-design-component-group__'; 54 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /demo/src/styles/components/richtext/index.scss: -------------------------------------------------------------------------------- 1 | @import './editor'; 2 | 3 | .zent-design-component-richtext-editor { 4 | .input-append { 5 | font-size: 0; 6 | display: inline-block; 7 | margin-bottom: 0; 8 | vertical-align: middle; 9 | 10 | .zent-color-picker { 11 | margin-right: 0; 12 | vertical-align: top; 13 | } 14 | 15 | .zent-color-picker__text { 16 | border-radius: 4px 0 0 4px; 17 | } 18 | 19 | .zent-btn { 20 | border-radius: 0 4px 4px 0; 21 | margin-left: -1px; 22 | background: $theme-stroke-8; 23 | 24 | &:hover { 25 | color: $theme-stroke-1; 26 | border-color: $theme-stroke-4; 27 | } 28 | } 29 | } 30 | 31 | .control-label { 32 | display: inline-block; 33 | width: 85px; 34 | text-align: right; 35 | vertical-align: middle; 36 | } 37 | } 38 | 39 | .zent-design-component-richtext-preview { 40 | padding: 0 10px; 41 | padding-top: 10px; 42 | box-sizing: border-box; 43 | font-size: 16px; 44 | color: $theme-stroke-1; 45 | line-height: 1.5; 46 | overflow: hidden; 47 | text-align: left; 48 | word-wrap: break-word; 49 | position: relative; 50 | 51 | &--fullscreen { 52 | padding: 0; 53 | margin: 0; 54 | } 55 | 56 | table, 57 | th, 58 | td { 59 | border: 1px solid $theme-stroke-6; 60 | } 61 | 62 | table { 63 | border-collapse: collapse; 64 | } 65 | 66 | td { 67 | padding: 5px 10px; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import "./design/variable"; 2 | @import "./design/preview"; 3 | @import "./design/editor-item"; 4 | @import "./design/preview-controller"; 5 | @import "./design/editor-add-component"; 6 | @import "./design/editor"; 7 | @import "./design/alert"; 8 | @import "./design/header"; 9 | 10 | .zent-design { 11 | text-rendering: optimizelegibility; 12 | font-family: inherit; 13 | z-index: 99; 14 | position: fixed; 15 | top: 0; 16 | left: 0; 17 | right: 0; 18 | bottom: 0; 19 | background: #fff; 20 | padding-top: $header-height; 21 | overflow: hidden; 22 | display: flex; 23 | flex-direction: row-reverse; 24 | 25 | &__add { 26 | width: 200px; 27 | padding: 15px 0; 28 | position: relative; 29 | overflow: hidden; 30 | overflow-y: auto; 31 | box-shadow: 5px 0 5px 0px #eee; 32 | 33 | &::-webkit-scrollbar { 34 | display: none; 35 | } 36 | 37 | &--mixed { 38 | background: $theme-stroke-8; 39 | 40 | &:after { 41 | border-bottom-color: $theme-stroke-8; 42 | } 43 | } 44 | 45 | &--grouped { 46 | 47 | &:after { 48 | border-bottom-color: $theme-stroke-9; 49 | } 50 | } 51 | } 52 | 53 | &__disabled-mask { 54 | position: absolute; 55 | z-index: 10; 56 | background-color: rgba(black, 0.2); 57 | height: 100%; 58 | width: 100%; 59 | cursor: not-allowed; 60 | top: 0; 61 | left: 0; 62 | } 63 | 64 | .hide-scrollbar::-webkit-scrollbar { 65 | display: none; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/styles/design/preview-controller.scss: -------------------------------------------------------------------------------- 1 | @import './theme-default'; 2 | 3 | .zent-design-preview-controller { 4 | position: relative; 5 | box-sizing: border-box; 6 | border: 0; 7 | 8 | &__action-btn-delete { 9 | position: absolute; 10 | top: -10px; 11 | right: 0; 12 | z-index: 15; 13 | font-size: 0; 14 | line-height: 0; 15 | cursor: pointer; 16 | } 17 | 18 | &__icon-delete { 19 | display: none; 20 | 21 | circle { 22 | fill: $theme-stroke-4; 23 | } 24 | 25 | &:hover { 26 | circle { 27 | fill: $theme-stroke-3; 28 | } 29 | } 30 | } 31 | 32 | &--editable { 33 | cursor: pointer; 34 | } 35 | 36 | &--dragable { 37 | cursor: move; 38 | } 39 | 40 | .zent-pop-wrapper { 41 | margin-left: 1px; 42 | } 43 | 44 | &:hover &__icon-delete, 45 | &:hover &__action-btn-add-container { 46 | display: block; 47 | } 48 | } 49 | 50 | .zent-design-preview-controller--highlight.zent-design-preview-controller { 51 | &:before { 52 | content: ''; 53 | position: absolute; 54 | width: 100%; 55 | height: 100%; 56 | top: 0; 57 | left: 0; 58 | box-sizing: border-box; 59 | pointer-events: none; 60 | display: none; 61 | } 62 | 63 | &:hover, 64 | &--selected { 65 | &:before { 66 | z-index: 2; 67 | display: block; 68 | border: 1px dashed $theme-primary-4; 69 | } 70 | } 71 | 72 | &:hover:before { 73 | opacity: 0.5; 74 | } 75 | 76 | &--selected:before { 77 | opacity: 1; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "micro-design-editor", 3 | "version": "2.0.0-beta.8", 4 | "bugs": { 5 | "url": "https://github.com/xjh22222228/micro-design-editor/issues" 6 | }, 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/xjh22222228/micro-design-editor.git" 10 | }, 11 | "description": "design React component", 12 | "main": "es/index.js", 13 | "module": "es/index.js", 14 | "files": [ 15 | "css", 16 | "es" 17 | ], 18 | "scripts": { 19 | "build": "nwb build-react-component --no-demo && npm run build-css --scripts-prepend-node-path", 20 | "build-css": "./scripts/build-css.sh", 21 | "build-gh": "node gh.js", 22 | "clean": "nwb clean-module && nwb clean-demo && rm -rf css", 23 | "start": "nwb serve-react-demo", 24 | "test": "nwb test-react", 25 | "test:coverage": "nwb test-react --coverage", 26 | "test:watch": "nwb test-react --server" 27 | }, 28 | "dependencies": { 29 | "babel-runtime": "^6.26.0", 30 | "classnames": "^2.2.6", 31 | "lodash": "^4.17.15", 32 | "prop-types": "^15.7.2", 33 | "react-beautiful-dnd": "12.2.0", 34 | "uuid": "^3.3.3" 35 | }, 36 | "peerDependencies": { 37 | "react": "16.x" 38 | }, 39 | "devDependencies": { 40 | "fs-extra": "^8.1.0", 41 | "gh-pages": "^2.1.1", 42 | "node-sass": "^4.13.0", 43 | "node-sass-magic-importer": "^5.3.2", 44 | "nwb": "0.23.x", 45 | "nwb-sass": "^0.9.0", 46 | "react": "^16.8.2", 47 | "react-dom": "^16.12.0", 48 | "zent": "7.4.3" 49 | }, 50 | "license": "MIT", 51 | "keywords": [ 52 | "react-component", 53 | "micro-design-editor", 54 | "design" 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /src/utils/shallowEqual.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adapted from https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/shallowEqual.js 3 | */ 4 | 5 | const hasOwnProperty = Object.prototype.hasOwnProperty; 6 | 7 | /** 8 | * inlined Object.is polyfill to avoid requiring consumers ship their own 9 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is 10 | */ 11 | function is(x, y) { 12 | // SameValue algorithm 13 | if (x === y) { 14 | // Steps 1-5, 7-10 15 | // Steps 6.b-6.e: +0 != -0 16 | return x !== 0 || 1 / x === 1 / y; 17 | } 18 | // Step 6.a: NaN == NaN 19 | return x !== x && y !== y; // eslint-disable-line 20 | } 21 | 22 | /** 23 | * Performs equality by iterating through keys on an object and returning false 24 | * when any key has values which are not strictly equal between the arguments. 25 | * Returns true when the values of all keys are strictly equal. 26 | */ 27 | function shallowEqual(objA, objB) { 28 | if (is(objA, objB)) { 29 | return true; 30 | } 31 | 32 | if ( 33 | typeof objA !== 'object' || 34 | objA === null || 35 | typeof objB !== 'object' || 36 | objB === null 37 | ) { 38 | return false; 39 | } 40 | 41 | const keysA = Object.keys(objA); 42 | const keysB = Object.keys(objB); 43 | 44 | if (keysA.length !== keysB.length) { 45 | return false; 46 | } 47 | 48 | // Test for A's keys different from B. 49 | for (let i = 0; i < keysA.length; i++) { 50 | if ( 51 | !hasOwnProperty.call(objB, keysA[i]) || 52 | !is(objA[keysA[i]], objB[keysA[i]]) 53 | ) { 54 | return false; 55 | } 56 | } 57 | 58 | return true; 59 | } 60 | 61 | export default shallowEqual; -------------------------------------------------------------------------------- /src/utils/storage.js: -------------------------------------------------------------------------------- 1 | export const NOT_FOUND = () => {}; 2 | 3 | export function read(namespace, key) { 4 | const ns = readNamespace(namespace); 5 | if (ns !== NOT_FOUND && ns && ns.hasOwnProperty(key)) { 6 | return ns[key]; 7 | } 8 | 9 | return NOT_FOUND; 10 | } 11 | 12 | /** 13 | * 将 key 对应的 value 写入 namespace 下 14 | * 比较暴力,如果无法写入会把所有 Design 相关的缓存清除 15 | * @param {string} namespace 16 | * @param {string} key 17 | * @param {any} value 18 | * @return {bool} true 表示成功,false 写入失败 19 | */ 20 | export function write( 21 | namespace, 22 | key, 23 | value 24 | ) { 25 | let ns = readNamespace(namespace); 26 | const isRemove = value === undefined; 27 | 28 | // 不存在就创建一个新的 29 | if (ns === NOT_FOUND) { 30 | ns = {}; 31 | } 32 | 33 | if (isRemove) { 34 | // 删除 35 | delete ns[key]; 36 | } else { 37 | // 新增/更新 38 | ns[key] = value; 39 | } 40 | 41 | if (writeNamespace(namespace, ns)) { 42 | return true; 43 | } 44 | 45 | // 写入失败,尝试清空 namespace 下的所有值再重试 46 | ns = { [key]: value }; 47 | return writeNamespace(namespace, ns); 48 | } 49 | 50 | export function remove(namespace, key) { 51 | return write(namespace, key, undefined); 52 | } 53 | 54 | function readNamespace(namespace) { 55 | const ns = localStorage.getItem(namespace); 56 | if (!ns) { 57 | return NOT_FOUND; 58 | } 59 | 60 | try { 61 | return JSON.parse(ns); 62 | } catch (ex) { 63 | return NOT_FOUND; 64 | } 65 | } 66 | 67 | function writeNamespace(namespace, value) { 68 | try { 69 | if (Object.keys(value).length === 0) { 70 | localStorage.removeItem(namespace); 71 | } else { 72 | const ns = JSON.stringify(value); 73 | localStorage.setItem(namespace, ns); 74 | } 75 | } catch (ex) { 76 | return false; 77 | } 78 | 79 | return true; 80 | } 81 | -------------------------------------------------------------------------------- /demo/src/styles/components/image-ad/preview.scss: -------------------------------------------------------------------------------- 1 | @import '../../theme-default'; 2 | 3 | .zent-design-component-image-ad-preview { 4 | position: relative; 5 | 6 | &--no-data { 7 | height: 100px; 8 | line-height: 100px; 9 | text-align: center; 10 | background: $theme-primary-4; 11 | color: $theme-stroke-9; 12 | } 13 | 14 | &__image { 15 | box-sizing: border-box; 16 | position: relative; 17 | 18 | &-title { 19 | position: absolute; 20 | bottom: 0; 21 | left: 0; 22 | height: 30px; 23 | width: 100%; 24 | line-height: 30px; 25 | background: rgba(black, 0.6); 26 | color: $theme-stroke-9; 27 | text-align: center; 28 | overflow: hidden; 29 | text-overflow: ellipsis; 30 | white-space: nowrap; 31 | } 32 | 33 | &-img { 34 | padding-top: 100%; 35 | position: relative; 36 | } 37 | 38 | img { 39 | max-width: 100%; 40 | max-height: 100%; 41 | position: absolute; 42 | top: 50%; 43 | left: 50%; 44 | transform: translate(-50%, -50%); 45 | } 46 | } 47 | 48 | &--small { 49 | display: flex; 50 | flex-wrap: wrap; 51 | 52 | .zent-design-component-image-ad-preview__image { 53 | width: 50%; 54 | flex: 0 0 50%; 55 | box-sizing: border-box; 56 | margin-top: 5px; 57 | 58 | // first two 59 | &:nth-child(-n + 2) { 60 | margin-top: 0; 61 | } 62 | 63 | // odd 64 | &:nth-child(2n + 1) { 65 | padding-right: 3px; 66 | } 67 | 68 | // even 69 | &:nth-child(2n) { 70 | padding-left: 3px; 71 | } 72 | } 73 | } 74 | 75 | &--large { 76 | .zent-design-component-image-ad-preview__image { 77 | width: 100%; 78 | margin-top: 5px; 79 | 80 | &:first-child { 81 | margin-top: 0; 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /demo/src/components/image-ad/ImageAdPreview.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import cx from 'classnames'; 3 | import isEmpty from 'lodash/isEmpty'; 4 | 5 | import { IMAGE_SIZE, IMAGE_AD_ENTRY_UUID_KEY } from './constants'; 6 | 7 | export default class ImageAdPreview extends Component { 8 | render() { 9 | const { value } = this.props; 10 | const { size, images } = value; 11 | 12 | if (isEmpty(images)) { 13 | return ( 14 |
20 | 点击编辑图片广告 21 |
22 | ); 23 | } 24 | 25 | return ( 26 |
34 | {images.map(img => { 35 | const id = img[IMAGE_AD_ENTRY_UUID_KEY]; 36 | // eslint-disable-next-line 37 | const url = img.linkUrl || 'javascript:void(0);'; 38 | const title = img.linkTitle; 39 | 40 | return ( 41 | 48 |
51 | {title} 52 | {title && ( 53 |
56 | {title} 57 |
58 | )} 59 |
60 |
61 | ); 62 | })} 63 |
64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/utils/component-group.js: -------------------------------------------------------------------------------- 1 | import { uniqueId, startsWith, isEmpty } from 'lodash'; 2 | import { COMPONENT_GROUP_DESIGN_TYPE } from './design-type'; 3 | 4 | function DummyComponent() { 5 | return null; 6 | } 7 | 8 | export function createGroup(name) { 9 | return { 10 | type: `${uniqueId(COMPONENT_GROUP_DESIGN_TYPE)}|${name}`, 11 | editor: DummyComponent, 12 | preview: DummyComponent, 13 | name, 14 | }; 15 | } 16 | 17 | export function isGroupComponent(component) { 18 | if (!component) { 19 | return false; 20 | } 21 | 22 | return startsWith(component.type, COMPONENT_GROUP_DESIGN_TYPE); 23 | } 24 | 25 | /** 26 | * Check if component array is grouped. 27 | * 28 | * A grouped component array MUST have a group component as its first element. 29 | * @param {Array} components 30 | */ 31 | export function isGrouped(components) { 32 | // Grouped components must have at least 2 elements 33 | if (!components || components.length < 2) { 34 | return false; 35 | } 36 | 37 | const possiblyGroup = components[0]; 38 | return isGroupComponent(possiblyGroup); 39 | } 40 | 41 | /** 42 | * Split component array into an array of groups 43 | * 44 | * @param {Array} components 45 | */ 46 | export function splitGroup(components) { 47 | if (isEmpty(components)) { 48 | return []; 49 | } 50 | 51 | const lastIndex = components.length - 1; 52 | 53 | return components.reduce( 54 | (state, c, idx) => { 55 | const { groups, buffer, group } = state; 56 | const isGroup = isGroupComponent(c); 57 | 58 | if (!isGroup) { 59 | buffer.push(c); 60 | } 61 | 62 | // When processing the last component, ensure buffer is consumed 63 | if (isGroup || idx === lastIndex) { 64 | // Empty group is ignored 65 | if (group && !isEmpty(buffer)) { 66 | groups.push({ 67 | group, 68 | components: buffer, 69 | }); 70 | } 71 | 72 | // Start a new group 73 | state.buffer = []; 74 | state.group = c; 75 | } 76 | 77 | return state; 78 | }, 79 | { groups: [], buffer: [], group: null } 80 | ).groups; 81 | } 82 | -------------------------------------------------------------------------------- /example/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './app.css'; 3 | import 'micro-design-editor/css/index.css'; 4 | import Design from 'micro-design-editor/es/index'; 5 | import _cloneDeep from 'lodash/cloneDeep'; 6 | import navigationBarConf from './components/navigationBar'; 7 | import searchConf from './components/search'; 8 | import noticeConf from './components/notice'; 9 | import { Button, Notify } from 'zent'; 10 | 11 | const groupedComponents = [ 12 | navigationBarConf, 13 | 14 | Design.group('基础'), 15 | searchConf, 16 | noticeConf, 17 | 18 | Design.group('其他'), 19 | searchConf, 20 | ]; 21 | 22 | class App extends Component { 23 | state = { 24 | value: [ 25 | { 26 | type: navigationBarConf.type, 27 | ...navigationBarConf.editor.getInitialValue() 28 | } 29 | ] 30 | }; 31 | 32 | onChange = newValue => { 33 | this.setState({ value: newValue }); 34 | } 35 | 36 | onSettingsChange = newSettings => { 37 | this.setState({ settings: newSettings }); 38 | } 39 | 40 | /** 41 | * @param {Design} instance - Design 组件的实例, 用于调用组件里的方法 42 | */ 43 | saveDesign = instance => { 44 | this.design = instance && instance.getDecoratedComponentInstance(); 45 | 46 | // 恢复上一次未保存数据 47 | const cache = this.design && this.design.readCache(); 48 | if (Array.isArray(cache)) { 49 | this.setState({ value: cache }); 50 | } 51 | } 52 | 53 | onFormSubmit = () => { 54 | this.design.validate() 55 | .then(() => { 56 | // 如果需要对数据处理,需要对原数据进行拷贝 57 | let data = _cloneDeep(this.state.value); 58 | const stringify = JSON.stringify(data, null, 2); 59 | console.log(stringify); 60 | // 标记为以保存状态,如果使用了缓存或者离开提示需要手动调用这个函数通知 Design 更改已经保存 61 | // localStorage 将被清除 62 | this.design.markAsSaved(); 63 | Notify.success('保存成功'); 64 | }) 65 | .catch(validations => { 66 | Notify.warning('保存失败, 请检查配置'); 67 | }); 68 | } 69 | 70 | render() { 71 | return ( 72 |
73 | 86 |
87 | 88 |
89 |
90 | ); 91 | } 92 | } 93 | 94 | export default App; 95 | -------------------------------------------------------------------------------- /demo/src/components/line/LineEditor.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Radio from 'zent/es/radio'; 3 | import ColorPicker from 'zent/es/colorpicker'; 4 | 5 | import { DesignEditor, ControlGroup } from '../../../../src/editor/DesignEditor'; 6 | 7 | const RadioGroup = Radio.Group; 8 | const DEFAULT_COLOR = '#e5e5e5'; 9 | 10 | export default class LineEditor extends DesignEditor { 11 | render() { 12 | const { value, showError, validation } = this.props; 13 | 14 | return ( 15 |
16 | 21 | 26 | 30 | 重置 31 | 32 | 33 | 38 | 39 | 40 | 无边距 41 | 42 | 43 | 左右留边 44 | 45 | 46 | 47 | 52 | 53 | 54 | 实线 55 | 56 | 57 | 虚线 58 | 59 | 60 | 点线 61 | 62 | 63 | 64 |
65 | ); 66 | } 67 | 68 | onColorChange = this.onCustomInputChange('color'); 69 | 70 | onColorReset = () => { 71 | this.onColorChange(DEFAULT_COLOR); 72 | }; 73 | 74 | static designType = 'line'; 75 | 76 | static designDescription = '辅助线'; 77 | 78 | static getInitialValue() { 79 | return { 80 | color: DEFAULT_COLOR, 81 | hasPadding: false, 82 | lineType: 'solid', 83 | }; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/utils/scroll.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adapted from https://github.com/alicelieutier/smoothScroll 3 | */ 4 | 5 | import isBrowser from './isBrowser'; 6 | 7 | const SCROLL_TIME = 250; 8 | const w = isBrowser ? window : {}; 9 | const d = isBrowser ? document : {}; 10 | const originalScroll = w.scroll || w.scrollTo; 11 | const now = 12 | w.performance && w.performance.now 13 | ? w.performance.now.bind(w.performance) 14 | : Date.now; 15 | 16 | /** 17 | * changes scroll position inside an element 18 | */ 19 | function scrollElement(x, y) { 20 | this.scrollLeft = x; 21 | this.scrollTop = y; 22 | } 23 | 24 | /** 25 | * returns result of applying ease math function to a number 26 | * @method ease 27 | */ 28 | function ease(k) { 29 | return 0.5 * (1 - Math.cos(Math.PI * k)); 30 | } 31 | 32 | /** 33 | * self invoked function that, given a context, steps through scrolling 34 | * @method step 35 | * @param {Object} context 36 | */ 37 | function step(context) { 38 | const time = now(); 39 | let elapsed = (time - context.startTime) / context.duration; 40 | 41 | // avoid elapsed times higher than one 42 | elapsed = elapsed > 1 ? 1 : elapsed; 43 | 44 | // apply easing to elapsed time 45 | const value = ease(elapsed); 46 | const currentX = context.startX + (context.x - context.startX) * value; 47 | const currentY = context.startY + (context.y - context.startY) * value; 48 | 49 | context.method.call(context.scrollable, currentX, currentY); 50 | 51 | // scroll more if we have not reached our destination 52 | if (currentX !== context.x || currentY !== context.y) { 53 | requestAnimationFrame(step.bind(w, context)); 54 | } 55 | } 56 | 57 | /** 58 | * scrolls element with a smooth behavior 59 | * @method smoothScroll 60 | * @param {Object|Node} el element to scroll 61 | * @param {Number} x target position x 62 | * @param {Number} y target position y 63 | * @param {Number} duration animation duration 64 | */ 65 | export default function smoothScroll( 66 | el, 67 | x, 68 | y, 69 | duration = SCROLL_TIME 70 | ) { 71 | if (!isBrowser) { 72 | return; 73 | } 74 | 75 | let scrollable; 76 | let startX; 77 | let startY; 78 | let method; 79 | const startTime = now(); 80 | 81 | // define scroll context 82 | if (el === d.body || el === w) { 83 | scrollable = w; 84 | startX = w.scrollX || w.pageXOffset; 85 | startY = w.scrollY || w.pageYOffset; 86 | method = originalScroll; 87 | } else { 88 | scrollable = el; 89 | startX = el.scrollLeft; 90 | startY = el.scrollTop; 91 | method = scrollElement; 92 | } 93 | 94 | // scroll looping over a frame 95 | step({ 96 | duration, 97 | scrollable, 98 | method, 99 | startTime, 100 | startX, 101 | startY, 102 | x, 103 | y, 104 | }); 105 | } 106 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `yarn test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `yarn build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `yarn eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `yarn build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /src/styles/design/_theme-default.scss: -------------------------------------------------------------------------------- 1 | // Text colors 2 | $theme-stroke-1: #323233 !default; // text 3 | $theme-stroke-2: #646566 !default; // text 4 | $theme-stroke-3: #969799 !default; // help text 5 | $theme-stroke-4: #c8c9cc !default; // disabled text 6 | 7 | // Border and line colors 8 | $theme-stroke-5: #dcdee0 !default; 9 | $theme-stroke-6: #ebedf0 !default; 10 | 11 | // Background colors 12 | $theme-stroke-7: #f2f3f5 !default; 13 | $theme-stroke-8: #f7f8fa !default; 14 | $theme-stroke-9: #fff !default; // White button normal background color 15 | 16 | // Button background color 17 | $theme-stroke-10: #eaeaea !default; // Active 18 | $theme-stroke-11: #f4f5f5 !default; // Hover 19 | 20 | // Blue(primary) 21 | $theme-primary-1: #0a2a61 !default; 22 | $theme-primary-2: #10439b !default; 23 | $theme-primary-3: #114db4 !default; 24 | $theme-primary-4: #155bd4 !default; 25 | $theme-primary-5: #3773da !default; 26 | $theme-primary-6: #5487df !default; 27 | $theme-primary-7: #94b4eb !default; 28 | $theme-primary-8: #edf4ff !default; 29 | 30 | // Green 31 | $theme-success-1: #268d37 !default; 32 | $theme-success-2: #2da641 !default; 33 | $theme-success-3: #4cb35d !default; 34 | $theme-success-4: #66be74 !default; 35 | $theme-success-5: #f0faf2 !default; 36 | 37 | // Red 38 | $theme-error-1: #b40000 !default; 39 | $theme-error-2: #d40000 !default; 40 | $theme-error-3: #da2626 !default; 41 | $theme-error-4: #df4545 !default; 42 | $theme-error-5: #ffebeb !default; 43 | 44 | // Orange 45 | $theme-warn-1: #c95a0a !default; 46 | $theme-warn-2: #ed6a0c !default; 47 | $theme-warn-3: #ef8030 !default; 48 | $theme-warn-4: #f1924e !default; 49 | $theme-warn-5: #fff5ed !default; 50 | 51 | // Shadow 52 | $shadow-spec-focus: 0 0 4px 0 rgba($theme-primary-1, 0.2) !default; 53 | $shadow-spec-layer: 0 2px 8px 0 rgba($theme-stroke-4, 0.5) !default; 54 | $shadow-spec-modal: 0 2px 24px 0 rgba($theme-stroke-4, 0.5) !default; 55 | 56 | $theme-colors: ( 57 | 'stroke': ( 58 | $theme-stroke-1, 59 | $theme-stroke-2, 60 | $theme-stroke-3, 61 | $theme-stroke-4, 62 | $theme-stroke-5, 63 | $theme-stroke-6, 64 | $theme-stroke-7, 65 | $theme-stroke-8, 66 | $theme-stroke-9, 67 | $theme-stroke-10, 68 | $theme-stroke-11, 69 | ), 70 | 'primary': ( 71 | $theme-primary-1, 72 | $theme-primary-2, 73 | $theme-primary-3, 74 | $theme-primary-4, 75 | $theme-primary-5, 76 | $theme-primary-6, 77 | $theme-primary-7, 78 | $theme-primary-8, 79 | ), 80 | 'success': ( 81 | $theme-success-1, 82 | $theme-success-2, 83 | $theme-success-3, 84 | $theme-success-4, 85 | $theme-success-5, 86 | ), 87 | 'error': ( 88 | $theme-error-1, 89 | $theme-error-2, 90 | $theme-error-3, 91 | $theme-error-4, 92 | $theme-error-5, 93 | ), 94 | 'warn': ( 95 | $theme-warn-1, 96 | $theme-warn-2, 97 | $theme-warn-3, 98 | $theme-warn-4, 99 | $theme-warn-5, 100 | ), 101 | ); 102 | 103 | $theme-shadow: ( 104 | 'focus': $shadow-spec-focus, 105 | 'layer': $shadow-spec-layer, 106 | 'modal': $shadow-spec-modal, 107 | ); 108 | -------------------------------------------------------------------------------- /example/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