615 lines
18 KiB
Markdown
Raw Normal View History

2017-10-10 11:15:24 +02:00
# UI components
Strapi provides built-in UI Components to make development faster.
## Button
2017-10-11 10:51:28 +02:00
Button library based on bootstrap classes.
2017-10-10 11:15:24 +02:00
2017-10-12 14:49:02 +02:00
{% center %} ![Buttons img](../assets/buttons.png) {% endcenter %}
2017-10-10 11:15:24 +02:00
### Usage
| Property | Type | Required | Description |
| -------- | ---- | -------- | ----------- |
| `children`| node | no | Ex: `<Button primary>Click me</Button>` |
| `className` | any | no | Sets a custom className. Ex: `<Button className={styles.myCustomClass} label="Click me" />` |
| `kind` | string | no | Sets the built-in className to the button. Ex: `<Button kind="primaryAddShape" label="Click me" />` |
| `label` | string | no | Sets the button label with i18n Ex: `<Button label="myPlugin.button.label" primary />` |
| `labelValue` | string | no | Sets the button label with i18n and a dynamic value Ex: {% raw %} ```<Button label="myPlugin.button.label" labelValue={{ foo: 'bar' }} primary />``` {% endraw %} |
| `loader` | bool | no | Displays a button loader. Ex: `<Button loader />` |
| `primary` | bool | no | [Bootstrap className](https://v4-alpha.getbootstrap.com/components/buttons/) |
| `primaryAddShape` | bool | no | Inserts fontAwesone plus icon inside the button. Ex: `<Button primaryAddShape>Click me</Button>` |
| `secondary`| bool | no | [Bootstrap className](https://v4-alpha.getbootstrap.com/components/buttons/) |
| `secondaryHotline` | bool | no | Sets className |
| `secondaryHotlineAdd` | bool | no | Inserts fontAwesone plus icon inside the button. Ex: `<Button secondaryHotlineAdd>Click me</Button>` |
| `type` | string | no | Sets the button type |
### Example
**Path —** `./plugins/my-plugin/admin/src/translations/en.json`.
```json
{
"myPlugin.button.label": "Add a new"
}
```
2017-10-10 11:15:24 +02:00
**Path —** `./plugins/my-plugin/admin/src/components/Foo/index.js`.
```js
// Make sure you don't have any other component called Button otherwise it will
// import the one from your ./components folder instead.
import React from 'react';
2017-10-10 11:15:24 +02:00
import Button from 'components/Button';
import styles from './styles.scss';
2017-10-10 11:15:24 +02:00
function Foo() {
2017-10-10 11:15:24 +02:00
// Define your buttons
const buttons = [
{
kind: 'primaryAddShape',
label: 'myPlugin.button.label',
labelValues: {
foo: 'Bar',
},
onClick: () => console.log('Click'),
},
];
return (
<div className={styles.foo}>
{buttons.map(buttonProps => <Button key={buttonProps.label} {...buttonProps} />)}
2017-10-10 11:15:24 +02:00
</div>
);
// Same as
// return (
// <div className={styles.foo}>
// <Button
// label="myPlugin.button.label"
// labelValues={{ foo: 'Bar' }}
// onClick={() => console.log('Click')}
// primaryAddShape
// />
// </div>
// );
2017-10-10 11:15:24 +02:00
}
// Will display a primaryAddShape button with label: 'Add a new Bar'
export default Foo;
2017-10-10 11:15:24 +02:00
```
***
## ExtendComponent
2018-01-10 11:45:01 +01:00
ExtendComponent allows a plugin to inject components into another one.
2018-01-10 11:45:01 +01:00
> Refer to the use cases [documentation](./frontend-use-cases.md#inject-design) to see how to use it.
***
## Ico
Ico components that works with fontAwesome.
### Usage
| Property | Type | Required | Description |
| -------- | ---- | -------- | ----------- |
| `icoType` | string | no (default: `trash`) | fontAwesome ico name. Ex: <Ico icoType="pencil" /> |
| `onClick` | func | no | Function executed onClick. |
### Example
```js
import React from 'react';
import Ico from 'components/Ico';
import PopUpWarning from 'components/PopUpWarning';
import styles from 'styles';
class FooPage extends React.Component {
constructor(props) {
super(props);
this.state = { showModal: false };
}
handleClick = () => this.setState({ showModal: true });
render () {
return (
<div className={styles.fooPage}>
<Ico icoType="trash" onClick={this.handleClick} />
<PopUpWarning
isOpen={this.state.showModal}
onConfirm={() => this.setState({ showModal: false })}
toggleModal={() => this.setState({ showModal: false })}
/>
</div>
);
}
}
export default FooPage;
```
***
## IcoContainer
Container containing icons, generally used for editing or deleting data.
### Usage
| Property | Type | Required | Description |
| -------- | ---- | -------- | ----------- |
| icons | array | no | Array containing icons' props. |
### Example
```js
import React from 'react';
import IcoContainer from 'components/IcoContainer';
import PopUpWarning from 'components/PopUpWarning';
import styles from 'styles';
class FooPage extends React.Component {
constructor(props) {
super(props);
this.state = { showModal: false };
}
handleClick = () => this.setState({ showModal: true });
render() {
const icons = [
{ icoType: 'pencil', onClick: () => console.log('click on pencil icon') },
{ icoType: 'trash', onClick: this.handleClick },
];
return (
<div className={styles.fooPage}>
<IcoContainer icons={icons} />
<PopUpWarning
isOpen={this.state.showModal}
onConfirm={() => this.setState({ showModal: false })}
toggleModal={() => this.setState({ showModal: false })}
/>
</div>
);
}
}
export default FooPage;
```
***
2017-10-10 11:15:24 +02:00
## Input
Strapi provides a built-in input library which includes :
- All kind of inputs
- Front-End validations
- Error highlight
- i18n
### Usage
| Property | Type | Required | Description |
| -------- | ---- | -------- | ----------- |
| `addon` | string | no | Allows to add a string addon in your input, based on [Bootstrap](https://v4-alpha.getbootstrap.com/components/input-group/#basic-example). Ex: `<Input {...this.props} addon="@" />` |
| `addRequiredInputDesign` | bool | no | Allows to add an asterix on the input. Ex: `<Input {...this.props} addRequiredInputDesign />` |
| `customBootstrapClass` | string | no | Allows to override the input bootstrap col system. Ex: `<Input {...this.props} customBootstrapClass="col-md-6 offset-md-6 pull-md-6" />` |
| `deactivateErrorHighlight` | bool | no | Prevents from displaying error highlight in the input: Ex: `<Input {...this.props} deactivateErrorHighlight />` |
| `didCheckErrors` | bool | no | Use this props to display errors after submitting a form. Ex: `<Input {...this.props} didCheckErrors={this.state.error} />` |
| `disabled` | bool | no | Disable the input. Ex: `<Input {...this.props} disabled />` |
| `errors` | array | no | Allows to display custom error messages. Ex: `<Input {...this.props} errors={[{ id: 'components.Input.error.custom-error', errorMessage: 'Something is wrong' }]} />` |
2017-10-10 11:15:24 +02:00
| `inputDescription` | string | no | Allows to add an input description that is displayed like [bootstrap](https://v4-alpha.getbootstrap.com/components/forms/#defining-states). |
| `label` | string | yes | Displays the input's label with i18n. |
| `linkContent` | object | no | Allows to display a link within the input's description. Ex: {% raw %} ``` <Input {...this.props} linkContent={{ description: 'check out our', link: 'tutorial video' }} />``` {% endraw %} |
| `name` | string | yes | The key to update your reducer. |
| `noErrorsDescription` | bool | no | Prevents from displaying built-in errors. |
| `onBlur` | func or bool | no | Overrides the default onBlur behavior. If bool passed to the component it will disabled the input validations checking. |
| `onChange` | func | yes | Sets your reducer state. |
| `onFocus` | func | no | Adds an onFocus event to the input. |
2017-10-10 11:15:24 +02:00
| `placeholder` | string | no | Allows to set a placeholder. |
| `selectOptions` | array | no | Options for the select. |
| `tabIndex` | string | no | Sets the order in which the inputs are focused on tab key press. |
| `title` | string | no | This props can only be used for checkboxes, it allows to add a title on top of the input, the label will be on the right side of the checkbox. |
| `validations` | object | yes | Allows to have the built-in input's validations. If set to {} the validations will be ignored. Ex: {% raw %} ``` <Input {...this.props} validations={{ required: true }} />``` {% endraw %} |
| `value` | string or bool or number | yes | The input's value. |
### Example
**Path —** `./plugins/my-plugin/admin/src/containers/FooPage/index.js`.
2017-10-10 11:15:24 +02:00
```js
import React from 'react';
// Make sure you don't have a component called Input inside your ./components folder
// It will import the one in your components folder instead.
import Input from 'components/Input';
class FooPage extends React.Component {
constructor(props) {
super(props);
this.state {
data: {
foo: 'bar',
},
error: false,
errors: [],
};
}
handleChange = ({ target }) => {
const value = target.type === 'number' ? Number(target.value) : target.value;
const error = target.value.length === 0;
const data = {
[target.name]: value,
}
2017-10-10 11:15:24 +02:00
if (error) {
this.setState({ error: true, errors: [{ id: 'This input is required ' }] });
} else {
this.setState({ data, error: false, errors: [] });
2017-10-10 11:15:24 +02:00
}
}
render() {
return (
<div className={styles.fooPage}>
2017-10-10 11:15:24 +02:00
<Input
type="string"
value={this.state.data.foo}
label="This is a string input"
name="foo"
onChange={this.handleChange}
2017-10-10 11:15:24 +02:00
validations={{ required: true }}
didCheckErrors={this.state.error}
/>
</div>
);
}
}
// ...
export default FooPage;
```
#### Example with property linkContent and i18n
**Path —** `./plugins/my-plugin/admin/src/translations/en.json`.
```json
{
"form.input.inputDescription": "Content type name should be singular",
"form.input.label": "Name",
"form.input.linkContent.description": "check out our documentation"
}
```
**Path —** `./plugins/my-plugin/admin/src/translations/fr.json`.
```json
{
"form.input.inputDescription": "Le nom des modèles doit être au singulier",
"form.input.label": "Nom",
"form.input.linkContent.description": "regardez la documentation."
}
```
**Path —** `./plugins/my-plugin/admin/src/containers/FooPage/index.js`.
```js
import React from 'react';
// ...
class FooPage extends React.Component {
// ...
render () {
return (
<div className={styles.fooPage}>
<Input
didCheckErrors={this.state.error}
inputDescription="my-plugin.form.input.inputDescription"
label="my-plugin.form.input.label"
linkContent={{ link: 'https://strapi.io/documentation/', description: 'my-plugin.form.input.linkContent.description' }}
onChange={this.handleChange}
type="string"
validations={{ required: true }}
value={this.state.data.foo}
/>
</div>
);
}
}
// ...
2017-10-10 11:15:24 +02:00
export default FooPage;
```
***
2017-12-13 11:49:26 +01:00
## OverlayBlocker
2018-01-17 14:36:26 +01:00
The OverlayBlocker is a React component that is very useful to block user interactions when the strapi server is restarting in order to avoid front-end errors. This component is automatically displayed when the server needs to restart. You need to disable in order to override the current design.
2017-12-13 11:49:26 +01:00
### Usage
| Property | Type | Required | Description |
| -------- | ---- | -------- | ----------- |
| children | node | no | Anything that is wrapped inside the OverlayBlocker |
| isOpen | bool | no | If set to `true` it will display the component |
### Example
In this example we'll have a button that when clicked it will display the OverlayBlocker for 5 seconds thus 'freezes' the admin so the user can't navigate (it simulates a very long server restart).
**Path -** `./plugins/my-plugin/admin/src/containers/FooPage/constants.js`.
```js
export const ON_BUTTON_CLICK = 'MyPlugin/FooPage/ON_BUTTON_CLICK';
export const RESET_SHOW_OVERLAY_PROP = 'MyPlugin/FooPage/RESET_SHOW_OVERLAY_PROP';
```
**Path -** `./plugins/my-plugin/admin/src/containers/FooPage/actions.js`.
```js
import { ON_BUTTON_CLICK, RESET_SHOW_OVERLAY_PROP } from './constants';
export function onButtonClick() {
return {
type: ON_BUTTON_CLICK,
};
}
export function resetShowOverlayProp() {
return {
type: RESET_SHOW_OVERLAY_PROP,
};
}
```
**Path -** `./plugins/my-plugin/admin/src/containers/FooPage/index.js`.
```js
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux';
2018-01-17 14:36:26 +01:00
// Actions required for disabling and enabling the OverlayBlocker
import {
disableGlobalOverlayBlocker,
enableGlobalOverlayBlocker,
} from 'actions/overlayBlocker';
2017-12-13 11:49:26 +01:00
// Design
import Button from 'components/Button';
import OverlayBlocker from 'components/OverlayBlocker';
// Utils
import injectSaga from 'utils/injectSaga';
import injectReducer from 'utils/injectReducer';
// Actions
import { onButtonClick } from './actions';
// Reducer
import reducer from './reducer';
// Saga
import saga from './saga';
// Selectors (see the documentation to see how to use selectors)
import makeSelectFooPage from './selectors';
export class FooPage extends React.Component {
2018-01-17 14:36:26 +01:00
componentDidMount() {
// Disable the AdminPage OverlayBlocker in order to give it a custom design (children)
this.props.disableGlobalOverlayBlocker();
}
componentWillUnmount() {
// Enable the AdminPage OverlayBlocker so it is displayed when the server is restarting in the other plugins
this.props.enableGlobalOverlayBlocker();
}
2017-12-13 11:49:26 +01:00
render() {
return (
<div>
<OverlayBlocker isOpen={this.props.showOverlayBlocker}>
<div style={{ width: '100px', height: '100px', backgroundColor: '#fff' }}>
<h4>The app is now blocked for 5 seconds</h4>
</div>
</Overlay>
<Button onClick={this.props.onButtonClick} primary>Click me</Button>
</div>
);
}
}
FooPage.propTypes = {
2018-01-17 14:36:26 +01:00
disableGlobalOverlayBlocker: PropTypes.func.isRequired,
enableGlobalOverlayBlocker: PropTypes.func.isRequired,
2017-12-13 11:49:26 +01:00
onButtonClick: PropTypes.func.isRequired,
showOverlayBlocker: PropTypes.bool.isRequired,
};
const mapStateToProps = makeSelectFooPage();
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
2018-01-17 14:36:26 +01:00
disableGlobalOverlayBlocker,
enableGlobalOverlayBlocker,
2017-12-13 11:49:26 +01:00
onButtonClick,
},
dispatch,
);
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);
const withReducer = injectReducer({ key: 'fooPage', reducer });
const withSaga = injectSaga({ key: 'fooPage', saga });
export default compose(
withReducer,
withSaga,
withConnect,
)(FooPage);
```
**Path -** `./plugins/my-plugin/admin/src/containers/FooPage/reducer.js`.
```js
import { fromJS } from 'immutable';
import { ON_BUTTON_CLICK, RESET_SHOW_OVERLAY_PROP } from './constants';
const initialState = fromJS({
showOverlayBlocker: false,
});
function fooPageReducer(state = initialState, action) {
switch (action.type) {
case ON_BUTTON_CLICK:
return state.set('showOverlayBlocker', true);
case RESET_SHOW_OVERLAY_PROP:
return state.set('showOverlayBlocker', false);
default:
return state;
}
}
export default fooPageReducer;
```
**Path -** `./plugins/my-plugin/admin/src/containers/FooPage/saga.js`.
```js
import {
fork,
put,
takeLatest,
} from 'redux-saga/effects';
import { resetShowOverlayProp } from './actions';
import { ON_BUTTON_CLICK } from './constants';
export function* buttonClicked() {
try {
// Start the timer
yield new Promise(resolve => {
setTimeout(() => {
resolve();
}, 5000);
});
yield put(resetShowOverlayProp());
} catch(err) {
yield put(resetShowOverlayProp());
}
}
export default function* defaultSaga() {
yield fork(takeLatest, ON_BUTTON_CLICK, buttonClicked);
}
```
***
2017-10-10 11:15:24 +02:00
## PopUp Warning
PopUp warning library based on [reactstrap](https://reactstrap.github.io/components/modals/).
2017-10-12 14:49:02 +02:00
{% center %} ![PopUp warning img](../assets/popup-warning.png) {% endcenter %}
2017-10-10 11:15:24 +02:00
### Usage
| Property | Type | Required | Description |
| -------- | ---- | -------- | ----------- |
| content | object | no | Used to set the confirm button, cancel button, title, body messages. |
| onConfirm | func | yes | Function executed when the user clicks on the `Confirm button`. |
2017-10-10 11:15:24 +02:00
| isOpen | bool | yes | Show or hide the popup. |
| onlyConfirmButton | bool | yes | Display only the confirm button (`primary`) with `width: 100%`. |
2017-10-10 11:15:24 +02:00
| popUpWarningType | string | yes | Sets the popup body icon. Available types: `danger`, `info`, `notFound`, `success`, `warning` |
| toggleModal | func | yes | Function to toggle the modal. |
### Example
**Path —** `./plugins/my-plugin/admin/src/translations/en.json`.
```json
{
"popup.danger.button.cancel": "Cancel...",
"popup.danger.button.confirm": "Confirm../",
"popup.danger.message": "Are you sure you want to delete this item?!",
"popup.danger.title": "Please confirm...."
2017-10-10 11:15:24 +02:00
}
```
**Path —** `./plugins/my-plugin/admin/src/translations/fr.json`.
```json
{
"popup.danger.button.cancel": "Annuler...",
"popup.danger.button.label": "Je confirme...",
"popup.danger.message": "Êtes-vous certain de vouloir supprimer ce message?!",
"popup.danger.title": "Merci de confirmer..."
2017-10-10 11:15:24 +02:00
}
```
**Path —** `./plugins/my-plugin/admin/src/containers/FooPage/index.js`.
```js
// ...
import Button from 'components/Button';
import PopUpWarning from 'components/PopUpWarning';
// ...
class FooPage extends React.Component {
constructor(props) {
super(props);
this.state = {
isOpen: false,
};
}
handlePopUpConfirm = () => {
// Some logic Here
this.setState({ isOpen: false });
}
render() {
const popupContent = {
cancel: 'my-plugin.popup.danger.button.cancel',
confirm: 'my-plugin.popup.danger.button.confirm',
message: 'my-plugin.popup.danger.message',
title: 'my-plugin.popup.danger.title',
};
return (
2017-10-10 11:15:24 +02:00
<div>
<Button primary onClick={() => this.setState({ isOpen: !this.state.isOpen })} label="my-plugin.button.label" />
2017-10-10 11:15:24 +02:00
<PopUpWarning
content={popupContent}
onConfirm={this.handlePopUpConfirm}
2017-10-10 11:15:24 +02:00
toggleModal={() => this.setState({ isOpen: !this.state.isOpen })}
popUpWarningType="danger"
/>
</div>
);
// Equivalent without custom messages
// return (
// <div>
// <Button primary onClick={() => this.setState({ isOpen: !this.state.isOpen })} label="my-plugin.button.label" />
// <PopUpWarning
// onConfirm={this.handlePopUpConfirm}
// toggleModal={() => this.setState({ isOpen: !this.state.isOpen })}
// popUpWarningType="danger"
// />
// </div>
// );
2017-10-10 11:15:24 +02:00
}
}
export default FooPage;
```