diff --git a/extensions/default/src/ViewerLayout/index.jsx b/extensions/default/src/ViewerLayout/index.jsx
index 730ee515b..941a5487a 100644
--- a/extensions/default/src/ViewerLayout/index.jsx
+++ b/extensions/default/src/ViewerLayout/index.jsx
@@ -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);
diff --git a/modes/longitudinal/src/toolbarButtons.js b/modes/longitudinal/src/toolbarButtons.js
index 3f4db8160..6fc5634ea 100644
--- a/modes/longitudinal/src/toolbarButtons.js
+++ b/modes/longitudinal/src/toolbarButtons.js
@@ -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 }) => (
+ <>
+
+
+ {title}
+
+
+ {subtitle}
+
+
+ {index + 1}
+ >
+ )
+ }
},
},
{
diff --git a/platform/core/src/services/ToolBarService/ToolBarService.js b/platform/core/src/services/ToolBarService/ToolBarService.js
index 95cc1b431..589839448 100644
--- a/platform/core/src/services/ToolBarService/ToolBarService.js
+++ b/platform/core/src/services/ToolBarService/ToolBarService.js
@@ -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 }), //
};
}
}
diff --git a/platform/ui/index.js b/platform/ui/index.js
index db85039d9..2025cfb3b 100644
--- a/platform/ui/index.js
+++ b/platform/ui/index.js
@@ -32,6 +32,8 @@ export {
DateRange,
Dialog,
EmptyStudies,
+ ExpandableToolbarButton,
+ ListMenu,
Icon,
IconButton,
Input,
diff --git a/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.css b/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.css
new file mode 100644
index 000000000..3a5a6c8be
--- /dev/null
+++ b/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.css
@@ -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;
+}
diff --git a/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.jsx b/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.jsx
new file mode 100644
index 000000000..b343d9ed7
--- /dev/null
+++ b/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.jsx
@@ -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 (
+
+ );
+};
+
+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;
diff --git a/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.mdx b/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.mdx
new file mode 100644
index 000000000..b6867f370
--- /dev/null
+++ b/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.mdx
@@ -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';
+```
+
+
+ {() => {
+ 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 }) => (
+ <>
+
+
+ {title}
+
+
+ {subtitle}
+
+
+ {index + 1}
+ >
+ )
+ }
+ };
+ return (
+
+
+
+ );
+ }}
+
+
+## Properties
+
+
diff --git a/platform/ui/src/components/ExpandableToolbarButton/index.js b/platform/ui/src/components/ExpandableToolbarButton/index.js
new file mode 100644
index 000000000..2a5e6032c
--- /dev/null
+++ b/platform/ui/src/components/ExpandableToolbarButton/index.js
@@ -0,0 +1,2 @@
+import ExpandableToolbarButton from './ExpandableToolbarButton';
+export default ExpandableToolbarButton;
diff --git a/platform/ui/src/components/IconButton/IconButton.jsx b/platform/ui/src/components/IconButton/IconButton.jsx
index 475e61620..d92a6b9ca 100644
--- a/platform/ui/src/components/IconButton/IconButton.jsx
+++ b/platform/ui/src/components/IconButton/IconButton.jsx
@@ -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,
diff --git a/platform/ui/src/components/ListMenu/ListMenu.jsx b/platform/ui/src/components/ListMenu/ListMenu.jsx
new file mode 100644
index 000000000..56a693740
--- /dev/null
+++ b/platform/ui/src/components/ListMenu/ListMenu.jsx
@@ -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 (
+
+ {renderer && renderer(props)}
+
+ );
+ };
+
+ return (
+
+ {options.map((option, index) => {
+ const onClickHandler = () => {
+ setSelectedIndex(index);
+ onClick({ ...option, index });
+ };
+
+ return (
+
+ );
+ })}
+
+ );
+};
+
+const noop = () => { };
+
+ListMenu.propTypes = {
+ options: PropTypes.array.isRequired,
+ renderer: PropTypes.func.isRequired,
+ onClick: PropTypes.func
+};
+
+ListMenu.defaultProps = {
+ onClick: noop
+};
+
+export default ListMenu;
diff --git a/platform/ui/src/components/ListMenu/ListMenu.mdx b/platform/ui/src/components/ListMenu/ListMenu.mdx
new file mode 100644
index 000000000..195361e21
--- /dev/null
+++ b/platform/ui/src/components/ListMenu/ListMenu.mdx
@@ -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';
+```
+
+
+ {() => {
+ return (
+ (
+ <>
+
+
+ {title}
+
+
+ {subtitle}
+
+
+ {index + 1}
+ >
+ )}
+ />
+ );
+ }}
+
+
+## Properties
+
+
diff --git a/platform/ui/src/components/ListMenu/index.js b/platform/ui/src/components/ListMenu/index.js
new file mode 100644
index 000000000..b9d95c885
--- /dev/null
+++ b/platform/ui/src/components/ListMenu/index.js
@@ -0,0 +1,2 @@
+import ListMenu from './ListMenu';
+export default ListMenu;
diff --git a/platform/ui/src/components/Select/Select.jsx b/platform/ui/src/components/Select/Select.jsx
index 3a47d8a6a..399edd541 100644
--- a/platform/ui/src/components/Select/Select.jsx
+++ b/platform/ui/src/components/Select/Select.jsx
@@ -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]),
[]
);
diff --git a/platform/ui/src/components/Tooltip/Tooltip.jsx b/platform/ui/src/components/Tooltip/Tooltip.jsx
index fabfa18f6..42ef4498b 100644
--- a/platform/ui/src/components/Tooltip/Tooltip.jsx
+++ b/platform/ui/src/components/Tooltip/Tooltip.jsx
@@ -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 (