feat: Tooltip, ToolbarButton, Toolbar API and misc

This commit is contained in:
Rodrigo Antinarelli 2020-04-16 18:19:37 -03:00 committed by James A. Petts
parent 3223d6a8a1
commit 9154f36477
15 changed files with 580 additions and 290 deletions

View File

@ -28,6 +28,7 @@ export {
MeasurementTable,
NavBar,
Notification,
PrimaryToolbar,
Select,
SegmentationTable,
SidePanel,
@ -46,6 +47,8 @@ export {
Thumbnail,
ThumbnailSR,
ThumbnailList,
ToolbarButton,
Tooltip,
Typography,
ViewportActionBar,
ViewportGrid,

View File

@ -41,62 +41,6 @@
@apply hidden;
}
.tooltip {
@apply z-10 absolute;
}
/* TOOLTIP WORKAROUND FOR ARROW UP */
.tooltip.tooltip-up::before {
@apply bg-primary-dark absolute z-10;
content: '';
width: 14px;
height: 1px;
top: -1px;
left: 50%;
transform: translateX(-50%);
}
.tooltip.tooltip-right {
left: calc(100% + 15px);
}
.tooltip.tooltip-right::before {
@apply bg-black absolute z-10;
content: '';
width: 2px;
height: 15px;
left: -1px;
top: 50%;
transform: translateY(-50%);
}
.tooltip.tooltip-top-right::before {
@apply bg-primary-dark absolute z-10;
content: '';
width: 15px;
height: 2px;
right: 5px;
top: -1px;
}
.tooltip.tooltip-top-left::before {
@apply bg-primary-dark absolute z-10;
content: '';
width: 15px;
height: 2px;
left: 5px;
top: -1px;
}
/* TOOLTIP WORKAROUND FOR ARROW */
.showTooltipOnHover .tooltip {
display: none;
}
.showTooltipOnHover:hover .tooltip {
display: block;
}
.showExcludeButtonOnHover .excludeButton {
display: none;
}

View File

@ -0,0 +1,79 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { ToolbarButton, IconButton, Icon, Tooltip } from '@ohif/ui';
const PrimaryToolbar = ({ activeTool, tools, moreTools }) => {
return (
<div className="flex">
{tools.map((tool) => {
const { id, onClick, icon, label, dropdownContent } = tool;
const isActive = activeTool === tool.id;
return (
<div className="relative flex justify-center" key={tool.id}>
<ToolbarButton
id={id}
isActive={isActive}
onClick={onClick}
icon={icon}
label={label}
dropdownContent={dropdownContent}
/>
</div>
);
})}
{!!moreTools.length && (
<>
<span className="w-1 border-l py-4 mx-2 border-common-dark" />
<Tooltip position="bottom" content="More tools">
<IconButton
className={classnames(
'mx-1 text-common-bright hover:bg-primary-dark hover:text-primary-light'
)}
color="inherit"
>
<Icon name="tool-more-menu" />
</IconButton>
</Tooltip>
</>
)}
</div>
);
};
PrimaryToolbar.defaultProps = {
activeTool: '',
moreTools: [],
};
PrimaryToolbar.propTypes = {
activeTool: PropTypes.string,
tools: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
label: PropTypes.string,
icon: PropTypes.string,
commandName: PropTypes.string,
commandOptions: PropTypes.shape({
toolName: PropTypes.string,
}),
onClick: PropTypes.func,
dropdownContent: PropTypes.node,
})
).isRequired,
moreTools: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
label: PropTypes.string,
icon: PropTypes.string,
commandName: PropTypes.string,
commandOptions: PropTypes.shape({
toolName: PropTypes.string,
}),
onClick: PropTypes.func,
})
),
};
export default PrimaryToolbar;

View File

