Permalink
Browse files

feat(Toolbar): Added Toolbar component

  • Loading branch information...
HHogg committed Sep 24, 2018
1 parent 21d4960 commit be2add5cb2d7902bad46c7ebfa15cf52bcfe1cff
@@ -8,8 +8,7 @@ export default class ComponentExample extends Component {
backgroundColor="shade-2"
container
margin="x6"
padding="x6"
scrollable />
padding="x6" />
);
}
}
@@ -0,0 +1,64 @@
import React, { Component } from 'react';
import { Icon, Flex, Toolbar, ToolbarAction, ToolbarActionGroup } from 'preshape';
import onEdit from '../Component/onEdit';
import DocumentationPage from '../Documentation/DocumentationPage';
import ComponentExample from '../Component/ComponentExample';
import ComponentPropsTable from '../Component/ComponentPropsTable';

export default class ComponentToggle extends Component {
constructor(props) {
super(props);
this.state = {
Toolbar: {
visible: false,
},
};
}

render() {
return (
<DocumentationPage { ...this.props }>
<ComponentExample>
<Flex
alignChildren="middle"
direction="horizontal">
<Flex>
<Toolbar { ...this.state.Toolbar } target={ (ref) => (
<Icon name="Star" ref={ ref } size="2rem" />
) }>
<ToolbarActionGroup>
<ToolbarAction>
<Icon name="Pencil" size="1rem" />
</ToolbarAction>

<ToolbarAction>
<Icon name="Copy" size="1rem" />
</ToolbarAction>

<ToolbarAction>
<Icon name="Water" size="1rem" />
</ToolbarAction>
</ToolbarActionGroup>

<ToolbarActionGroup>
<ToolbarAction>
<Icon name="Delete" size="1rem" />
</ToolbarAction>

<ToolbarAction>
<Icon name="Eye" size="1rem" />
</ToolbarAction>
</ToolbarActionGroup>
</Toolbar>
</Flex>
</Flex>
</ComponentExample>

<ComponentPropsTable
components={ [ require('!!@brandwatch/axiom-documentation-loader!../../../src/Toolbar/Toolbar') ] }
onEdit={ (...args) => this.setState(onEdit(this.state, ...args)) }
values={ this.state } />
</DocumentationPage>
);
}
}
@@ -22,6 +22,7 @@ import Tab from './Tab';
import Table from './Table';
import Text from './Text';
import TextArea from './TextArea';
import Toolbar from './Toolbar';

