vitessce
Version:
Vitessce app and React component library
165 lines (151 loc) • 5.52 kB
JavaScript
import React, { useState, useEffect } from 'react';
import { TwitterPicker } from 'react-color';
import { colorArrayToString, callbackOnKeyPress } from './utils';
import { PALETTE } from '../utils';
import Popover from './Popover';
/**
* Wrapper around a button element that supports asking for confirmation.
* @param {object} props
* @param {string} props.title The main button text.
* @param {string} props.subtitle Smaller text on a line beneath the main text. Optional.
* @param {function} props.onClick A "clean up" handler passed from the parent,
* to alert the parent Popover component that it should close the popover after the button has
* fired its handler.
* @param {function} props.handler A function to call on button click (or after confirmation).
* @param {string} props.handlerKey A key associated with the button, to support accessibility.
* @param {boolean} props.confirm Does the user need to press the button again to confirm?
* By default, false.
* @param {boolean} props.visible The visibility state from the parent popover,
* so that on visibility change, the button can clear its confirmation state.
*/
function PopoverMenuListButton(props) {
const {
title, subtitle, onClick, handler, handlerKey, confirm,
visible,
} = props;
const [isConfirming, setIsConfirming] = useState(false);
useEffect(() => {
// Want to clear the "confirming",
// state if the user hides the popover.
setIsConfirming(false);
}, [visible]);
function handleOrRequireConfirm() {
if (!confirm || isConfirming) {
onClick();
handler();
} else {
setIsConfirming(true);
}
}
const titleWithConfirm = `${isConfirming ? 'Confirm ' : ''}${title}`;
return (
<button
title={titleWithConfirm}
type="button"
onClick={handleOrRequireConfirm}
onKeyPress={e => callbackOnKeyPress(e, handlerKey, handleOrRequireConfirm)}
>{titleWithConfirm}
{subtitle && (<><br /><span className="small">{subtitle}</span></>)}
</button>
);
}
/**
* Helper component to create a list of buttons for the body of a popover.
* If the color, setColor, and palette props are provided then a color picker
* will be rendered at the top of the button list.
* @param {object} props
* @param {object[]} props.menuConfig The list of button definition objects.
* `{ title, subtitle, confirm, handler, handlerKey }`
* @param {function} props.onClick A "clean up" handler passed from the parent,
* to alert the parent Popover component that it should close the popover after the button has
* fired its handler.
* @param {number[]} props.color The current color. Optional.
* @param {string} props.palette The color palette for the color picker. Optional.
* @param {boolean} props.setColor The handler to call when a color has been selected. Optional.
* @param {boolean} props.visible The visibility state from the parent popover,
* so that on visibility change, buttons can clear confirmation states.
*/
function PopoverMenuList(props) {
const {
menuConfig,
onClick,
color = null,
palette = null,
setColor = null,
visible,
} = props;
function handleColorChange({ rgb }) {
if (rgb && setColor) {
setColor([rgb.r, rgb.g, rgb.b]);
}
}
const defaultPalette = palette
? palette.map(colorArrayToString)
: PALETTE.concat([[255, 255, 255], [128, 128, 128], [0, 0, 0]]).map(colorArrayToString);
return (
<div>
{color && setColor && defaultPalette && (
<TwitterPicker
className="popover-menu-color"
disableAlpha
width={108}
triangle="hide"
colors={defaultPalette}
color={colorArrayToString(color)}
onChangeComplete={handleColorChange}
/>
)}
<ul className="popover-menu-list">
{menuConfig.map(item => (
<li key={item.title + item.subtitle}>
<PopoverMenuListButton
{...item}
onClick={onClick}
visible={visible}
/>
</li>
))}
</ul>
</div>
);
}
/**
* Helper component to create a popover component with a list of buttons.
* If the color, setColor, and palette props are provided then a color picker
* will be rendered at the top of the button list.
* @param {object} props
* @param {object[]} props.menuConfig The list of button definition objects.
* `{ title, subtitle, confirm, handler, handlerKey }`
* @param {string} placement Where to place the popover (top, bottom, left, right).
* @param {number[]} props.color The current color. Optional.
* @param {string} props.palette The color palette for the color picker. Optional.
* @param {boolean} props.setColor The handler to call when a color has been selected. Optional.
* @param {Element|React.Component} props.children Children to render,
* which will trigger the popover on click.
*/
export default function PopoverMenu(props) {
const {
menuConfig, placement, children,
color = null, setColor = null, palette = null,
} = props;
const [visible, setVisible] = useState(false);
return (
<Popover
content={(
<PopoverMenuList
menuConfig={menuConfig}
onClick={() => setVisible(false)}
color={color}
setColor={setColor}
palette={palette}
visible={visible}
/>
)}
placement={placement}
visible={visible}
onVisibleChange={setVisible}
>
{children}
</Popover>
);
}