Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui(components): Adds ToolButton and ToolButtonList to ui-next #4670

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
10 changes: 10 additions & 0 deletions extensions/default/src/getToolbarModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import ToolbarLayoutSelectorWithServices from './Toolbar/ToolbarLayoutSelector';
import ToolbarSplitButtonWithServices from './Toolbar/ToolbarSplitButtonWithServices';
import ToolbarButtonGroupWithServices from './Toolbar/ToolbarButtonGroupWithServices';
import { ToolbarButton } from '@ohif/ui';
import { ToolButton } from '@ohif/ui-next';
import { ToolButtonList } from '@ohif/ui-next';
import { ProgressDropdownWithService } from './Components/ProgressDropdownWithService';

const getClassName = isToggled => {
Expand All @@ -20,6 +22,14 @@ export default function getToolbarModule({ commandsManager, servicesManager }: w
name: 'ohif.radioGroup',
defaultComponent: ToolbarButton,
},
{
name: 'ohif.toolButton',
defaultComponent: ToolButton,
},
{
name: 'ohif.toolButtonList',
defaultComponent: ToolButtonList,
},
{
name: 'ohif.divider',
defaultComponent: ToolbarDivider,
Expand Down
14 changes: 7 additions & 7 deletions modes/longitudinal/src/toolbarButtons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const setToolActiveToolbar = {
const toolbarButtons: Button[] = [
{
id: 'MeasurementTools',
uiType: 'ohif.splitButton',
uiType: 'ohif.toolButtonList',
props: {
groupId: 'MeasurementTools',
// group evaluate to determine which item should move to the top
Expand Down Expand Up @@ -110,7 +110,7 @@ const toolbarButtons: Button[] = [
},
{
id: 'Zoom',
uiType: 'ohif.radioGroup',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-zoom',
label: 'Zoom',
Expand All @@ -121,7 +121,7 @@ const toolbarButtons: Button[] = [
// Window Level
{
id: 'WindowLevel',
uiType: 'ohif.radioGroup',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-window-level',
label: 'Window Level',
Expand All @@ -138,7 +138,7 @@ const toolbarButtons: Button[] = [
// Pan...
{
id: 'Pan',
uiType: 'ohif.radioGroup',
uiType: 'ohif.toolButton',
props: {
type: 'tool',
icon: 'tool-move',
Expand All @@ -149,7 +149,7 @@ const toolbarButtons: Button[] = [
},
{
id: 'TrackballRotate',
uiType: 'ohif.radioGroup',
uiType: 'ohif.toolButton',
props: {
type: 'tool',
icon: 'tool-3d-rotate',
Expand All @@ -163,7 +163,7 @@ const toolbarButtons: Button[] = [
},
{
id: 'Capture',
uiType: 'ohif.radioGroup',
uiType: 'ohif.toolButton',
props: {
icon: 'tool-capture',
label: 'Capture',
Expand All @@ -188,7 +188,7 @@ const toolbarButtons: Button[] = [
},
{
id: 'Crosshairs',
uiType: 'ohif.radioGroup',
uiType: 'ohif.toolButton',
props: {
type: 'tool',
icon: 'tool-crosshair',
Expand Down
48 changes: 48 additions & 0 deletions platform/docs/src/pages/components-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import {
CardDescription,
CardContent,
} from '../../../ui-next/src/components/Card';
import ToolButton from '../../../ui-next/src/components/ToolButton/ToolButton';
import ToolButtonList from '../../../ui-next/src/components/ToolButton/ToolButtonList';

interface ShowcaseRowProps {
title: string;
Expand Down Expand Up @@ -580,6 +582,52 @@ Example code coming soon.
{/* Render the Toaster component */}
<Toaster />
</ShowcaseRow>

<ShowcaseRow
title="ToolButton"
description="Used to activate tools or perform single actions"
code={`
// Example usage:
<ToolButton
id="Zoom"
icon="zoom" // must exist in your Icons or fallback to 'MissingIcon'
label="Zoom"
tooltip="Zoom Tool"
isActive={false}
onInteraction={({ itemId }) => console.log(\`Clicked \${itemId}\`)}
/>
`}
>
<div className="bg-popover flex h-11 w-[450px] items-center justify-center rounded">
<ToolButton
id="Zoom"
icon="ToolZoom"
label="Zoom"
tooltip="Zoom"
isActive={false}
className="bg-primary-dark text-primary-light hover:bg-background hover:text-highlight"
onInteraction={({ itemId }) => console.log(`Clicked ${itemId}`)}
/>
<ToolButton
id="Zoom"
icon="ToolMove"
label="Pan"
tooltip="Pan"
isActive={false}
className="bg-primary-dark text-primary-light hover:bg-background hover:text-highlight"
onInteraction={({ itemId }) => console.log(`Clicked ${itemId}`)}
/>
<ToolButton
id="Zoom"
icon="ToolWindowLevel"
label="Window Level"
tooltip="Window Level"
isActive={false}
className="bg-primary-dark text-primary-light hover:bg-background hover:text-highlight"
onInteraction={({ itemId }) => console.log(`Clicked ${itemId}`)}
/>
</div>
</ShowcaseRow>
</div>
</div>
</Layout>
Expand Down
70 changes: 70 additions & 0 deletions platform/ui-next/src/components/ToolButton/ToolButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from 'react';
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '../Tooltip';
import { Icons } from '../Icons';
import { Button } from '../Button';
import { cn } from '../../lib/utils';

interface ToolButtonProps {
id: string;
icon?: string;
label?: string;
tooltip?: string;
isActive?: boolean;
commands?: any;
onInteraction?: (details: { itemId: string; commands?: any }) => void;
className?: string;
}

function ToolButton(props: ToolButtonProps) {
const {
id,
icon = 'MissingIcon',
label,
tooltip,
isActive = false,
commands,
onInteraction,
className,
} = props;

const IconComponent = Icons[icon] || Icons['MissingIcon'];

const baseClasses = 'w-10 h-10 !rounded-lg inline-flex items-center justify-center';
const defaultClasses = 'bg-transparent text-foreground hover:bg-background hover:text-highlight';
const activeClasses = 'bg-highlight text-background hover:!bg-highlight/80';
const appliedClasses = isActive
? cn(baseClasses, activeClasses, className)
: cn(baseClasses, defaultClasses, className);

// Click: pass along to parent
const handleClick = () => {
onInteraction?.({ itemId: id, commands });
};

// Decide what text to show in the tooltip; prefer `tooltip` if available. If not, use `label`.
const tooltipText = tooltip || label;

return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Button
className={appliedClasses}
onClick={handleClick}
variant="ghost"
size="icon"
aria-pressed={isActive}
aria-label={tooltipText || id}
>
<IconComponent className="h-7 w-7" />
</Button>
</TooltipTrigger>

{/* Only render tooltip if we have text to display */}
{tooltipText && <TooltipContent side="bottom">{tooltipText}</TooltipContent>}
</Tooltip>
</TooltipProvider>
);
}

export default ToolButton;
120 changes: 120 additions & 0 deletions platform/ui-next/src/components/ToolButton/ToolButtonList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { Button } from '../Button';
import { Icons } from '../Icons';
import ToolButton from './ToolButton';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
} from '../DropdownMenu';
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '../Tooltip';
import { cn } from '../../lib/utils';

function ToolButtonList({ groupId, primary, items = [], onInteraction, servicesManager }) {
const { toolbarService } = servicesManager?.services || {};

// Handle clicking on a secondary tool from the dropdown.
const handleItemClick = useCallback(
item => {
onInteraction?.({
groupId,
itemId: item.id,
commands: item.commands,
});
},
[groupId, onInteraction]
);

const isActive = !!primary?.isActive;

return (
<div className="flex items-center space-x-1">
{/**
* Note: pass along the props that ToolButton needs (including `id`, `icon`,
* `label`, `tooltip`, `isActive`, `commands`, and a callback for `onInteraction`).
*/}
<ToolButton
id={primary?.id}
icon={primary?.icon}
label={primary?.label}
tooltip={primary?.tooltip}
isActive={isActive}
commands={primary?.commands}
onInteraction={({ itemId, commands }) => {
onInteraction?.({
groupId,
itemId,
commands,
});
}}
/>

<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="text-primary-foreground hover:bg-primary-dark hover:text-primary-light border-primary inline-flex h-10 w-5 items-center justify-center !rounded-tr-lg !rounded-br-lg !rounded-tl-none !rounded-bl-none border-l bg-transparent"
aria-label="Open tool list"
>
<Icons.ByName
name="chevron-down"
className="text-primary h-5 w-5"
/>
</Button>
</DropdownMenuTrigger>

<DropdownMenuContent
side="bottom"
align="end"
>
{items.map(item => (
<DropdownMenuItem
key={item.id}
onSelect={() => handleItemClick(item)}
className="flex items-center space-x-2"
>
<Icons.ByName
name={item.icon || 'MissingIcon'}
className="h-5 w-5"
/>
<span>{item.tooltip || item.label || item.id}</span>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}

ToolButtonList.propTypes = {
groupId: PropTypes.string,
primary: PropTypes.shape({
id: PropTypes.string.isRequired,
icon: PropTypes.string,
label: PropTypes.string,
tooltip: PropTypes.string,
isActive: PropTypes.bool,
commands: PropTypes.any,
}),
items: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
icon: PropTypes.string,
label: PropTypes.string,
tooltip: PropTypes.string,
isActive: PropTypes.bool,
commands: PropTypes.any,
})
),
onInteraction: PropTypes.func,
servicesManager: PropTypes.shape({
services: PropTypes.shape({
toolbarService: PropTypes.object,
}),
}),
};

export default ToolButtonList;
2 changes: 2 additions & 0 deletions platform/ui-next/src/components/ToolButton/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as ToolButton } from './ToolButton';
export { default as ToolButtonList } from './ToolButtonList';
4 changes: 4 additions & 0 deletions platform/ui-next/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ import {
ViewportOverlay,
ViewportGrid,
} from './Viewport';
import { ToolButton } from './ToolButton';
import { ToolButtonList } from './ToolButton';

export {
ErrorBoundary,
Expand Down Expand Up @@ -204,4 +206,6 @@ export {
ViewportOverlay,
ViewportGrid,
Clipboard,
ToolButton,
ToolButtonList,
};
4 changes: 4 additions & 0 deletions platform/ui-next/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ import {
ViewportActionCornersLocations,
ViewportOverlay,
ViewportGrid,
ToolButton,
ToolButtonList,
} from './components';
import { DataRow } from './components/DataRow';

Expand Down Expand Up @@ -196,4 +198,6 @@ export {
ViewportActionCornersLocations,
ViewportOverlay,
ViewportGrid,
ToolButton,
ToolButtonList,
};
Loading