Create window level dropdown menu
This commit is contained in:
parent
67508f6667
commit
44297af965
@ -57,13 +57,20 @@ function ViewerLayout({
|
||||
|
||||
const [toolbars, setToolbars] = useState({ primary: [], secondary: [] });
|
||||
|
||||
const onPrimaryClickHandler = (evt, btn) => {
|
||||
if (btn.props && btn.props.commands && evt.value && btn.props.commands[evt.value]) {
|
||||
const { commandName, commandOptions } = btn.props.commands[evt.value];
|
||||
commandsManager.runCommand(commandName, commandOptions);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = ToolBarService.subscribe(
|
||||
ToolBarService.EVENTS.TOOL_BAR_MODIFIED,
|
||||
() => {
|
||||
console.warn('~~~ TOOL BAR MODIFIED EVENT CAUGHT');
|
||||
const updatedToolbars = {
|
||||
primary: ToolBarService.getButtonSection('primary'),
|
||||
primary: ToolBarService.getButtonSection('primary', { onClick: onPrimaryClickHandler }),
|
||||
secondary: ToolBarService.getButtonSection('secondary'),
|
||||
};
|
||||
setToolbars(updatedToolbars);
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
// TODO: torn, can either bake this here; or have to create a whole new button type
|
||||
// Only ways that you can pass in a custom React component for render :l
|
||||
import { ExpandableToolbarButton, ListMenu } from '@ohif/ui';
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
export default [
|
||||
// Divider
|
||||
@ -29,13 +32,58 @@ export default [
|
||||
config: {
|
||||
groupName: 'primaryTool',
|
||||
},
|
||||
component: ExpandableToolbarButton,
|
||||
props: {
|
||||
isActive: true,
|
||||
icon: 'tool-window-level',
|
||||
label: 'Levels',
|
||||
commandName: 'setToolActive',
|
||||
commandOptions: { toolName: 'Wwwc' },
|
||||
commands: {
|
||||
'windowLevelPreset1': {
|
||||
commandName: 'windowLevelPreset1',
|
||||
commandOptions: {},
|
||||
},
|
||||
'windowLevelPreset2': {
|
||||
commandName: 'windowLevelPreset2',
|
||||
commandOptions: {},
|
||||
},
|
||||
'windowLevelPreset3': {
|
||||
commandName: 'windowLevelPreset3',
|
||||
commandOptions: {},
|
||||
},
|
||||
'windowLevelPreset4': {
|
||||
commandName: 'windowLevelPreset4',
|
||||
commandOptions: {},
|
||||
},
|
||||
'windowLevelPreset5': {
|
||||
commandName: 'windowLevelPreset5',
|
||||
commandOptions: {},
|
||||
}
|
||||
},
|
||||
type: 'primary',
|
||||
content: ListMenu,
|
||||
contentProps: {
|
||||
options: [
|
||||
{ value: 'windowLevelPreset1', title: 'Soft tissue', subtitle: '400 / 40' },
|
||||
{ value: 'windowLevelPreset2', title: 'Lung', subtitle: '1500 / -600' },
|
||||
{ value: 'windowLevelPreset3', title: 'Liver', subtitle: '150 / 90' },
|
||||
{ value: 'windowLevelPreset4', title: 'Bone', subtitle: '80 / 40' },
|
||||
{ value: 'windowLevelPreset5', title: 'Brain', subtitle: '2500 / 480' },
|
||||
],
|
||||
renderer: ({ title, subtitle, isActive, index }) => (
|
||||
<>
|
||||
<div>
|
||||
<span className={classnames(isActive ? "text-black" : "text-white", "mr-2 text-base")}>
|
||||
{title}
|
||||
</span>
|
||||
<span className={classnames(isActive ? "text-black" : "text-aqua-pale", "font-thin text-sm")}>
|
||||
{subtitle}
|
||||
</span>
|
||||
</div>
|
||||
<span className={classnames(isActive ? "text-black" : "text-primary-active", "text-sm")}>{index + 1}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@ -62,7 +62,7 @@ export default class ToolBarService {
|
||||
this._broadcastChange(this.EVENTS.TOOL_BAR_MODIFIED, {});
|
||||
}
|
||||
|
||||
getButtonSection(key) {
|
||||
getButtonSection(key, props) {
|
||||
const buttonSectionIds = this.buttonSections[key];
|
||||
const buttonsInSection = [];
|
||||
|
||||
@ -79,7 +79,7 @@ export default class ToolBarService {
|
||||
|
||||
btnIds.forEach(nestedBtnId => {
|
||||
const nestedBtn = this.buttons[nestedBtnId];
|
||||
const mappedNestedBtn = this._mapButtonToDisplay(nestedBtn, key);
|
||||
const mappedNestedBtn = this._mapButtonToDisplay(nestedBtn, key, props);
|
||||
|
||||
nestedButtons.push(mappedNestedBtn);
|
||||
});
|
||||
@ -90,7 +90,7 @@ export default class ToolBarService {
|
||||
} else {
|
||||
const btnId = btnIdOrArray;
|
||||
const btn = this.buttons[btnId];
|
||||
const mappedBtn = this._mapButtonToDisplay(btn, key);
|
||||
const mappedBtn = this._mapButtonToDisplay(btn, key, props);
|
||||
|
||||
buttonsInSection.push(mappedBtn);
|
||||
}
|
||||
@ -136,8 +136,8 @@ export default class ToolBarService {
|
||||
* @param {*} btn
|
||||
* @param {*} btnSection
|
||||
*/
|
||||
_mapButtonToDisplay(btn, btnSection) {
|
||||
const { id, type, component, props } = btn;
|
||||
_mapButtonToDisplay(btn, btnSection, props) {
|
||||
const { id, type, component } = btn;
|
||||
const buttonType = this._buttonTypes()[type];
|
||||
|
||||
if (!buttonType) {
|
||||
@ -154,12 +154,15 @@ export default class ToolBarService {
|
||||
if (btn.props.clickHandler) {
|
||||
btn.clickHandler(evt, btn, btnSection);
|
||||
}
|
||||
if (props && props.onClick) {
|
||||
props.onClick(evt, btn, btnSection, props);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
id,
|
||||
Component: component || buttonType.defaultComponent,
|
||||
componentProps: Object.assign({}, props, { onClick }), //
|
||||
componentProps: Object.assign({}, btn.props, props, { onClick }), //
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,6 +32,8 @@ export {
|
||||
DateRange,
|
||||
Dialog,
|
||||
EmptyStudies,
|
||||
ExpandableToolbarButton,
|
||||
ListMenu,
|
||||
Icon,
|
||||
IconButton,
|
||||
Input,
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
.ExpandableToolbarButton:hover .ExpandableToolbarButton__arrow:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
border-width: 10px 10px 0;
|
||||
border-style: solid;
|
||||
border-color:#5acce6 transparent;
|
||||
}
|
||||
|
||||
.ExpandableToolbarButton .ExpandableToolbarButton__content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ExpandableToolbarButton:hover .ExpandableToolbarButton__content {
|
||||
display: block;
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { IconButton, Icon } from '@ohif/ui';
|
||||
|
||||
import './ExpandableToolbarButton.css';
|
||||
|
||||
const ExpandableToolbarButton = ({
|
||||
type,
|
||||
id,
|
||||
isActive,
|
||||
onClick,
|
||||
icon,
|
||||
className,
|
||||
content: Content,
|
||||
contentProps
|
||||
}) => {
|
||||
const classes = {
|
||||
type: {
|
||||
primary: isActive
|
||||
? 'text-black'
|
||||
: 'text-common-bright hover:bg-primary-dark hover:text-primary-light',
|
||||
secondary: isActive
|
||||
? 'text-black'
|
||||
: 'text-white hover:bg-secondary-dark focus:bg-secondary-dark',
|
||||
},
|
||||
};
|
||||
|
||||
const onChildClickHandler = (...args) => {
|
||||
onClick(...args);
|
||||
|
||||
if (contentProps.onClick) {
|
||||
contentProps.onClick(...args);
|
||||
}
|
||||
};
|
||||
|
||||
const onClickHandler = (...args) => {
|
||||
onClick(...args);
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={id} className="ExpandableToolbarButton">
|
||||
<IconButton
|
||||
variant={isActive ? 'contained' : 'text'}
|
||||
className={classnames(
|
||||
'mx-1',
|
||||
classes.type[type],
|
||||
isActive && 'ExpandableToolbarButton__arrow'
|
||||
)}
|
||||
onClick={onClickHandler}
|
||||
key={id}
|
||||
>
|
||||
<Icon name={icon} />
|
||||
</IconButton>
|
||||
<div className="absolute z-10 pt-4">
|
||||
<div className={classnames("ExpandableToolbarButton__content w-48", className)}>
|
||||
<Content {...contentProps} onClick={onChildClickHandler} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const noop = () => { };
|
||||
|
||||
ExpandableToolbarButton.defaultProps = {
|
||||
isActive: false,
|
||||
type: 'primary',
|
||||
content: null,
|
||||
onClick: noop,
|
||||
};
|
||||
|
||||
ExpandableToolbarButton.propTypes = {
|
||||
/* Influences background/hover styling */
|
||||
type: PropTypes.oneOf(['primary', 'secondary']),
|
||||
id: PropTypes.string.isRequired,
|
||||
isActive: PropTypes.bool,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
icon: PropTypes.string.isRequired,
|
||||
/** Expandable toolbar button content can be replaced for a customized content by passing a node to this value. */
|
||||
content: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
|
||||
contentProps: PropTypes.object,
|
||||
};
|
||||
|
||||
export default ExpandableToolbarButton;
|
||||
@ -0,0 +1,58 @@
|
||||
---
|
||||
name: Expandable Toolbar Button
|
||||
menu: General
|
||||
route: components/expandableToolbarButton
|
||||
---
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Playground, Props } from 'docz';
|
||||
import { ExpandableToolbarButton, ListMenu } from '@ohif/ui';
|
||||
|
||||
# Toolbar Button
|
||||
|
||||
Expandable Toolbar Buttons are used to populate the Toolbar.
|
||||
|
||||
## Import
|
||||
|
||||
```javascript
|
||||
import { ExpandableToolbarButton, ListMenu } from '@ohif/ui';
|
||||
```
|
||||
|
||||
<Playground>
|
||||
{() => {
|
||||
const props = {
|
||||
content: ListMenu,
|
||||
contentProps: {
|
||||
options: [
|
||||
{ value: 'windowLevelPreset1', title: 'Soft tissue', subtitle: '400 / 40' },
|
||||
{ value: 'windowLevelPreset2', title: 'Lung', subtitle: '1500 / -600' },
|
||||
{ value: 'windowLevelPreset3', title: 'Liver', subtitle: '150 / 90' },
|
||||
{ value: 'windowLevelPreset4', title: 'Bone', subtitle: '80 / 40' },
|
||||
{ value: 'windowLevelPreset5', title: 'Brain', subtitle: '2500 / 480' },
|
||||
],
|
||||
renderer: ({ title, subtitle, isActive, index }) => (
|
||||
<>
|
||||
<div>
|
||||
<span className={classnames(isActive ? "text-black" : "text-white", "mr-2 text-base")}>
|
||||
{title}
|
||||
</span>
|
||||
<span className={classnames(isActive ? "text-black" : "text-aqua-pale", "font-thin text-sm")}>
|
||||
{subtitle}
|
||||
</span>
|
||||
</div>
|
||||
<span className={classnames(isActive ? "text-black" : "text-primary-active", "text-sm")}>{index + 1}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="py-10 flex justify-center">
|
||||
<ExpandableToolbarButton {...props} />
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Playground>
|
||||
|
||||
## Properties
|
||||
|
||||
<Props of={ToolbarButton} />
|
||||
@ -0,0 +1,2 @@
|
||||
import ExpandableToolbarButton from './ExpandableToolbarButton';
|
||||
export default ExpandableToolbarButton;
|
||||
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
const baseClasses =
|
||||
'text-center items-center justify-center outline-none transition duration-300 ease-in-out font-bold focus:outline-none';
|
||||
'text-center items-center justify-center outline-none font-bold focus:outline-none';
|
||||
|
||||
const roundedClasses = {
|
||||
none: '',
|
||||
@ -113,7 +113,7 @@ const IconButton = ({
|
||||
};
|
||||
|
||||
IconButton.defaultProps = {
|
||||
onClick: () => {},
|
||||
onClick: () => { },
|
||||
color: 'default',
|
||||
disabled: false,
|
||||
fullWidth: false,
|
||||
|
||||
65
platform/ui/src/components/ListMenu/ListMenu.jsx
Normal file
65
platform/ui/src/components/ListMenu/ListMenu.jsx
Normal file
@ -0,0 +1,65 @@
|
||||
import React, { useState } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const ListMenu = ({ options = [], renderer, onClick }) => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(null);
|
||||
|
||||
const ListItem = (props) => {
|
||||
const flex = 'flex flex-row justify-between items-center';
|
||||
const theme = 'bg-indigo-dark';
|
||||
const hover = 'hover:bg-primary-dark';
|
||||
const spacing = 'p-3 h-8';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
flex,
|
||||
theme,
|
||||
spacing,
|
||||
'cursor-pointer',
|
||||
!props.isActive && hover,
|
||||
props.isActive && 'bg-primary-light',
|
||||
)}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
{renderer && renderer(props)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col rounded-md bg-secondary-dark pt-2 pb-2">
|
||||
{options.map((option, index) => {
|
||||
const onClickHandler = () => {
|
||||
setSelectedIndex(index);
|
||||
onClick({ ...option, index });
|
||||
};
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={`ListItem${index}`}
|
||||
{...option}
|
||||
index={index}
|
||||
isActive={selectedIndex === index}
|
||||
onClick={onClickHandler}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const noop = () => { };
|
||||
|
||||
ListMenu.propTypes = {
|
||||
options: PropTypes.array.isRequired,
|
||||
renderer: PropTypes.func.isRequired,
|
||||
onClick: PropTypes.func
|
||||
};
|
||||
|
||||
ListMenu.defaultProps = {
|
||||
onClick: noop
|
||||
};
|
||||
|
||||
export default ListMenu;
|
||||
52
platform/ui/src/components/ListMenu/ListMenu.mdx
Normal file
52
platform/ui/src/components/ListMenu/ListMenu.mdx
Normal file
@ -0,0 +1,52 @@
|
||||
---
|
||||
name: List Menu
|
||||
menu: General
|
||||
route: components/listMenu
|
||||
---
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Playground, Props } from 'docz';
|
||||
import { ListMenu } from '@ohif/ui';
|
||||
|
||||
# List Menu
|
||||
|
||||
List Menus are used to populate expandable Toolbar.
|
||||
|
||||
## Import
|
||||
|
||||
```javascript
|
||||
import { ListMenu } from '@ohif/ui';
|
||||
```
|
||||
|
||||
<Playground>
|
||||
{() => {
|
||||
return (
|
||||
<ToolbarButton options={[
|
||||
{ value: 'windowLevelPreset1', title: 'Soft tissue', subtitle: '400 / 40' },
|
||||
{ value: 'windowLevelPreset2', title: 'Lung', subtitle: '1500 / -600' },
|
||||
{ value: 'windowLevelPreset3', title: 'Liver', subtitle: '150 / 90' },
|
||||
{ value: 'windowLevelPreset4', title: 'Bone', subtitle: '80 / 40' },
|
||||
{ value: 'windowLevelPreset5', title: 'Brain', subtitle: '2500 / 480' },
|
||||
]}
|
||||
renderer={
|
||||
({ title, subtitle, isActive, index }) => (
|
||||
<>
|
||||
<div>
|
||||
<span className={classnames(isActive ? "text-black" : "text-white", "mr-2 text-base")}>
|
||||
{title}
|
||||
</span>
|
||||
<span className={classnames(isActive ? "text-black" : "text-aqua-pale", "font-thin text-sm")}>
|
||||
{subtitle}
|
||||
</span>
|
||||
</div>
|
||||
<span className={classnames(isActive ? "text-black" : "text-primary-active", "text-sm")}>{index + 1}</span>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Playground>
|
||||
|
||||
## Properties
|
||||
|
||||
<Props of={ToolbarButton} />
|
||||
2
platform/ui/src/components/ListMenu/index.js
Normal file
2
platform/ui/src/components/ListMenu/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
import ListMenu from './ListMenu';
|
||||
export default ListMenu;
|
||||
@ -76,7 +76,7 @@ const Select = ({
|
||||
options={options}
|
||||
value={selectedOptions}
|
||||
onChange={(selectedOptions, { action }) => {
|
||||
const newSelection = selectedOptions.reduce(
|
||||
const newSelection = !selectedOptions.length ? selectedOptions : selectedOptions.reduce(
|
||||
(acc, curr) => acc.concat([curr.value]),
|
||||
[]
|
||||
);
|
||||
|
||||
@ -24,7 +24,7 @@ const arrowPositionStyle = {
|
||||
},
|
||||
};
|
||||
|
||||
const Tooltip = ({ content, isSticky, position, tight, children }) => {
|
||||
const Tooltip = ({ content, isSticky, position, tight, children, isDisabled }) => {
|
||||
const [isActive, setIsActive] = useState(false);
|
||||
|
||||
const handleMouseOver = () => {
|
||||
@ -39,7 +39,7 @@ const Tooltip = ({ content, isSticky, position, tight, children }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const isOpen = isSticky || isActive;
|
||||
const isOpen = (isSticky || isActive) && !isDisabled;
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -84,10 +84,12 @@ Tooltip.defaultProps = {
|
||||
tight: false,
|
||||
isSticky: false,
|
||||
position: 'bottom',
|
||||
isDisabled: false
|
||||
};
|
||||
|
||||
Tooltip.propTypes = {
|
||||
// Allow null
|
||||
isDisabled: PropTypes.bool,
|
||||
content: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
|
||||
position: PropTypes.oneOf([
|
||||
'bottom',
|
||||
|
||||
@ -39,6 +39,8 @@ import ThumbnailNoImage from './ThumbnailNoImage';
|
||||
import ThumbnailTracked from './ThumbnailTracked';
|
||||
import ThumbnailList from './ThumbnailList';
|
||||
import ToolbarButton from './ToolbarButton';
|
||||
import ExpandableToolbarButton from './ExpandableToolbarButton';
|
||||
import ListMenu from './ListMenu';
|
||||
import Tooltip from './Tooltip';
|
||||
import Typography from './Typography';
|
||||
import Viewport from './Viewport';
|
||||
@ -52,6 +54,8 @@ export {
|
||||
DateRange,
|
||||
Dialog,
|
||||
EmptyStudies,
|
||||
ExpandableToolbarButton,
|
||||
ListMenu,
|
||||
Icon,
|
||||
IconButton,
|
||||
Input,
|
||||
|
||||
@ -16,6 +16,13 @@ module.exports = {
|
||||
initial: 'initial',
|
||||
inherit: 'inherit',
|
||||
|
||||
indigo: {
|
||||
dark: '#0b1a42'
|
||||
},
|
||||
aqua: {
|
||||
pale: '#7bb2ce'
|
||||
},
|
||||
|
||||
primary: {
|
||||
light: '#5acce6',
|
||||
main: '#0944b3',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user