@ -0,0 +1,127 @@
---
name: Primary Toolbar
menu: Data Display
route: components/primaryToolbar
---
import { useState } from 'react';
import { Playground } from 'docz';
import classnames from 'classnames';
import { PrimaryToolbar } from '@ohif/ui';
# Primary Toolbar
Primary Toolbar is used to create a list of tools.
## Import
```javascript
import { PrimaryToolbar } from '@ohif/ui';
```
<Playground>
{() => {
const [activeTool, setActiveTool] = useState('Zoom');
const dropdownContent = [
{
name: 'Soft tissue',
value: '400/40',
},
{ name: 'Lung', value: '1500 / -600' },
{ name: 'Liver', value: '150 / 90' },
{ name: 'Bone', value: '2500 / 480' },
{ name: 'Brain', value: '80 / 40' },
];
const tools = [
{
id: 'Zoom',
label: 'Zoom',
icon: 'tool-zoom',
commandName: 'setToolActive',
commandOptions: { toolName: 'Zoom' },
onClick: () => setActiveTool('Zoom'),
},
{
id: 'Wwwc',
label: 'Levels',
icon: 'tool-window-level',
commandName: 'setToolActive',
commandOptions: { toolName: 'Wwwc' },
onClick: () => setActiveTool('Wwwc'),
dropdownContent: (
<div>
{dropdownContent.map((row, i) => (
<div
key={i}
className="flex justify-between py-2 px-3 hover:bg-secondary-dark cursor-pointer"
>
<div>
<span className="text-base text-white">{row.name}</span>
<span className="text-base text-primary-light ml-3">
{row.value}
</span>
</div>
<span className="text-base text-primary-active ml-4">{i}</span>
</div>
))}
</div>
),
},
{
id: 'Pan',
label: 'Pan',
icon: 'tool-move',
commandName: 'setToolActive',
commandOptions: { toolName: 'Pan' },
onClick: () => setActiveTool('Pan'),
},
{
id: 'Capture',
label: 'Capture',
icon: 'tool-capture',
commandName: 'setToolActive',
commandOptions: { toolName: 'Capture' },
onClick: () => setActiveTool('Capture'),
},
{
id: 'Layout',
label: 'Layout',
icon: 'tool-layout',
commandName: 'setToolActive',
commandOptions: { toolName: 'Layout' },
onClick: () => setActiveTool('Layout'),
},
];
const moreTools = [
{
id: 'Zoom 2',
label: 'Zoom',
icon: 'tool-zoom',
commandName: 'setToolActive',
commandOptions: { toolName: 'Zoom' },
onClick: () => setActiveTool('Zoom 2'),
},
{
id: 'Layout 2',
label: 'Layout',
icon: 'tool-layout',
commandName: 'setToolActive',
commandOptions: { toolName: 'Layout' },
onClick: () => setActiveTool('Layout 2'),
},
];
return (
<div className="py-10 flex justify-center">
<PrimaryToolbar
moreTools={moreTools}
activeTool={activeTool}
tools={tools}
/>
</div>
);
}}
</Playground>
## Properties
<Props of={PrimaryToolbar} />

View File

@ -0,0 +1,2 @@
import PrimaryToolbar from './PrimaryToolbar';
export default PrimaryToolbar;

View File

@ -0,0 +1,53 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { IconButton, Icon, Tooltip } from '@ohif/ui';
const ToolbarButton = ({
id,
isActive,
onClick,
icon,
label,
dropdownContent,
}) => {
const shouldShowDropdown = !!isActive && !!dropdownContent;
return (
<div key={id}>
<Tooltip
content={shouldShowDropdown ? dropdownContent : label}
tight={shouldShowDropdown}
>
<IconButton
variant={isActive ? 'contained' : 'text'}
className={classnames('mx-1', {
'text-black': isActive,
'text-common-bright hover:bg-primary-dark hover:text-primary-light': !isActive,
})}
onClick={onClick}
key={id}
>
<Icon name={icon} />
</IconButton>
</Tooltip>
</div>
);
};
ToolbarButton.defaultProps = {
dropdownContent: null,
isActive: false,
};
ToolbarButton.propTypes = {
id: PropTypes.string.isRequired,
isActive: PropTypes.bool,
onClick: PropTypes.func.isRequired,
icon: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
/** Tooltip content can be replaced for a customized content by passing a node to this value. */
dropdownContent: PropTypes.node,
};
export default ToolbarButton;

