Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions src/components/action-menu/action-menu.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
@import "../../css/colors.css";

$main-button-size: 2.75rem;
$more-button-size: 2.25rem;

.menu-container {
display: flex;
flex-direction: column-reverse;
transition: 0.2s;
position: relative;
}

.button {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
background: $motion-primary;
outline: none;
border: none;
transition: background-color 0.2s;
}

.button:hover {
background: $pen-primary;
}

.button.coming-soon:hover {
background: $data-primary;
}

.main-button {
border-radius: 100%;
width: $main-button-size;
height: $main-button-size;
z-index: 20; /* TODO reorder layout to prevent z-index need */
}

.main-icon {
width: calc($main-button-size - 1rem);
height: calc($main-button-size - 1rem);
}

.more-buttons-outer {
/*
Need to use two divs to set different overflow x/y
which is needed to get animation to look right while
allowing the tooltips to be visible.
*/
overflow-y: hidden;

background: $motion-tertiary;
border-top-left-radius: $more-button-size;
border-top-right-radius: $more-button-size;
width: $more-button-size;
margin-left: calc(($main-button-size - $more-button-size) / 2);

position: absolute;
bottom: calc($main-button-size);

margin-bottom: calc($main-button-size / -2);
padding-bottom: calc($main-button-size / 2);
}

.more-buttons {
max-height: 0;
transition: max-height 1s;
overflow-x: visible;
display: flex;
flex-direction: column;
z-index: 10; /* @todo justify */
}

.expanded .more-buttons {
max-height: 1000px; /* Arbitrary, needs to be a value in order for animation to run */
}

.force-hidden .more-buttons {
display: none; /* This property does not animate */
}

.more-buttons:first-child { /* Round off top button */
border-top-right-radius: $more-button-size;
border-top-left-radius: $more-button-size;
}

.more-button {
width: $more-button-size;
height: $more-button-size;
background: $motion-tertiary;
}

.more-icon {
width: calc($more-button-size - 1rem);
height: calc($more-button-size - 1rem);
}

.coming-soon .more-icon {
opacity: 0.5;
}

/*
@todo needs to be refactored with coming soon tooltip overrides.
The "!important"s are for the same reason as with coming soon, the library
is not very easy to style.
*/
.tooltip {
background-color: $pen-primary !important;
opacity: 1 !important;
border: 1px solid hsla(0, 0%, 0%, .1) !important;
box-shadow: 0 0 .5rem hsla(0, 0%, 0%, .25) !important;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important;
}

.tooltip:after {
background-color: $pen-primary;
}

.coming-soon-tooltip {
background-color: $data-primary !important;
}

.coming-soon-tooltip:after {
background-color: $data-primary !important;
}

.tooltip {
border: 1px solid hsla(0, 0%, 0%, .1) !important;
border-radius: .25rem !important;
box-shadow: 0 0 .5rem hsla(0, 0%, 0%, .25) !important;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important;
z-index: 100 !important;
}

$arrow-size: 0.5rem;
$arrow-inset: -0.25rem;
$arrow-rounding: 0.125rem;

.tooltip:after {
content: "";
border-top: 1px solid hsla(0, 0%, 0%, .1) !important;
border-left: 0 !important;
border-bottom: 0 !important;
border-right: 1px solid hsla(0, 0%, 0%, .1) !important;
border-radius: $arrow-rounding;
height: $arrow-size !important;
width: $arrow-size !important;
}

.tooltip:global(.place-left):after {
margin-top: $arrow-inset !important;
right: $arrow-inset !important;
transform: rotate(45deg) !important;
}

.tooltip:global(.place-right):after {
margin-top: $arrow-inset !important;
left: $arrow-inset !important;
transform: rotate(-135deg) !important;
}

.tooltip:global(.place-top):after {
margin-right: $arrow-inset !important;
bottom: $arrow-inset !important;
transform: rotate(135deg) !important;
}

