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): New tool components ToolToggle and ToolToggleList (WIP) #4653

Closed
wants to merge 9 commits into from
9 changes: 9 additions & 0 deletions extensions/default/src/getToolbarModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import ToolbarDivider from './Toolbar/ToolbarDivider';
import ToolbarLayoutSelectorWithServices from './Toolbar/ToolbarLayoutSelector';
import ToolbarSplitButtonWithServices from './Toolbar/ToolbarSplitButtonWithServices';
import ToolbarButtonGroupWithServices from './Toolbar/ToolbarButtonGroupWithServices';
import { ToolToggle, ToolToggleList } from '@ohif/ui-next';
import { ToolbarButton } from '@ohif/ui';
import { ProgressDropdownWithService } from './Components/ProgressDropdownWithService';

Expand All @@ -28,6 +29,14 @@ export default function getToolbarModule({ commandsManager, servicesManager }: w
name: 'ohif.splitButton',
defaultComponent: ToolbarSplitButtonWithServices,
},
{
name: 'ohif.toggleTool',
defaultComponent: ToolToggle,
},
{
name: 'ohif.toggleToolList',
defaultComponent: ToolToggleList,
},
{
name: 'ohif.layoutSelector',
defaultComponent: props =>
Expand Down
174 changes: 121 additions & 53 deletions modes/longitudinal/src/toolbarButtons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,105 +12,173 @@ export const setToolActiveToolbar = {
},
};