View File

@ -0,0 +1,44 @@
---
name: Toolbar Button
menu: General
route: components/toolbarButton
---
import { useState } from 'react';
import { Playground, Props } from 'docz';
import { ToolbarButton } from '@ohif/ui';
# Toolbar Button
Toolbar Buttons are used to populate the Toolbar.
## Import
```javascript
import { ToolbarButton } from '@ohif/ui';
```
<Playground>
{() => {
const [isActive, setIsActive] = useState(false);
const tool = {
id: 'Zoom',
label: 'Zoom',
icon: 'tool-zoom',
type: null,
commandName: 'setToolActive',
commandOptions: { toolName: 'Zoom' },
onClick: () => setIsActive(true),
isActive,
};
return (
<div className="py-10 flex justify-center">
<ToolbarButton {...tool} />
</div>
);
}}
</Playground>
## Properties
<Props of={ToolbarButton} />

View File

@ -0,0 +1,2 @@
import ToolbarButton from './ToolbarButton';
export default ToolbarButton;

View File

@ -0,0 +1,91 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import './tooltip.css';
const arrowPositionStyle = {
bottom: {
top: -15,
left: '50%',
transform: 'translateX(-50%)',
},
'bottom-left': { top: -15, left: 5 },
'bottom-right': { top: -15, right: 5 },
right: {
top: 'calc(50% - 8px)',
left: -15,
transform: 'rotate(270deg)',
},
left: {
top: 'calc(50% - 8px)',
right: -15,
transform: 'rotate(-270deg)',
},
};
const Tooltip = ({ position, content, tight, children }) => {
const [isActive, setIsActive] = useState(false);
return (
<div
className="relative"
onMouseOver={() => {
if (!isActive) {
setIsActive(true);
}
}}
onMouseOut={() => {
if (isActive) {
setIsActive(false);
}
}}
>
{children}
<div
className={classnames(`tooltip tooltip-${position}`, {
block: isActive,
hidden: !isActive,
})}
>
<div
className={classnames(
'relative tooltip-box bg-primary-dark border border-secondary-main text-white text-base rounded inset-x-auto top-full w-max-content',
{
'py-1 px-4': !tight,
}
)}
>
{content}
<svg
className="absolute text-primary-dark h-4 stroke-secondary-main"
style={arrowPositionStyle[position]}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path fill="currentColor" d="M24 22h-24l12-20z" />
</svg>
</div>
</div>
</div>
);
};
Tooltip.defaultProps = {
tight: false,
position: 'bottom',
};
Tooltip.propTypes = {
tight: PropTypes.bool,
position: PropTypes.oneOf([
'bottom',
'bottom-left',
'bottom-right',
'left',
'right',
]),
children: PropTypes.node.isRequired,
content: PropTypes.node.isRequired,
};
export default Tooltip;

View File

@ -0,0 +1,40 @@
---
name: Tooltip
menu: Data Display
route: components/tooltip
---
import { Playground, Props } from 'docz';
import { Tooltip, Typography } from '@ohif/ui';
# Tooltip
Tooltips display informative content when users hover over, focus on, or tap an
element.
## Import
```javascript
import { Tooltip } from '@ohif/ui';
```
<Playground>
{() => {
const tooltips = ['bottom', 'bottom-left', 'bottom-right', 'left', 'right'];
return (
<div className="py-10 flex flex-col items-center justify-center">
{tooltips.map((tooltip) => (
<div className="mb-16">
<Tooltip content="Tooltip content" position={tooltip}>
<Typography>Tooltip {tooltip}</Typography>
</Tooltip>
</div>
))}
</div>
);
}}
</Playground>
## Properties
<Props of={Tooltip} />

View File

