---
title: Design
path: component/design
group: Data Entry
---

## Design

H5 page editor, build your H5 pages in a WYSIWYG way.

⚠️ Warning：The `Design` component exported by Zent uses `react-dnd-html5-backend`'s `HTML5Backend`. Each React component tree can have only one instance of `HTML5Backend`. Please use `zent/lib/design/Design` to replace the default export from Zent if you are using `HTML5Backend` somewhere else. There two components are almost the same, except the one in `zent/lib/design/Design` does not depend on `HTML5Backend`.

### API

| Property | Description | Type | Default | Required |
|------|------|------|--------|--------|
| components | All available components in Design | array | [] | Yes |
| value | Current value | array | [] | Yes |
| onChange | Callback when value changes | func(value: array): void | Yes |
| defaultSelectedIndex| Default selected index in value array | number | -1 | No |
| preview | Custom Preview component | Component | DesingPreview | No |
| confirmUnsavedLeave| Show a confirm dialog if there're unsaved changes | boolean | true | No |
| cache | Cache unsaved changes to `localStorage` | boolean | false | No |
| cacheId | Cache id, must be used with `cache` | string | | Yes if `cache` is `true`, No otherwise |
| cacheRestoreMessage | Message to restore cache from `localStorage` | node | 提示：在浏览器中发现未提交的内容，是否使用该内容替换当前内容？ | No |
| disabled | Is Design disabled | boolean | false | No |
| globalConfig | Global config across Design | object | | No |
| children | Additional children inside Design | node | | No |
| scrollTopOffset | Top scroll offset | number \| func | | No |
| scrollLeftOffset | Left scroll offset | number \| func | | No |
| className | Custom class name | string | | No |
| prefix | Custom prefix | string | | No |

`components` is an array, all available componets should be included in this array. Each item in this array is a component description, here are the possible options.

```js
type Component = {
  // Component type, must be unique
  type: string | string[],
  
  // Default component type
  // If `type` is an array, `defaultType` can be a number
  // If `defaultType` is a function, it will be called with `type` as the sole argument
  defaultType?: number | (string[] | string) => string

  // Component to render preview
  preview: ReactComponent,

  // Component responsible for editing
  editor: ReactComponent,

  // Preview component container 
  previewItem?: ReactComponent,

  // Preview controller, responsible for dnd, select and so on
  previewController?: ReactComponent,

  // Editor component container
  editorItem?: ReactComponent,

  // Is this component dragable?
  dragable?: boolean,

  // Should this component appear in the component list?
  appendable?: boolean,

  // Is this component configurable(edit/add/delete on the bottom right corner)?
  configurable?: boolean,

  // Is this component editable? Only editable components are selectable
  editable?: boolean,

  // Highlight preview when selected
  highlightWhenSelect?: boolean,

  // Maximum number of instances this component can have
  // Zero is no limit
  // If passing a function, return false to stop adding more
  limit?: number | (count: number) => boolean,
  
  // Callback when adding a new instance for component
  // Add only if Promise resolves.
  shouldCreate?: (comp: Component) => Promise,

  // Additional props passed to editor
  editorProps: (value: object) => object | object,

  // Addtional props passed to preview
  previewProps: (value: object) => object | object
}
```

Each item in `value` array must have a `type` property, `Design` uses this property to determine why component in `component` array should be used to render this value.

### Design.group

Declaration：`group(name: string): object`

`Design` supports component grouping in add component area, all you have to do is insert `Desgin.group(groupName)` to the right place in your `components` array.

```js
[
  config,

  Design.group('分组1'),
  componentA,
  componentB,

  Design.group('分组2'),
  componentC,
  componentD
]
```

### Design Instance Methods

* `design.validate(): Promise`, trigger a validation, resolves only if there's no erro.
* `design.markAsSaved()`, tell `Desgin` data has been saved.

### stripUUID

There's a `stripUUID` method on `Design` instance, you can use this method to strip all internal ids used by `Design` before sending data to server. This may help reduce data size.

Note: calling `stripUUID` before sending data to server is optional.

### How to Implement new Design Component?

Each Desgin component are divided in two parts: Preview and Editor.

Preview is just a component which accepts `{ value: any, globalConfig: any, design: object }` as props and renders a UI with these props.

It is a little bit complex about Editor component. You are recommended to extend the `@youzan/design/lib/base/editor/DesignEditor` base class, this class has some useful methods you can use(e.g. `onChange` event handlers).

Editor has these props:

`{ value: any, onChange: func, showError: boolean, validation: object, design object }`

- `validate(value): Promise` You should resolve an error object if there're errors
- `props.design` There're some useful methods on this prop

A editor component must have these static properties: 

`designType, designDescription, getInitialValue, validate`

#### Example

```jsx
// Preview
import React, { PureComponent, Component } from 'react';

export default class NoticePreview extends (PureComponent || Component) {
  render() {
    const { value } = this.props;

    return (
      <div className="rc-design-component-notice-preview">{value}</div>
    );
  }
}

// Editor
import React from 'react';
import { Input } from 'zent';

import { DesignEditor, ControlGroup } from '@youzan/design/base/editor/DesignEditor';

export const PLACEHOLDER = '请填写内容，如果过长，将会在手机上滚动显示';

export default class NoticeEditor extends DesignEditor {
  render() {
    const { value, showError, validation } = this.props;

    return (
      <div className="rc-design-component-notice-editor">
        <ControlGroup
          label="公告:"
          required
          showError={showError || this.getMetaProperty('content', 'touched')}
          error={validation.content}
        >
          <Input
            name="content"
            placeholder={PLACEHOLDER}
            value={value.content}
            onChange={this.onInputChange}
            onBlur={this.onInputBlur}
          />
        </ControlGroup>
      </div>
    );
  }

  static designType = 'notice';
  static designDescription = '公告';
  static getInitialValue() {
    return {
      content: '',
      scrollable: false
    };
  }

  static validate(value) {
    return new Promise(resolve => {
      const errors = {};
      const { content } = value;
      if (!content || !content.trim()) {
        errors.content = '请填写公告内容';
      }

      resolve(errors);
    });
  }
}
```