export default [{
Component: Alert,
@@ -143,4 +144,9 @@ export default [{
name: 'TextArea',
to: '/components/text-area',
description: 'Standard multiple line text area input for collection user feedback data.',
}, {
Component: Toolbar,
name: 'Toolbar',
to: '/components/toolbar',
description: 'A contextual multi-purpose toolbar. Useful for editors that require inline actions.',
}];
@@ -0,0 +1,26 @@
:root {
--Toolbar-tip-size: var(--size--x4);
}

.Toolbar__popper {
position: absolute;
}

.Toolbar {
padding: var(--Toolbar-tip-size);
}

.Toolbar__content {
position: relative;
border-radius: var(--border-size--x2);
overflow: hidden;
}

.Toolbar__tip {
position: absolute;
bottom: 0;
width: var(--Toolbar-tip-size);
height: var(--Toolbar-tip-size);
transform: translateY(-50%) rotate(45deg);
pointer-events: none;
}
@@ -0,0 +1,168 @@
import PropTypes from 'prop-types';
import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';
import popperJS from 'popper.js';
import omit from 'lodash.omit';
import Appear from '../Appear/Appear';
import Base from '../Base/Base';
import Flex from '../Flex/Flex';
import ToolbarTargetReference from './ToolbarTargetReference';
import './Toolbar.css';

export default class Toolbar extends Component {
static propTypes = {
/** Toolbar action components. */
children: PropTypes.node.isRequired,
/**
* Target is a function that is provided with a ref. Used for
* situations where the Toolbar is to be positioned around a
* DOM node.
*/
target: PropTypes.func,
/**
* An object that specifies `width`, `height`, `x` and `y` values.
* Used for situations where the Toolbar is to be pragmatically
* positioned, for example on an HTML canvas.
*/
targetBox: PropTypes.shape({
height: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
}),
/**
* Visibility toggle for the Toolbar.
*/
visible: PropTypes.bool.isRequired,
};

constructor(props) {
super(props);
this.state = {
render: props.visible,
visible: false,
};
}

componentDidMount() {
if (this.state.render) {
this.createPopper();
}
}

componentDidUpdate(prevProps) {
const {
visible,
targetBox,
} = this.props;

if (visible !== prevProps.visible) {
if (visible) {
this.setState({ render: true }, () => {
this.createPopper();
});
} else {
this.setState({ visible: false });
}
} else if (this.popper && visible) {
if (this.targetReference) {
this.targetReference.update(targetBox);
}

this.popper.update();
}
}

componentWillUnmount() {
this.destroyPopper();
}

createPopper() {
this.popper = new popperJS(this.target(), this.elContent, {
onCreate: () => this.setState({ visible: true }),
placement: 'top',
modifiers: {
arrow: {
element: this.elArrow,
},
flip: {
behavior: ['top'],
},
inner: {
enabled: false,
},
offset: {
enabled: false,
},
},
});
}

destroyPopper() {
if (this.propper) {
this.popper.destroy();
}
}

target() {
const {
target,
targetBox,
} = this.props;

if (target) {
return this.elTarget;
}

this.targetReference = new ToolbarTargetReference();
this.targetReference.update(targetBox);
return this.targetReference;
}

handleExited() {
this.destroyPopper();
this.setState({ render: false });
}

render() {
const { children, target, ...rest } = this.props;
const { render, visible } = this.state;
const props = omit(rest, [
'targetBox',
'visible',
]);

return (
<Fragment>
{ target && target((el) => this.elTarget = ReactDOM.findDOMNode(el)) }

{ render && ReactDOM.createPortal(
<div
className="Toolbar__popper"
ref={ (el) => this.elContent = ReactDOM.findDOMNode(el) }>
<Appear { ...props }
animation="Pop"
className="Toolbar"
onExited={ () => this.handleExited() }
visible={ visible }>
<Base
backgroundColor="text-shade-2"
className="Toolbar__tip"
ref={ (el) => this.elArrow = ReactDOM.findDOMNode(el) } />
<Base
Component={ Flex }
backgroundColor="text-shade-2"
className="Toolbar__content"
direction="horizontal"
gutter="x1"
padding="x1">
{ children }
</Base>
</Appear>
</div>,
document.body
) }
</Fragment>
);
}
}
@@ -0,0 +1,19 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Button from '../Button/Button';

export default class ToolbarAction extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
};

render() {
const { children, ...rest } = this.props;

return (
<Button { ...rest } style="fill">
{ children }
</Button>
);
}
}
@@ -0,0 +1,19 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Buttons from '../Button/Buttons';

export default class ToolbarActionGroup extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
};

render() {
const { children, ...rest } = this.props;

return (
<Buttons { ...rest }>
{ children }
</Buttons>
);
}
}
@@ -0,0 +1,24 @@
export default class ToolbarTargetReference {
update({ height, width, x, y }) {
this.rect = {
top: y,
left: x,
right: x + width,
bottom: y + height,
width: 0,
height: 0,
};
}

get clientWidth() {
return this.rect.width;
}

get clientHeight() {
return this.rect.height;
}

getBoundingClientRect() {
return this.rect;
}
}
@@ -50,4 +50,6 @@ export { default as TabContent } from './Tab/TabContent';
export { default as Text } from './Text/Text';
export { default as TextArea } from './TextArea/TextArea';
export { default as ThemeContext } from './Theme/ThemeContext';

export { default as Toolbar } from './Toolbar/Toolbar';
export { default as ToolbarAction } from './Toolbar/ToolbarAction';
export { default as ToolbarActionGroup } from './Toolbar/ToolbarActionGroup';

0 comments on commit be2add5

Please sign in to comment.