.tooltip:global(.place-bottom):after {
margin-left: $arrow-inset !important;
top: $arrow-inset !important;
transform: rotate(-45deg) !important;
}
146 changes: 146 additions & 0 deletions src/components/action-menu/action-menu.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
import bindAll from 'lodash.bindall';
import ReactTooltip from 'react-tooltip';

import styles from './action-menu.css';

const CLOSE_DELAY = 300; // ms

class ActionMenu extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleClosePopover',
'handleToggleOpenState',
'clickDelayer'
]);
this.state = {
isOpen: false,
forceHide: false
};
}
handleClosePopover () {
this.closeTimeoutId = setTimeout(() => {
this.setState({isOpen: false});
this.closeTimeoutId = null;
}, CLOSE_DELAY);
}
handleToggleOpenState () {
// Mouse enter back in after timeout was started prevents it from closing.
if (this.closeTimeoutId) {
clearTimeout(this.closeTimeoutId);
this.closeTimeoutId = null;
} else if (!this.state.isOpen) {
this.setState({
isOpen: true,
forceHide: false
});
}
}
clickDelayer (fn) {
// Return a wrapped action that manages the menu closing.
// @todo we may be able to use react-transition for this in the future
// for now all this work is to ensure the menu closes BEFORE the
// (possibly slow) action is started.
return event => {
this.setState({forceHide: true, isOpen: false}, () => {
if (fn) fn(event);
setTimeout(() => this.setState({forceHide: false}));
});
};
}
render () {
const {
className,
img: mainImg,
title: mainTitle,
moreButtons,
onClick
} = this.props;

const mainTooltipId = `tooltip-${Math.random()}`;

return (
<div
className={classNames(styles.menuContainer, className, {
[styles.expanded]: this.state.isOpen,
[styles.forceHidden]: this.state.forceHide
})}
onMouseEnter={this.handleToggleOpenState}
onMouseLeave={this.handleClosePopover}
onTouchStart={this.handleToggleOpenState}
>
<button
aria-label={mainTitle}
className={classNames(styles.button, styles.mainButton)}
data-for={mainTooltipId}
data-tip={mainTitle}
onClick={this.clickDelayer(onClick)}
>
<img
className={styles.mainIcon}
draggable={false}
src={mainImg}
/>
</button>
<ReactTooltip
className={styles.tooltip}
effect="solid"
id={mainTooltipId}
place="left"
/>
<div className={styles.moreButtonsOuter}>
<div className={styles.moreButtons}>
{(moreButtons || []).map(({img, title, onClick: handleClick}) => {
const isComingSoon = !handleClick;
const tooltipId = `tooltip-${Math.random()}`;
return (
<div key={tooltipId}>
<button
aria-label={title}
className={classNames(styles.button, styles.moreButton, {
[styles.comingSoon]: isComingSoon
})}
data-for={tooltipId}
data-tip={title}
onClick={this.clickDelayer(handleClick)}
>
<img
className={styles.moreIcon}
draggable={false}
src={img}
/>
</button>
<ReactTooltip
className={classNames(styles.tooltip, {
[styles.comingSoonTooltip]: isComingSoon
})}
effect="solid"
id={tooltipId}
place="left"
/>
</div>
);
})}
</div>
</div>
</div>
);
}
}

ActionMenu.propTypes = {
className: PropTypes.string,
img: PropTypes.string,
moreButtons: PropTypes.arrayOf(PropTypes.shape({
img: PropTypes.string,
title: PropTypes.node.isRequired,
onClick: PropTypes.func // Optional, "coming soon" if no callback provided
})),
onClick: PropTypes.func.isRequired,
title: PropTypes.node.isRequired
};

export default ActionMenu;
12 changes: 12 additions & 0 deletions src/components/action-menu/icon--camera.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions src/components/action-menu/icon--file-upload.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions src/components/action-menu/icon--paint.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions src/components/action-menu/icon--surprise.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 0 additions & 21 deletions src/components/asset-button/asset-button.css

This file was deleted.

Loading