feat: 🎸 React custom component on toolbar button (#935)

* feat: 🎸 React custom component on toolbar button

Toolbar button can consume/render react custom components. Its only
required to expose the component into toolbar definitions
(toolbarModule)

Closes: 924

* feat: 🎸 Core review. Minor changes on documentation

Closes: 924

* feat: 🎸 Code review

* feat: 🎸 Code review. Changing function call to variable decl
This commit is contained in:
ladeirarodolfo 2019-09-26 14:45:19 -03:00 committed by Danny Brown
parent 5b523c2afa
commit a90605c82d
2 changed files with 95 additions and 32 deletions

View File

@ -160,6 +160,7 @@ An extension can register a Toolbar Module by providing a `getToolbarModule()`
method that returns a React Component. The component does not receive any props.
If you want to modify or react to state, you will need to connect to the redux
store.
The given toolbar must determine its set of elements and the context of them. The set of elements will be listed on toolbar `definitions`.
![Toolbar Extension](../assets/img/extensions-toolbar.gif)
@ -170,6 +171,31 @@ Toolbar components are rendered in the `ToolbarRow` component.
For a complete example implementation,
[check out the OHIFCornerstoneViewport's Toolbar Module](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone/src/toolbarModule.js).
##### Toolbar Custom Component
Toolbar elements can define its own custom react component to be consumed when rendering it. So far, it accepts `Functional` and `Class` Components. For that, you just need to expose your `CustomToolbarComponent` as the value of key `CustomComponent`. In case the property `CustomComponent` is not present, a default toolbar component will be used to render it. See bellow
```js
definitions: [
...
{
id: 'Custom',
label: 'Custom',
icon: 'custom-icon',
CustomComponent: CustomToolbarComponent,
}
...
]
```
`CustomComponent` components will receive the following props:
- parentContext: parent context. (In most of the cases it will be a ToolbarRow instance)
- toolbarClickCallback: callback method when clicking on toolbar
- button: its own definition object
- key: react key prop
- activeButtons: list of active elements
- isActive: if current
#### SopClassHandler
...

View File

@ -150,45 +150,82 @@ class ToolbarRow extends Component {
}
}
function _getCustomButtonComponent(button, activeButtons) {
const CustomComponent = button.CustomComponent;
const isValidComponent = typeof CustomComponent === 'function';
// Check if its a valid customComponent. Later on an CustomToolbarComponent interface could be implemented.
if (isValidComponent) {
const parentContext = this;
const isActive = activeButtons.includes(button.id);
return (
<CustomComponent
parentContext={parentContext}
toolbarClickCallback={_handleToolbarButtonClick.bind(this)}
button={button}
key={button.id}
activeButtons={activeButtons}
isActive={isActive}
/>
);
}
}
function _getExpandableButtonComponent(button, activeButtons) {
// Iterate over button definitions and update `onClick` behavior
let activeCommand;
const childButtons = button.buttons.map(childButton => {
childButton.onClick = _handleToolbarButtonClick.bind(this, childButton);
if (activeButtons.indexOf(childButton.id) > -1) {
activeCommand = childButton.id;
}
return childButton;
});
return (
<ExpandableToolMenu
key={button.id}
label={button.label}
icon={button.icon}
buttons={childButtons}
activeCommand={activeCommand}
/>
);
}
function _getDefaultButtonComponent(button, activeButtons) {
return (
<ToolbarButton
key={button.id}
label={button.label}
icon={button.icon}
onClick={_handleToolbarButtonClick.bind(this, button)}
isActive={activeButtons.includes(button.id)}
/>
);
}
/**
* Determine which extension buttons should be showing, if they're
* active, and what their onClick behavior should be.
*/
function _getButtonComponents(toolbarButtons, activeButtons) {
return toolbarButtons.map((button, index) => {
let activeCommand = undefined;
const _this = this;
return toolbarButtons.map(button => {
const hasCustomComponent = button.CustomComponent;
const hasNestedButtonDefinitions = button.buttons && button.buttons.length;
if (button.buttons && button.buttons.length) {
// Iterate over button definitions and update `onClick` behavior
const childButtons = button.buttons.map(childButton => {
childButton.onClick = _handleToolbarButtonClick.bind(this, childButton);
if (activeButtons.indexOf(childButton.id) > -1) {
activeCommand = childButton.id;
}
return childButton;
});
return (
<ExpandableToolMenu
key={button.id}
label={button.label}
icon={button.icon}
buttons={childButtons}
activeCommand={activeCommand}
/>
);
if (hasCustomComponent) {
return _getCustomButtonComponent.call(_this, button, activeButtons);
}
return (
<ToolbarButton
key={button.id}
label={button.label}
icon={button.icon}
onClick={_handleToolbarButtonClick.bind(this, button)}
isActive={activeButtons.includes(button.id)}
/>
);
if (hasNestedButtonDefinitions) {
return _getExpandableButtonComponent.call(_this, button, activeButtons);
}
return _getDefaultButtonComponent.call(_this, button, activeButtons);
});
}