// const toolbarButtons: Button[] = [
// {
// id: 'MeasurementTools',
// uiType: 'ohif.splitButton',
// props: {
// groupId: 'MeasurementTools',
// // group evaluate to determine which item should move to the top
// evaluate: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList',
// primary: createButton({
// id: 'Length',
// icon: 'tool-length',
// label: 'Length',
// tooltip: 'Length Tool',
// commands: setToolActiveToolbar,
// evaluate: 'evaluate.cornerstoneTool',
// }),
// secondary: {
// icon: 'chevron-down',
// tooltip: 'More Measure Tools',
// },
// items: [
// createButton({
// id: 'Length',
// icon: 'tool-length',
// label: 'Length',
// tooltip: 'Length Tool',
// commands: setToolActiveToolbar,
// evaluate: 'evaluate.cornerstoneTool',
// }),
// createButton({
// id: 'Bidirectional',
// icon: 'tool-bidirectional',
// label: 'Bidirectional',
// tooltip: 'Bidirectional Tool',
// commands: setToolActiveToolbar,
// evaluate: 'evaluate.cornerstoneTool',
// }),
// createButton({
// id: 'ArrowAnnotate',
// icon: 'tool-annotate',
// label: 'Annotation',
// tooltip: 'Arrow Annotate',
// commands: setToolActiveToolbar,
// evaluate: 'evaluate.cornerstoneTool',
// }),
// createButton({
// id: 'EllipticalROI',
// icon: 'tool-ellipse',
// label: 'Ellipse',
// tooltip: 'Ellipse ROI',
// commands: setToolActiveToolbar,
// evaluate: 'evaluate.cornerstoneTool',
// }),
// createButton({
// id: 'RectangleROI',
// icon: 'tool-rectangle',
// label: 'Rectangle',
// tooltip: 'Rectangle ROI',
// commands: setToolActiveToolbar,
// evaluate: 'evaluate.cornerstoneTool',
// }),
// createButton({
// id: 'CircleROI',
// icon: 'tool-circle',
// label: 'Circle',
// tooltip: 'Circle Tool',
// commands: setToolActiveToolbar,
// evaluate: 'evaluate.cornerstoneTool',
// }),
// createButton({
// id: 'PlanarFreehandROI',
// icon: 'icon-tool-freehand-roi',
// label: 'Freehand ROI',
// tooltip: 'Freehand ROI',
// commands: setToolActiveToolbar,
// evaluate: 'evaluate.cornerstoneTool',
// }),
// createButton({
// id: 'SplineROI',
// icon: 'icon-tool-spline-roi',
// label: 'Spline ROI',
// tooltip: 'Spline ROI',
// commands: setToolActiveToolbar,
// evaluate: 'evaluate.cornerstoneTool',
// }),
// createButton({
// id: 'LivewireContour',
// icon: 'icon-tool-livewire',
// label: 'Livewire tool',
// tooltip: 'Livewire tool',
// commands: setToolActiveToolbar,
// evaluate: 'evaluate.cornerstoneTool',
// }),
// ],
// },
// },
const toolbarButtons: Button[] = [
{
id: 'MeasurementTools',
uiType: 'ohif.splitButton',
uiType: 'ohif.toggleToolList',
props: {
groupId: 'MeasurementTools',
// group evaluate to determine which item should move to the top
evaluate: 'evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList',
primary: createButton({
id: 'Length',
icon: 'tool-length',
label: 'Length',
tooltip: 'Length Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
secondary: {
icon: 'chevron-down',
tooltip: 'More Measure Tools',
},
evaluate: 'evaluate.group.promoteToPrimary',
id: 'Length',
icon: 'tool-length',
label: 'Length',
commands: setToolActiveToolbar,
items: [
createButton({
{
id: 'Length',
icon: 'tool-length',
label: 'Length',
tooltip: 'Length Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
},
{
id: 'Bidirectional',
icon: 'tool-bidirectional',
label: 'Bidirectional',
tooltip: 'Bidirectional Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
},
{
id: 'ArrowAnnotate',
icon: 'tool-annotate',
label: 'Annotation',
tooltip: 'Arrow Annotate',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
},
{
id: 'EllipticalROI',
icon: 'tool-ellipse',
label: 'Ellipse',
tooltip: 'Ellipse ROI',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
},
{
id: 'RectangleROI',
icon: 'tool-rectangle',
label: 'Rectangle',
tooltip: 'Rectangle ROI',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
},
{
id: 'CircleROI',
icon: 'tool-circle',
label: 'Circle',
tooltip: 'Circle Tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
},
{
id: 'PlanarFreehandROI',
icon: 'icon-tool-freehand-roi',
label: 'Freehand ROI',
tooltip: 'Freehand ROI',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
},
{
id: 'SplineROI',
icon: 'icon-tool-spline-roi',
label: 'Spline ROI',
tooltip: 'Spline ROI',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
createButton({
},
{
id: 'LivewireContour',
icon: 'icon-tool-livewire',
label: 'Livewire tool',
tooltip: 'Livewire tool',
commands: setToolActiveToolbar,
evaluate: 'evaluate.cornerstoneTool',
}),
},
],
},
},
{
id: 'Zoom',
uiType: 'ohif.radioGroup',
uiType: 'ohif.toggleTool',
props: {
icon: 'tool-zoom',
label: 'Zoom',
Expand Down
10 changes: 10 additions & 0 deletions platform/docs/src/pages/components-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,16 @@ Example code coming soon.
{/* Render the Toaster component */}
<Toaster />
</ShowcaseRow>

<ShowcaseRow
title="Toggle"
description="A toggle Switch is used to change between two different states. Use descriptive labels next to Switches that are understandable before interacting."
code={`
<Switch />
`}
>
<Toggle>Hi</Toggle>
</ShowcaseRow>
</div>
</div>
</Layout>
Expand Down
16 changes: 11 additions & 5 deletions platform/ui-next/src/components/Toggle/Toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,26 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '../../lib/utils';

const toggleVariants = cva(
'inline-flex items-center justify-center rounded-md text-primary-foreground/80 font-medium transition-colors hover:bg-primary/20 hover:text-primary-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-primary/20 data-[state=on]:text-highlight',
'inline-flex items-center justify-center text-primary-foreground/80 font-medium transition-colors hover:bg-primary/20 hover:text-primary-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-primary/20 data-[state=on]:text-highlight',
{
variants: {
variant: {
default: 'bg-transparent hover:text-primary',
outline:
'border border-input bg-transparent shadow-sm hover:bg-primary/20 hover:text-primary-foreground',
default: 'rounded-md bg-transparent hover:text-primary',
outline: 'rounded-md border border-input hover:bg-primary/20 hover:text-primary-foreground',
tool: [
'!rounded-lg',
'bg-transparent text-foreground', // default
'hover:bg-background hover:text-primary', // hover
'data-[state=on]:bg-highlight data-[state=on]:text-background data-[state=on]:hover:bg-highlight/50 ', // active
].join(' '),
},
size: {
default: 'h-[24px] w-[28px]',
default: 'h-10 w-10',
sm: 'h-8 px-2',
lg: 'h-10 px-3',
},
},

defaultVariants: {
variant: 'default',
size: 'default',
Expand Down
93 changes: 93 additions & 0 deletions platform/ui-next/src/components/Toggle/ToolToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { Toggle } from './Toggle';
import { Icons } from '@ohif/ui-next';
import {
Tooltip,
TooltipTrigger,
TooltipContent,
TooltipProvider,
} from '../../components/Tooltip/Tooltip';

/**
* ToolToggle
* ----------
* A generic toggle-based tool button that uses Radix Toggle + cva styling
* + your custom Tooltip to display the tool name on hover.
*/
export default function ToolToggle({
id,
icon,
label,
commands,
isActive = false,
disabled = false,
onInteraction,
className,
...rest
}) {
// Local "pressed" state to sync with isActive
const [pressed, setPressed] = useState(isActive);

useEffect(() => {
setPressed(isActive);
}, [isActive]);

/**
* handlePressedChange
* We can optionally block toggling off by ignoring newPressed === false
* if we want this tool to remain "on" until replaced by another tool, etc.
*/
const handlePressedChange = newPressed => {
// Example logic: keep it pressed if we detect an attempt to un-toggle
const finalPressed = pressed && newPressed === false ? true : newPressed;

setPressed(finalPressed);

if (!disabled && onInteraction) {
onInteraction({
itemId: id,
commands,
});
}
};

return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Toggle
pressed={pressed}
onPressedChange={handlePressedChange}
variant="tool"
size="default"
disabled={disabled}
className={className}
{...rest}
>
{icon && (
<Icons.ByName
name={icon}
className="h-7 w-7 flex-shrink-0"
/>
)}
</Toggle>
</TooltipTrigger>

{/* Only show tooltip if a label exists */}
{label && <TooltipContent>{label}</TooltipContent>}
</Tooltip>
</TooltipProvider>
);
}

ToolToggle.propTypes = {
id: PropTypes.string.isRequired,
icon: PropTypes.string,
label: PropTypes.string,
commands: PropTypes.oneOfType([PropTypes.array, PropTypes.object, PropTypes.string]),
isActive: PropTypes.bool,
disabled: PropTypes.bool,
onInteraction: PropTypes.func,
className: PropTypes.string,
};
Loading
Loading