2020-08-19 21:49:00 +02:00
|
|
|
import React, { useState } from 'react';
|
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
|
import classNames from 'classnames';
|
|
|
|
|
import OutsideClickHandler from 'react-outside-click-handler';
|
|
|
|
|
|
|
|
|
|
import { Icon, Tooltip, ListMenu } from '@ohif/ui';
|
|
|
|
|
|
|
|
|
|
const baseClasses = {
|
2020-09-15 03:18:46 +02:00
|
|
|
Button:
|
2020-09-21 10:42:53 +02:00
|
|
|
'flex items-center rounded-md border-transparent border-2 cursor-pointer',
|
2020-09-15 03:18:46 +02:00
|
|
|
Primary:
|
|
|
|
|
'h-full flex flex-1 items-center rounded-md rounded-tr-none rounded-br-none',
|
|
|
|
|
Secondary:
|
|
|
|
|
'h-full flex items-center justify-center rounded-tr-md rounded-br-md w-4',
|
2020-08-19 21:49:00 +02:00
|
|
|
PrimaryIcon: 'w-5 h-5',
|
|
|
|
|
SecondaryIcon: 'w-4 h-full stroke-1',
|
|
|
|
|
Separator: 'border-l pt-2 pb-2',
|
2020-09-21 10:42:53 +02:00
|
|
|
Content: 'absolute z-10 top-0 mt-12',
|
2020-08-19 21:49:00 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const classes = {
|
2020-09-15 03:18:46 +02:00
|
|
|
Button: ({ isExpanded, primary }) =>
|
|
|
|
|
classNames(
|
|
|
|
|
baseClasses.Button,
|
|
|
|
|
!isExpanded &&
|
|
|
|
|
!primary.isActive &&
|
|
|
|
|
'hover:bg-primary-dark hover:border-primary-dark'
|
|
|
|
|
),
|
2020-08-19 21:49:00 +02:00
|
|
|
Interface: 'h-full flex flex-row items-center',
|
2020-09-15 03:18:46 +02:00
|
|
|
Primary: ({ primary, isExpanded }) =>
|
|
|
|
|
classNames(
|
|
|
|
|
baseClasses.Primary,
|
|
|
|
|
primary.isActive && !isExpanded
|
2021-03-01 16:52:54 +01:00
|
|
|
? 'bg-primary-light rounded-tr-md rounded-br-md active'
|
2020-09-15 03:18:46 +02:00
|
|
|
: isExpanded
|
|
|
|
|
? 'bg-primary-dark'
|
|
|
|
|
: 'bg-secondary-dark hover:bg-primary-dark'
|
|
|
|
|
),
|
|
|
|
|
Secondary: ({ isExpanded, primary }) =>
|
|
|
|
|
classNames(
|
|
|
|
|
baseClasses.Secondary,
|
|
|
|
|
isExpanded
|
|
|
|
|
? 'bg-primary-light rounded-tr-md rounded-br-md'
|
|
|
|
|
: primary.isActive
|
|
|
|
|
? 'bg-secondary-dark'
|
|
|
|
|
: 'hover:bg-primary-dark bg-secondary-dark'
|
|
|
|
|
),
|
|
|
|
|
PrimaryIcon: ({ primary, isExpanded }) =>
|
|
|
|
|
classNames(
|
|
|
|
|
baseClasses.PrimaryIcon,
|
|
|
|
|
primary.isActive && !isExpanded
|
|
|
|
|
? 'text-primary-dark'
|
|
|
|
|
: 'text-common-bright'
|
|
|
|
|
),
|
|
|
|
|
SecondaryIcon: ({ isExpanded }) =>
|
|
|
|
|
classNames(
|
|
|
|
|
baseClasses.SecondaryIcon,
|
|
|
|
|
isExpanded
|
|
|
|
|
? 'text-primary-dark'
|
|
|
|
|
: 'text-primary-active hover:text-common-bright'
|
|
|
|
|
),
|
|
|
|
|
Separator: ({ primary, isExpanded, isHovering }) =>
|
|
|
|
|
classNames(
|
|
|
|
|
baseClasses.Separator,
|
|
|
|
|
isHovering || isExpanded || primary.isActive
|
|
|
|
|
? 'border-transparent'
|
|
|
|
|
: 'border-primary-active'
|
|
|
|
|
),
|
|
|
|
|
Content: ({ isExpanded }) =>
|
|
|
|
|
classNames(baseClasses.Content, isExpanded ? 'block' : 'hidden'),
|
2020-08-19 21:49:00 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const SplitButton = ({
|
|
|
|
|
isRadio,
|
|
|
|
|
isAction,
|
2020-09-15 03:18:46 +02:00
|
|
|
//
|
|
|
|
|
bState,
|
|
|
|
|
//
|
|
|
|
|
groupId,
|
2020-08-19 21:49:00 +02:00
|
|
|
primary: _primary,
|
|
|
|
|
secondary,
|
|
|
|
|
items: _items,
|
|
|
|
|
renderer,
|
2020-09-15 03:18:46 +02:00
|
|
|
onInteraction,
|
2020-08-19 21:49:00 +02:00
|
|
|
}) => {
|
2021-03-01 16:52:54 +01:00
|
|
|
const { primaryToolId, toggles } = bState;
|
2020-08-19 21:49:00 +02:00
|
|
|
/* Bubbles up individual item clicks */
|
2020-09-15 03:18:46 +02:00
|
|
|
const getSplitButtonItems = items =>
|
|
|
|
|
items.map((item, index) => ({
|
|
|
|
|
...item,
|
|
|
|
|
index,
|
|
|
|
|
onClick: () => {
|
|
|
|
|
onInteraction({
|
|
|
|
|
groupId,
|
|
|
|
|
//
|
|
|
|
|
itemId: item.id,
|
|
|
|
|
interactionType: item.type,
|
|
|
|
|
// splitButtonId? (so we can track group?)
|
|
|
|
|
// info to fire item's command/event?
|
|
|
|
|
commandName: item.commandName,
|
|
|
|
|
commandOptions: item.commandOptions,
|
|
|
|
|
});
|
2020-08-19 21:49:00 +02:00
|
|
|
|
2020-09-15 03:18:46 +02:00
|
|
|
setState(state => ({
|
|
|
|
|
...state,
|
|
|
|
|
primary: !isAction ? { ...item, index } : state.primary,
|
|
|
|
|
isExpanded: false,
|
|
|
|
|
items: getSplitButtonItems(_items).filter(item =>
|
|
|
|
|
isRadio && !isAction ? item.index !== index : true
|
|
|
|
|
),
|
|
|
|
|
}));
|
|
|
|
|
},
|
|
|
|
|
}));
|
2020-08-19 21:49:00 +02:00
|
|
|
|
|
|
|
|
const [state, setState] = useState({
|
|
|
|
|
primary: _primary,
|
2020-09-15 03:18:46 +02:00
|
|
|
items: getSplitButtonItems(_items).filter(item =>
|
|
|
|
|
isRadio && !isAction ? item.id !== _primary.id : true
|
|
|
|
|
),
|
2020-08-19 21:49:00 +02:00
|
|
|
isHovering: false,
|
2020-09-15 03:18:46 +02:00
|
|
|
isExpanded: false,
|
2020-08-19 21:49:00 +02:00
|
|
|
});
|
|
|
|
|
|
2020-09-15 03:18:46 +02:00
|
|
|
const onSecondaryClickHandler = () =>
|
|
|
|
|
setState(state => ({ ...state, isExpanded: !state.isExpanded }));
|
|
|
|
|
const onMouseEnterHandler = () =>
|
|
|
|
|
setState(state => ({ ...state, isHovering: true }));
|
|
|
|
|
const onMouseLeaveHandler = () =>
|
|
|
|
|
setState(state => ({ ...state, isHovering: false }));
|
|
|
|
|
const outsideClickHandler = () =>
|
|
|
|
|
setState(state => ({ ...state, isExpanded: false }));
|
2020-08-19 21:49:00 +02:00
|
|
|
const onPrimaryClickHandler = () => {
|
2020-09-15 03:18:46 +02:00
|
|
|
onInteraction({
|
|
|
|
|
groupId,
|
|
|
|
|
itemId: state.primary.id,
|
|
|
|
|
interactionType: state.primary.type,
|
|
|
|
|
// splitButtonId? (so we can track group?)
|
|
|
|
|
// info to fire item's command/event?
|
|
|
|
|
//
|
|
|
|
|
commandName: state.primary.commandName,
|
|
|
|
|
commandOptions: state.primary.commandOptions,
|
|
|
|
|
});
|
2020-08-19 21:49:00 +02:00
|
|
|
};
|
|
|
|
|
|
2020-09-15 03:18:46 +02:00
|
|
|
const isPrimaryActive =
|
|
|
|
|
(state.primary.type === 'tool' && primaryToolId === state.primary.id) ||
|
|
|
|
|
(state.primary.type === 'toggle' && toggles[state.primary.id] === true);
|
|
|
|
|
|
2020-08-19 21:49:00 +02:00
|
|
|
return (
|
|
|
|
|
<OutsideClickHandler onOutsideClick={outsideClickHandler}>
|
2020-09-15 03:18:46 +02:00
|
|
|
<div name="SplitButton" className="relative">
|
2020-08-19 21:49:00 +02:00
|
|
|
<div
|
2020-09-15 03:18:46 +02:00
|
|
|
className={classes.Button({
|
|
|
|
|
...state,
|
|
|
|
|
primary: { isActive: isPrimaryActive },
|
|
|
|
|
})}
|
2020-09-21 10:42:53 +02:00
|
|
|
style={{ height: '40px' }}
|
2020-08-19 21:49:00 +02:00
|
|
|
onMouseEnter={onMouseEnterHandler}
|
|
|
|
|
onMouseLeave={onMouseLeaveHandler}
|
|
|
|
|
>
|
|
|
|
|
<div className={classes.Interface}>
|
2020-09-15 03:18:46 +02:00
|
|
|
<div
|
|
|
|
|
onClick={onPrimaryClickHandler}
|
|
|
|
|
className={classes.Primary({
|
|
|
|
|
...state,
|
|
|
|
|
primary: { isActive: isPrimaryActive },
|
|
|
|
|
})}
|
2021-03-01 16:52:54 +01:00
|
|
|
data-tool={state.primary.id}
|
|
|
|
|
data-cy={`${groupId}-split-button-primary`}
|
2020-09-15 03:18:46 +02:00
|
|
|
>
|
|
|
|
|
<Tooltip
|
|
|
|
|
isDisabled={!state.primary.tooltip}
|
|
|
|
|
content={state.primary.tooltip}
|
|
|
|
|
>
|
2020-09-21 10:42:53 +02:00
|
|
|
<div
|
|
|
|
|
className="flex items-center justify-center w-full h-full"
|
|
|
|
|
style={{ padding: '10px' }}
|
|
|
|
|
>
|
2020-09-15 03:18:46 +02:00
|
|
|
<Icon
|
|
|
|
|
name={state.primary.icon}
|
|
|
|
|
className={classes.PrimaryIcon({
|
|
|
|
|
...state,
|
|
|
|
|
primary: { isActive: isPrimaryActive },
|
|
|
|
|
})}
|
|
|
|
|
/>
|
2020-08-19 21:49:00 +02:00
|
|
|
</div>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</div>
|
2020-09-15 03:18:46 +02:00
|
|
|
<div
|
|
|
|
|
className={classes.Separator({
|
|
|
|
|
...state,
|
|
|
|
|
primary: { isActive: isPrimaryActive },
|
|
|
|
|
})}
|
|
|
|
|
></div>
|
|
|
|
|
<div
|
|
|
|
|
className={classes.Secondary({
|
|
|
|
|
...state,
|
|
|
|
|
primary: { isActive: isPrimaryActive },
|
|
|
|
|
})}
|
|
|
|
|
onClick={onSecondaryClickHandler}
|
2021-03-01 16:52:54 +01:00
|
|
|
data-cy={`${groupId}-split-button-secondary`}
|
2020-09-15 03:18:46 +02:00
|
|
|
>
|
2020-08-19 21:49:00 +02:00
|
|
|
<Tooltip
|
|
|
|
|
isDisabled={state.isExpanded || !secondary.tooltip}
|
|
|
|
|
content={secondary.tooltip}
|
|
|
|
|
className="h-full"
|
|
|
|
|
>
|
2020-09-15 03:18:46 +02:00
|
|
|
<Icon
|
|
|
|
|
name={secondary.icon}
|
|
|
|
|
className={classes.SecondaryIcon({
|
|
|
|
|
...state,
|
|
|
|
|
primary: { isActive: isPrimaryActive },
|
|
|
|
|
})}
|
|
|
|
|
/>
|
2020-08-19 21:49:00 +02:00
|
|
|
</Tooltip>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2020-09-15 03:18:46 +02:00
|
|
|
{/* EXPANDED LIST OF OPTIONS */}
|
2021-03-01 16:52:54 +01:00
|
|
|
<div
|
|
|
|
|
className={classes.Content({ ...state })}
|
|
|
|
|
data-cy={`${groupId}-list-menu`}
|
|
|
|
|
>
|
2020-08-19 21:49:00 +02:00
|
|
|
<ListMenu items={state.items} renderer={renderer} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</OutsideClickHandler>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const DefaultListItemRenderer = ({ icon, label, isActive }) => (
|
2020-09-15 03:18:46 +02:00
|
|
|
<div
|
|
|
|
|
className={classNames(
|
|
|
|
|
'flex flex-row items-center p-3 h-8 w-full hover:bg-primary-dark',
|
|
|
|
|
isActive && 'bg-primary-dark'
|
|
|
|
|
)}
|
2020-08-19 21:49:00 +02:00
|
|
|
>
|
2020-09-21 10:42:53 +02:00
|
|
|
<span className="mr-4 text-base whitespace-pre text-common-bright">
|
2020-09-15 03:18:46 +02:00
|
|
|
<Icon name={icon} className="w-5 h-5 text-common-bright" />
|
2020-08-19 21:49:00 +02:00
|
|
|
</span>
|
2020-09-21 10:42:53 +02:00
|
|
|
<span className="mr-5 text-base whitespace-pre text-common-bright">
|
|
|
|
|
{label}
|
|
|
|
|
</span>
|
2020-09-15 03:18:46 +02:00
|
|
|
</div>
|
2020-08-19 21:49:00 +02:00
|
|
|
);
|
|
|
|
|
|
2020-09-15 03:18:46 +02:00
|
|
|
const noop = () => {};
|
2020-08-19 21:49:00 +02:00
|
|
|
|
|
|
|
|
SplitButton.defaultProps = {
|
|
|
|
|
isRadio: false,
|
|
|
|
|
isAction: false,
|
|
|
|
|
primary: {
|
|
|
|
|
label: null,
|
|
|
|
|
tooltip: null,
|
|
|
|
|
},
|
|
|
|
|
secondary: {
|
|
|
|
|
icon: 'chevron-down',
|
|
|
|
|
label: null,
|
|
|
|
|
isActive: true,
|
2020-09-15 03:18:46 +02:00
|
|
|
tooltip: 'More Measure Tools',
|
2020-08-19 21:49:00 +02:00
|
|
|
},
|
|
|
|
|
items: [],
|
|
|
|
|
renderer: DefaultListItemRenderer,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SplitButton.propTypes = {
|
|
|
|
|
primary: PropTypes.shape({
|
2020-09-15 03:18:46 +02:00
|
|
|
id: PropTypes.string.isRequired,
|
2020-08-19 21:49:00 +02:00
|
|
|
icon: PropTypes.string,
|
|
|
|
|
label: PropTypes.string,
|
2020-09-15 03:18:46 +02:00
|
|
|
type: PropTypes.oneOf(['tool', 'action', 'toggle']).isRequired,
|
2020-08-19 21:49:00 +02:00
|
|
|
tooltip: PropTypes.string,
|
|
|
|
|
}),
|
|
|
|
|
secondary: PropTypes.shape({
|
|
|
|
|
id: PropTypes.string,
|
|
|
|
|
icon: PropTypes.string,
|
|
|
|
|
label: PropTypes.string,
|
|
|
|
|
tooltip: PropTypes.string,
|
2020-09-15 03:18:46 +02:00
|
|
|
isActive: PropTypes.bool,
|
2020-08-19 21:49:00 +02:00
|
|
|
}),
|
|
|
|
|
renderer: PropTypes.func,
|
|
|
|
|
items: PropTypes.arrayOf(
|
|
|
|
|
PropTypes.shape({
|
|
|
|
|
id: PropTypes.string.isRequired,
|
|
|
|
|
icon: PropTypes.string,
|
|
|
|
|
label: PropTypes.string,
|
2020-09-15 03:18:46 +02:00
|
|
|
type: PropTypes.oneOf(['tool', 'action', 'toggle']).isRequired,
|
2020-08-19 21:49:00 +02:00
|
|
|
tooltip: PropTypes.string,
|
|
|
|
|
isActive: PropTypes.bool,
|
|
|
|
|
})
|
2020-09-15 03:18:46 +02:00
|
|
|
),
|
|
|
|
|
/** Callback function to inform ToolbarService of important events */
|
|
|
|
|
onInteraction: PropTypes.func.isRequired,
|
2020-08-19 21:49:00 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default SplitButton;
|