Skip to content

Commit

Permalink
0.2.4, add in Repeat component with example
Browse files Browse the repository at this point in the history
  • Loading branch information
zackify committed Apr 17, 2018
1 parent 611c17b commit a8fe214
Show file tree
Hide file tree
Showing 15 changed files with 240 additions and 16 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ module.exports = {

The configuration is the exact same as webpack with one extra piece: pass `babelOptions` with plugins and presets like a babelrc has to customize the babel loader.

# Coming Soon
# Future plans

* Inspector tools for repeat actions (adding multiple items recursively, ex: tabs with products in each)
* Automatic i18n
* Complicated examples (tabs component, loading in data from wordpress)

* Handling the `save` method
* Test coverage
* Batch updates when updating nested tabs that cause lots of rerenders in Gutenberg

Currently, we don't use wordpress to render blocks, so we don't use this. But I will be working to make this part better if needed.

Expand Down
2 changes: 1 addition & 1 deletion controls/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gutenblock-controls",
"version": "0.1.0",
"version": "0.1.3",
"description": "Useful inspector controls for gutenberg",
"main": "dist/index.js",
"scripts": {
Expand Down
13 changes: 11 additions & 2 deletions controls/src/base/inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@ import { Consumer } from './context';

// Todo, use react context vs map when we add other inspector controls :)

export default ({ children, isSelected }) => (
export default ({ children }) => (
<Consumer>
{data =>
data.isSelected ? <InspectorControls>{children}</InspectorControls> : null
data.isSelected ? (
<InspectorControls>
{React.Children.map(children, child =>
React.cloneElement(child, {
setAttributes: data.setAttributes,
attributes: data.attributes,
})
)}
</InspectorControls>
) : null
}
</Consumer>
);
4 changes: 4 additions & 0 deletions controls/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ export { default as Attributes } from './base/attributes';
//Form stuff
export { default as Input } from './form/input';
export { default as RichText } from './form/rich-text';

//Repeat
export { default as Repeat } from './repeat';
export { default as RepeatInput } from './repeat/input';
141 changes: 141 additions & 0 deletions controls/src/repeat/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React from 'react';

const { Button } = wp.components;

export default class Repeat extends React.Component {
constructor({ attribute, attributes }) {
super();
this.update = this.update.bind(this);
this.delete = this.delete.bind(this);

let items =
attributes && attributes[attribute] ? attributes[attribute] : [];
this.state = { items: items.length };
}

static getDerivedStateFromProps({ attributes, attribute }, state) {
let items =
attributes && attributes[attribute] ? attributes[attribute] : [];
if (items.length !== state.items) return { items: items.length };

return null;
}

update(name, value, tabId, parentChange, customUpdate) {
let { attribute, attributes, setAttributes } = this.props;

//if nested repeats, pass state up to top most one
if (parentChange) {
let currentAttributes = attributes[attribute] || [];

let foundAttribute = currentAttributes.find(
(attr, index) => index === tabId
);
let newAttributes;
if (!foundAttribute)
newAttributes = [...currentAttributes, { [name]: value }];
else
newAttributes = currentAttributes.map(
(attr, index) => (index === tabId ? { ...attr, [name]: value } : attr)
);

return parentChange(attribute, newAttributes);
}

let topLevel = [...attributes[attribute]];

topLevel[tabId] = { ...topLevel[tabId], [name]: value };

setAttributes({ [attribute]: topLevel });
}

delete(childAttributes, childAttribute, tabId, onDelete, customDelete) {
let { attribute, attributes, setAttributes } = this.props;

let newAttributes;
if (!childAttributes)
newAttributes = attributes[attribute].filter(
(attr, index) => index !== tabId
);

if (childAttributes) {
let newItem = {
...attributes[attribute][tabId],
[childAttribute]: childAttributes,
};
newAttributes = attributes[attribute].map(
(item, index) => (index === tabId ? newItem : item)
);
}

if (onDelete) return onDelete(newAttributes, attribute);

setAttributes({ [attribute]: newAttributes });
}

renderChildren(index) {
let {
attribute,
children,
setAttributes,
attributes,
onChange,
onDelete,
} = this.props;

return React.Children.map(children, child =>
React.cloneElement(child, {
key: index,
index,
setAttributes,
attributes:
attributes && attributes[attribute] && attributes[attribute][index]
? attributes[attribute][index]
: undefined,
onChange: (name, value) =>
this.update(name, value, index, onChange, child.onUpdate),
onDelete: (childAttributes, childAttribute) =>
this.delete(
childAttributes,
childAttribute,
index,
onDelete,
child.onDelete
),
style: {
marginLeft: '10px',
marginTop: '15px',
},
})
);
}

render() {
let { items } = this.state;
let { style, title, indent, addNew, max } = this.props;

let repeats = [];
for (let item = 0; item < items; item++) {
repeats.push(this.renderChildren(item));
}

return (
<div style={style}>
<h1>{title}</h1>
<div>
{repeats}
<div style={{ marginTop: '10px' }}>
{max > items || !max ? (
<Button
isPrimary
onClick={() => this.setState({ items: items + 1 })}
>
{addNew}
</Button>
) : null}
</div>
</div>
</div>
);
}
}
32 changes: 32 additions & 0 deletions controls/src/repeat/input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';

const RepeatInput = ({
name,
label,
onChange,
onDelete,
type,
attributes,
setAttributes,
}) => (
<div className="components-base-control__field">
<label htmlFor={name} className="components-base-control__label">
{label}
</label>
<div style={{ display: 'flex' }}>
<input
id={name}
name={name}
value={attributes ? attributes[name] : ''}
type={type || 'string'}
className="components-text-control__input"
onChange={e => onChange(name, e.target.value)}
/>
<div style={{ alignSelf: 'center' }} onClick={() => onDelete()}>
X
</div>
</div>
</div>
);

export default RepeatInput;
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gutenblock",
"version": "0.2.3",
"version": "0.2.4",
"description": "",
"main": "index.js",
"scripts": {
Expand Down
6 changes: 3 additions & 3 deletions plugin/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"author": "Crossfield",
"license": "ISC",
"dependencies": {
"gutenblock-controls": "^0.1.0",
"gutenblock-controls": "^0.1.3",
"react": "^16.3.1",
"react-hot-loader": "^4.0.1"
}
Expand Down
5 changes: 3 additions & 2 deletions plugin/src/example/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ const Block = {
title: {
type: 'string',
},
description: {
type: 'string',
tabs: {
type: 'array',
default: [],
},
},
};
5 changes: 5 additions & 0 deletions plugin/src/example/components/note.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

export default ({ note }) => (
<li style={{ listStyle: 'square !important' }}>{note.heading}</li>
);
9 changes: 9 additions & 0 deletions plugin/src/example/components/tabs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Note from './note';

export default ({ tabs }) =>
tabs.map(tab => (
<div>
<h2>Tab: {tab.title}</h2>
{tab.notes ? tab.notes.map(note => <Note note={note} />) : null}
</div>
));
14 changes: 12 additions & 2 deletions plugin/src/example/edit.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import Inspector from './inspector';
import Tabs from './components/tabs';
import { RichText } from 'gutenblock-controls';

const Edit = () => (
const Edit = ({ attributes }) => (
<div>
<RichText name="title" />
<div>
<RichText
tagName="h1"
name="title"
placeholder="Section Title: Add tabs to the right ->"
/>
</div>
<Tabs tabs={attributes.tabs} />
<Inspector />
</div>
);
13 changes: 13 additions & 0 deletions plugin/src/example/inspector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Inspector, Repeat, RepeatInput } from 'gutenblock-controls';

export default () => (
<Inspector>
<Repeat title="Tabs" addNew="Add Tab" attribute="tabs">
<RepeatInput name="title" />

<Repeat title="Notes" addNew="Add Note" attribute="notes" max={3}>
<RepeatInput name="heading" />
</Repeat>
</Repeat>
</Inspector>
);

0 comments on commit a8fe214

Please sign in to comment.