@ -0,0 +1,2 @@
import Tooltip from './Tooltip';
export default Tooltip;

View File

@ -0,0 +1,91 @@
.tooltip {
@apply z-10 absolute;
}
/* TOOLTIP WORKAROUND FOR ARROW UP */
.tooltip.tooltip-bottom .tooltip-box::before {
@apply bg-primary-dark absolute z-10;
content: '';
width: 14px;
height: 1px;
top: -1px;
left: 50%;
transform: translateX(-50%);
}
.tooltip.tooltip-bottom {
@apply pt-2 mt-1;
left: 50%;
transform: translateX(-50%);
}
.tooltip.tooltip-bottom-left {
@apply pt-2 mt-1;
left: 0;
}
.tooltip.tooltip-bottom-right {
@apply pt-2 mt-1;
right: 0;
}
.tooltip.tooltip-left {
@apply mr-4;
top: 50%;
right: calc(100%);
transform: translateY(-50%);
}
.tooltip.tooltip-right {
@apply ml-4;
top: 50%;
left: calc(100%);
transform: translateY(-50%);
}
.tooltip.tooltip-right .tooltip-box::before {
@apply bg-primary-dark absolute z-10;
content: '';
width: 2px;
height: 15px;
left: -1px;
top: 50%;
transform: translateY(-50%);
}
.tooltip.tooltip-left .tooltip-box::before {
@apply bg-primary-dark absolute z-10;
content: '';
width: 2px;
height: 15px;
right: -1px;
top: 50%;
transform: translateY(-50%);
}
.tooltip.tooltip-bottom-right .tooltip-box::before {
@apply bg-primary-dark absolute z-10;
content: '';
width: 15px;
height: 2px;
right: 5px;
top: -1px;
}
.tooltip.tooltip-bottom-left .tooltip-box::before {
@apply bg-primary-dark absolute z-10;
content: '';
width: 15px;
height: 2px;
left: 5px;
top: -1px;
}
/* TODO: REMOVE BELOW CLASS "showTooltipOnHover" AFTER TOOLTIP REPLACEMENT */
.showTooltipOnHover .tooltip {
display: none;
}
.showTooltipOnHover:hover .tooltip {
display: block;
}

View File

