Nested toolbar w/ "sticky" when clicked

This commit is contained in:
dannyrb 2020-06-14 23:42:44 -04:00
parent 217d848f9f
commit 8c1a844558
11 changed files with 167 additions and 93 deletions

View File

@ -0,0 +1,41 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { ToolbarButton } from '@ohif/ui';
function NestedToolbar({ children }) {
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
function closeNestedToolbar() {
if (isOpen) {
setIsOpen(false);
}
}
window.addEventListener('click', closeNestedToolbar);
return () => {
window.removeEventListener('click', closeNestedToolbar);
};
}, [isOpen]);
const dropdownContent = isOpen ? children : undefined;
return (
<ToolbarButton
id="More"
label="More"
icon="tool-more-menu"
onClick={() => {
setIsOpen(!isOpen);
}}
dropdownContent={dropdownContent}
isActive={isOpen}
type="primary"
/>
);
}
NestedToolbar.propTypes = {
children: PropTypes.any.isRequired,
};
export default NestedToolbar;

View File

@ -1,8 +1,9 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { SidePanel, ToolbarButton } from '@ohif/ui'; import { SidePanel } from '@ohif/ui';
import Header from './Header.jsx'; import Header from './Header.jsx';
import NestedToolbar from './NestedToolbar.jsx';
function ViewerLayout({ function ViewerLayout({
// From Extension Module Params // From Extension Module Params
@ -79,12 +80,26 @@ function ViewerLayout({
return ( return (
<div> <div>
<Header> <Header>
{/* relative, flex, justify-center */} <div className="relative flex justify-center">
<div className="flex">
{toolbars.primary.map(toolDef => { {toolbars.primary.map(toolDef => {
const { id, Component, componentProps } = toolDef; const isNested = Array.isArray(toolDef);
return <Component key={id} id={id} {...componentProps} />; if (!isNested) {
const { id, Component, componentProps } = toolDef;
return <Component key={id} id={id} {...componentProps} />;
} else {
return (
<NestedToolbar>
<div className="flex">
{toolDef.map(x => {
const { id, Component, componentProps } = x;
return <Component key={id} id={id} {...componentProps} />;
})}
</div>
</NestedToolbar>
);
}
})} })}
</div> </div>
</Header> </Header>

View File

@ -55,17 +55,17 @@ export default function mode({ modeConfiguration }) {
]); ]);
// Could import layout selector here from org.ohif.default (when it exists!) // Could import layout selector here from org.ohif.default (when it exists!)
ToolBarService.setToolBarLayout([ // ToolBarService.setToolBarLayout([
// Primary // // Primary
{ // {
tools: ['Zoom', 'Levels', 'Pan', 'Capture', 'Layout'], // tools: ['Zoom', 'Levels', 'Pan', 'Capture', 'Layout'],
moreTools: ['Zoom'], // moreTools: ['Zoom'],
}, // },
// Secondary // // Secondary
{ // {
tools: ['Annotate', 'Bidirectional', 'Ellipse', 'Length'], // tools: ['Annotate', 'Bidirectional', 'Ellipse', 'Length'],
}, // },
]); // ]);
}, },
layoutTemplate: ({ routeProps }) => { layoutTemplate: ({ routeProps }) => {
return { return {

View File

@ -34,10 +34,10 @@ export default function mode({ modeConfiguration }) {
'Zoom', 'Zoom',
'Wwwc', 'Wwwc',
'Pan', 'Pan',
'Capture', // toggle --> command to open capture modal? needs to know when it should be off? (promise?) 'Capture',
'Layout', // toggle --> command to open layout dialog? needs to know when it should be off? (promise?) // 'Layout', // toggle --> command to open layout dialog? needs to know when it should be off? (promise?)
'Divider', 'Divider',
'Zoom', // ? array of arrays? ['Zoom', 'Wwwc'],
]); ]);
ToolBarService.createButtonSection('secondary', [ ToolBarService.createButtonSection('secondary', [
'Annotate', 'Annotate',

View File

@ -4,7 +4,7 @@ export default [
id: 'Divider', id: 'Divider',
type: 'ohif.divider', type: 'ohif.divider',
}, },
// Primary // ~~ Primary
{ {
id: 'Zoom', id: 'Zoom',
type: 'ohif.radioGroup', type: 'ohif.radioGroup',
@ -50,7 +50,19 @@ export default [
type: 'primary', type: 'primary',
}, },
}, },
// Secondary {
id: 'Capture',
type: 'ohif.action',
props: {
icon: 'tool-capture',
label: 'Capture',
commandName: 'showDownloadViewportModal',
type: 'primary',
},
},
// Layout
// Expanded/Nested?
// ~~ Secondary
{ {
id: 'Annotate', id: 'Annotate',
type: 'ohif.radioGroup', type: 'ohif.radioGroup',
@ -63,8 +75,7 @@ export default [
label: 'Annotate', label: 'Annotate',
commandName: 'setToolActive', commandName: 'setToolActive',
commandOptions: { toolName: 'ArrowAnnotate' }, commandOptions: { toolName: 'ArrowAnnotate' },
type: 'secondary', // Purely for background color/hover styles :| type: 'secondary',
// Layout Template can set this?
}, },
}, },
{ {

View File

@ -63,61 +63,40 @@ export default class ToolBarService {
} }
getButtonSection(key) { getButtonSection(key) {
const buttonSection = this.buttonSections[key]; const buttonSectionIds = this.buttonSections[key];
const buttonsInSection = []; const buttonsInSection = [];
if (!buttonSection) { if (!buttonSectionIds) {
return buttonsInSection; return buttonsInSection;
} }
buttonSection.forEach(btnId => { buttonSectionIds.forEach(btnIdOrArray => {
const button = this.buttons[btnId]; const isNested = Array.isArray(btnIdOrArray);
if (button) {
buttonsInSection.push(button); if (isNested) {
const btnIds = btnIdOrArray;
const nestedButtons = [];
btnIds.forEach(nestedBtnId => {
const nestedBtn = this.buttons[nestedBtnId];
const mappedNestedBtn = this._mapButtonToDisplay(nestedBtn, key);
nestedButtons.push(mappedNestedBtn);
});
if (nestedButtons.length) {
buttonsInSection.push(nestedButtons);
}
} else { } else {
console.warn(`${btnId} is not a registered button.`); const btnId = btnIdOrArray;
const btn = this.buttons[btnId];
const mappedBtn = this._mapButtonToDisplay(btn, key);
buttonsInSection.push(mappedBtn);
} }
}); });
const mappedButtonSection = buttonsInSection.map(bt => { return buttonsInSection;
const { id, type, component, props } = bt;
const buttonType = this._buttonTypes()[type];
// Filter out & warn
if (!buttonType) {
return;
}
const onClick = evt => {
console.warn(
`Handling click for: BTN::${id}`,
evt,
bt,
buttonType,
btnSection
);
const btnSection = key;
if (buttonType.clickHandler) {
buttonType.clickHandler(evt, bt, btnSection);
}
if (bt.props.onClick) {
bt.onClick(evt, bt, btnSection);
}
if (bt.props.clickHandler) {
bt.clickHandler(evt, bt, btnSection);
}
this._trySetButtonActive(id);
};
return {
id,
Component: component || buttonType.defaultComponent,
componentProps: Object.assign({}, props, { onClick }), //
};
});
return mappedButtonSection;
} }
/** /**
@ -152,4 +131,38 @@ export default class ToolBarService {
}); });
} }
}; };
/**
*
* @param {*} btn
* @param {*} btnSection
*/
_mapButtonToDisplay(btn, btnSection) {
const { id, type, component, props } = btn;
const buttonType = this._buttonTypes()[type];
if (!buttonType) {
return;
}
const onClick = evt => {
if (buttonType.clickHandler) {
buttonType.clickHandler(evt, btn, btnSection);
}
if (btn.props.onClick) {
btn.onClick(evt, btn, btnSection);
}
if (btn.props.clickHandler) {
btn.clickHandler(evt, btn, btnSection);
}
this._trySetButtonActive(id);
};
return {
id,
Component: component || buttonType.defaultComponent,
componentProps: Object.assign({}, props, { onClick }), //
};
}
} }

View File

@ -28,6 +28,7 @@ const ToolbarButton = ({
return ( return (
<div key={id}> <div key={id}>
<Tooltip <Tooltip
isSticky={shouldShowDropdown}
content={shouldShowDropdown ? dropdownContent : label} content={shouldShowDropdown ? dropdownContent : label}
tight={shouldShowDropdown} tight={shouldShowDropdown}
> >

View File

@ -24,7 +24,7 @@ const arrowPositionStyle = {
}, },
}; };
const Tooltip = ({ position, content, tight, children }) => { const Tooltip = ({ content, isSticky, position, tight, children }) => {
const [isActive, setIsActive] = useState(false); const [isActive, setIsActive] = useState(false);
const handleMouseOver = () => { const handleMouseOver = () => {
@ -39,6 +39,8 @@ const Tooltip = ({ position, content, tight, children }) => {
} }
}; };
const isOpen = isSticky || isActive;
return ( return (
<div <div
className="relative " className="relative "
@ -51,8 +53,8 @@ const Tooltip = ({ position, content, tight, children }) => {
{children} {children}
<div <div
className={classnames(`tooltip tooltip-${position}`, { className={classnames(`tooltip tooltip-${position}`, {
block: isActive, block: isOpen,
hidden: !isActive, hidden: !isOpen,
})} })}
> >
<div <div
@ -65,7 +67,7 @@ const Tooltip = ({ position, content, tight, children }) => {
> >
{content} {content}
<svg <svg
className="absolute text-primary-dark h-4 stroke-secondary-main" className="absolute h-4 text-primary-dark stroke-secondary-main"
style={arrowPositionStyle[position]} style={arrowPositionStyle[position]}
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@ -80,11 +82,12 @@ const Tooltip = ({ position, content, tight, children }) => {
Tooltip.defaultProps = { Tooltip.defaultProps = {
tight: false, tight: false,
isSticky: false,
position: 'bottom', position: 'bottom',
}; };
Tooltip.propTypes = { Tooltip.propTypes = {
tight: PropTypes.bool, content: PropTypes.node.isRequired,
position: PropTypes.oneOf([ position: PropTypes.oneOf([
'bottom', 'bottom',
'bottom-left', 'bottom-left',
@ -92,8 +95,9 @@ Tooltip.propTypes = {
'left', 'left',
'right', 'right',
]), ]),
isSticky: PropTypes.bool,
tight: PropTypes.bool,
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired,
content: PropTypes.node.isRequired,
}; };
export default Tooltip; export default Tooltip;

View File

@ -1,10 +1,10 @@
.tooltip { .tooltip {
@apply z-10 absolute; @apply absolute z-10;
} }
/* TOOLTIP WORKAROUND FOR ARROW UP */ /* TOOLTIP WORKAROUND FOR ARROW UP */
.tooltip.tooltip-bottom .tooltip-box::before { .tooltip.tooltip-bottom .tooltip-box::before {
@apply bg-primary-dark absolute z-10; @apply absolute z-10 bg-primary-dark;
content: ''; content: '';
width: 14px; width: 14px;
height: 1px; height: 1px;
@ -44,7 +44,7 @@
} }
.tooltip.tooltip-right .tooltip-box::before { .tooltip.tooltip-right .tooltip-box::before {
@apply bg-primary-dark absolute z-10; @apply absolute z-10 bg-primary-dark;
content: ''; content: '';
width: 2px; width: 2px;
height: 15px; height: 15px;
@ -54,7 +54,7 @@
} }
.tooltip.tooltip-left .tooltip-box::before { .tooltip.tooltip-left .tooltip-box::before {
@apply bg-primary-dark absolute z-10; @apply absolute z-10 bg-primary-dark;
content: ''; content: '';
width: 2px; width: 2px;
height: 15px; height: 15px;
@ -64,7 +64,7 @@
} }
.tooltip.tooltip-bottom-right .tooltip-box::before { .tooltip.tooltip-bottom-right .tooltip-box::before {
@apply bg-primary-dark absolute z-10; @apply absolute z-10 bg-primary-dark;
content: ''; content: '';
width: 15px; width: 15px;
height: 2px; height: 2px;
@ -73,19 +73,10 @@
} }
.tooltip.tooltip-bottom-left .tooltip-box::before { .tooltip.tooltip-bottom-left .tooltip-box::before {
@apply bg-primary-dark absolute z-10; @apply absolute z-10 bg-primary-dark;
content: ''; content: '';
width: 15px; width: 15px;
height: 2px; height: 2px;
left: 5px; left: 5px;
top: -1px; top: -1px;
} }
/* TODO: REMOVE BELOW CLASS "showTooltipOnHover" AFTER TOOLTIP REPLACEMENT */
.showTooltipOnHover .tooltip {
display: none;
}
.showTooltipOnHover:hover .tooltip {
display: block;
}

View File

@ -184,7 +184,7 @@ const ViewportActionBar = ({ studyData, onSeriesChange }) => {
</div> </div>
} }
> >
<div className="relative flex justify-end showTooltipOnHover"> <div className="relative flex justify-end">
<div className="relative"> <div className="relative">
<Icon name="profile" className="w-5 text-white" /> <Icon name="profile" className="w-5 text-white" />
<Icon <Icon

View File

@ -37,7 +37,6 @@ import Thumbnail from './Thumbnail';
import ThumbnailNoImage from './ThumbnailNoImage'; import ThumbnailNoImage from './ThumbnailNoImage';
import ThumbnailTracked from './ThumbnailTracked'; import ThumbnailTracked from './ThumbnailTracked';
import ThumbnailList from './ThumbnailList'; import ThumbnailList from './ThumbnailList';
import Toolbar from './Toolbar';
import ToolbarButton from './ToolbarButton'; import ToolbarButton from './ToolbarButton';
import Tooltip from './Tooltip'; import Tooltip from './Tooltip';
import Typography from './Typography'; import Typography from './Typography';
@ -87,7 +86,6 @@ export {
ThumbnailNoImage, ThumbnailNoImage,
ThumbnailTracked, ThumbnailTracked,
ThumbnailList, ThumbnailList,
Toolbar,
ToolbarButton, ToolbarButton,
Tooltip, Tooltip,
Typography, Typography,