@ -15,6 +15,7 @@ import MeasurementTable from './MeasurementTable';
import NavBar from './NavBar';
import Notification from './Notification';
import Select from './Select';
import PrimaryToolbar from './PrimaryToolbar';
import SegmentationTable from './SegmentationTable';
import SidePanel from './SidePanel';
import StudyBrowser from './StudyBrowser';
@ -32,6 +33,8 @@ import ThemeWrapper from './ThemeWrapper/';
import Thumbnail from './Thumbnail';
import ThumbnailSR from './ThumbnailSR';
import ThumbnailList from './ThumbnailList';
import ToolbarButton from './ToolbarButton';
import Tooltip from './Tooltip';
import Typography from './Typography';
import ViewportActionBar from './ViewportActionBar';
import ViewportGrid from './ViewportGrid';
@ -53,6 +56,7 @@ export {
MeasurementTable,
NavBar,
Notification,
PrimaryToolbar,
Select,
SegmentationTable,
SidePanel,
@ -71,6 +75,8 @@ export {
Thumbnail,
ThumbnailSR,
ThumbnailList,
ToolbarButton,
Tooltip,
Typography,
ViewportActionBar,
ViewportGrid,

View File

@ -1,166 +0,0 @@
---
name: Toolbar Header
menu: Views
route: examples/toolbarHeader
---
import { useState } from 'react';
import { Playground } from 'docz';
import classnames from 'classnames';
import { NavBar, Typography, Svg, Icon, IconButton } from '@ohif/ui';
# Toolbar Header
This example shows you how you can build a Toolbar using the available
components.
## Basic usage
<Playground>
{() => {
const [activeTool, setActiveTool] = useState('Zoom');
const [showTooltip, setShowTooltip] = useState(null);
const tools = [
{
id: 'Zoom',
label: 'Zoom',
icon: 'tool-zoom',
type: null,
commandName: 'setToolActive',
commandOptions: { toolName: 'Zoom' },
},
{
id: 'Wwwc',
label: 'Levels',
icon: 'tool-window-level',
type: null,
commandName: 'setToolActive',
commandOptions: { toolName: 'Wwwc' },
},
{
id: 'Pan',
label: 'Pan',
icon: 'tool-move',
type: null,
commandName: 'setToolActive',
commandOptions: { toolName: 'Pan' },
},
{
id: 'Capture',
label: 'Capture',
icon: 'tool-capture',
type: null,
commandName: 'setToolActive',
commandOptions: { toolName: 'Capture' },
},
{
id: 'Layout',
label: 'Layout',
icon: 'tool-layout',
type: null,
commandName: 'setToolActive',
commandOptions: { toolName: 'Layout' },
},
];
const renderToolbar = () => {
return tools.map((tool, i) => {
const isActive = activeTool === tool.id;
const shouldShowTooltip = showTooltip === tool.id;
return (
<div className="relative flex justify-center" key={tool.id}>
<IconButton
variant={isActive ? 'contained' : 'text'}
className={classnames('mx-1', {
'text-black': isActive,
'text-common-bright hover:bg-primary-dark hover:text-primary-light': !isActive,
})}
onClick={(e) => {
setActiveTool(tool.id);
}}
onMouseOver={() => setShowTooltip(tool.id)}
onMouseOut={() => setShowTooltip(null)}
key={tool.id}
>
<Icon name={tool.icon} />
</IconButton>
{shouldShowTooltip && (
<div
className={classnames(
'tooltip tooltip-up bg-primary-dark border border-secondary-main text-white text-base rounded py-1 px-4 inset-x-auto top-full mt-2 w-max-content'
)}
>
{tool.label}
<svg
className="absolute text-primary-dark w-full h-4 left-0 stroke-secondary-main"
style={{ top: -15 }}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path fill="currentColor" d="M24 22h-24l12-20z" />
</svg>
</div>
)}
</div>
);
});
};
return (
<div className="pb-10">
<NavBar className="justify-between border-b-4 border-black">
<div className="flex flex-1 justify-between">
<div className="flex items-center">
<div className="mx-3 inline-flex items-center">
<Icon
name="chevron-left"
className="text-primary-main w-10 h-10 cursor-pointer"
onClick={() => alert('Navigate to previous page')}
/>
<a href="#" className="ml-4">
<Svg name="logo-ohif" />
</a>
</div>
</div>
<div className="flex items-center">
<div className="flex items-center">
{renderToolbar()}
<span className="w-1 border-l py-4 mx-2 border-common-dark" />
<IconButton
className={classnames(
'mx-1 text-common-bright hover:bg-primary-dark hover:text-primary-light'
)}
color="inherit"
onClick={(e) => {
alert('Open menu');
}}
>
<Icon name="tool-more-menu" />
</IconButton>
</div>
</div>
<div className="flex items-center">
<span className="mr-3 text-common-light text-lg">
FOR INVESTIGATIONAL USE ONLY
</span>
<IconButton
variant="text"
color="inherit"
className="text-primary-active"
onClick={() => {}}
>
<React.Fragment>
<Icon name="settings" /> <Icon name="chevron-down" />
</React.Fragment>
</IconButton>
</div>
</div>
</NavBar>
</div>
);
}}
</Playground>
## Properties
<Props of={NavBar} />

View File

@ -1,96 +1,78 @@
import React, { useState } from 'react';
import classnames from 'classnames';
import { NavBar, Svg, Icon, IconButton } from '@ohif/ui';
import { NavBar, Svg, Icon, IconButton, PrimaryToolbar } from '@ohif/ui';
const Header = () => {
const [activeTool, setActiveTool] = useState(null);
const [showTooltip, setShowTooltip] = useState(null);
const [activeTool, setActiveTool] = useState('Zoom');
const dropdownContent = [
{
name: 'Soft tissue',
value: '400/40',
},
{ name: 'Lung', value: '1500 / -600' },
{ name: 'Liver', value: '150 / 90' },
{ name: 'Bone', value: '2500 / 480' },
{ name: 'Brain', value: '80 / 40' },
];
const tools = [
{
id: 'Zoom',
label: 'Zoom',
icon: 'tool-zoom',
type: null,
commandName: 'setToolActive',
commandOptions: { toolName: 'Zoom' },
onClick: () => setActiveTool('Zoom'),
},
{
id: 'Wwwc',
label: 'Levels',
icon: 'tool-window-level',
type: null,
commandName: 'setToolActive',
commandOptions: { toolName: 'Wwwc' },
onClick: () => setActiveTool('Wwwc'),
dropdownContent: (
<div>
{dropdownContent.map((row, i) => (
<div
key={i}
className="flex justify-between py-2 px-3 hover:bg-secondary-dark cursor-pointer"
>
<div>
<span className="text-base text-white">{row.name}</span>
<span className="text-base text-primary-light ml-3">
{row.value}
</span>
</div>
<span className="text-base text-primary-active ml-4">{i}</span>
</div>
))}
</div>
),
},
{
id: 'Pan',
label: 'Pan',
icon: 'tool-move',
type: null,
commandName: 'setToolActive',
commandOptions: { toolName: 'Pan' },
onClick: () => setActiveTool('Pan'),
},
{
id: 'Capture',
label: 'Capture',
icon: 'tool-capture',
type: null,
commandName: 'setToolActive',
commandOptions: { toolName: 'Capture' },
onClick: () => setActiveTool('Capture'),
},
{
id: 'Layout',
label: 'Layout',
icon: 'tool-layout',
type: null,
commandName: 'setToolActive',
commandOptions: { toolName: 'Layout' },
onClick: () => setActiveTool('Layout'),
},
];
const renderToolbar = () => {
return tools.map((tool, i) => {
const isActive = activeTool === tool.id;
const shouldShowTooltip = showTooltip === tool.id;
return (
<div className="relative flex justify-center" key={tool.id}>
<IconButton
variant={isActive ? 'contained' : 'text'}
className={classnames('mx-1', {
'text-black': isActive,
'text-common-bright hover:bg-primary-dark hover:text-primary-light': !isActive,
})}
onClick={(e) => {
setActiveTool(tool.id);
}}
onMouseOver={() => setShowTooltip(tool.id)}
onMouseOut={() => setShowTooltip(null)}
key={tool.id}
>
<Icon name={tool.icon} />
</IconButton>
{shouldShowTooltip && (
<div
className={classnames(
'tooltip tooltip-up bg-primary-dark border border-secondary-main text-white text-base rounded py-1 px-4 inset-x-auto top-full mt-2 w-max-content'
)}
>
{tool.label}
<svg
className="absolute text-primary-dark w-full h-4 left-0 stroke-secondary-main"
style={{ top: -15 }}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path fill="currentColor" d="M24 22h-24l12-20z" />
</svg>
</div>
)}
</div>
);
});
};
return (
<NavBar className="justify-between border-b-4 border-black">
<div className="flex flex-1 justify-between">
@ -107,21 +89,11 @@ const Header = () => {
</div>
</div>
<div className="flex items-center">
<div className="flex items-center">
{renderToolbar()}
<span className="w-1 border-l py-4 mx-2 border-common-dark" />
<IconButton
className={classnames(
'mx-1 text-common-bright hover:bg-primary-dark hover:text-primary-light'
)}
color="inherit"
onClick={(e) => {
alert('Open menu');
}}
>
<Icon name="tool-more-menu" />
</IconButton>
</div>
<PrimaryToolbar
tools={tools}
activeTool={activeTool}
moreTools={tools}
/>
</div>
<div className="flex items-center">
<span className="mr-3 text-common-light text-lg">