chore(version): move to the next beta version [BUMP BETA] (#4939)
This commit is contained in:
1 parent
e1e9b5ccb1
commit
e37168094a
374 files changed
+6978
-1721
No files matched your search
+21
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Introduction
|
||||
position: 1
|
||||
---
|
||||
|
||||
|
||||
## Introduction
|
||||
|
||||
The OHIF Viewer has two main parts: the worklist and the image viewer.
|
||||
|
||||
In version 3.10, we successfully migrated the image viewer to the `@ohif/ui-next` library. This is a complete rewrite of each component, offering extensibility, accessibility, and a modern look and feel.
|
||||
|
||||
The worklist is still using the old `@ohif/ui` library, but it will be migrated to `@ohif/ui-next` in a future release.
|
||||
|
||||
## Migration Guide
|
||||
|
||||
You'll generally need to update your custom panels to use the new `@ohif/ui-next` components.
|
||||
|
||||
The task is to find the direct mapping of the components you're using in your custom panels.
|
||||
|
||||
This guide will cover the migration for them.
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
---
|
||||
title: Tests
|
||||
---
|
||||
|
||||
|
||||
## 1. ToolButton `data-active` Attribute
|
||||
|
||||
- **Previous**: Checked for a `bg-primary-light` class to determine if a tool was active.
|
||||
- **Now**: Check for the HTML attribute `data-active="true"`.
|
||||
|
||||
### Example
|
||||
|
||||
```diff
|
||||
- cy.get('@wwwcButton').should('have.class', 'bg-primary-light');
|
||||
+ cy.get('@wwwcButton').should('have.attr', 'data-active', 'true');
|
||||
```
|
||||
|
||||
## 2. Additional Data Attributes
|
||||
|
||||
- Each tool button now includes:
|
||||
- `data-tool="<toolId>"`
|
||||
- `data-active="<true|false>"`
|
||||
|
||||
This makes it easier to identify and assert on specific tools in the DOM.
|
||||
|
||||
### Example
|
||||
|
||||
```diff
|
||||
- <span data-cy={id}>
|
||||
+ <span
|
||||
+ data-cy={id}
|
||||
+ data-tool={id}
|
||||
+ data-active={isActive}
|
||||
+ >
|
||||
```
|
||||
|
||||
## 3. MPR Button Class Change
|
||||
|
||||
If you were targeting the `ohif-disabled` class, you need to update your tests to target the `cursor-not-allowed` class.
|
||||
|
||||
- **Previous**: `ohif-disabled`
|
||||
- **Now**: `cursor-not-allowed`
|
||||
|
||||
### Example
|
||||
|
||||
```diff
|
||||
- cy.get('[data-cy="MPR"]').should('have.class', 'ohif-disabled');
|
||||
+ cy.get('[data-cy="MPR"]').should('have.class', 'cursor-not-allowed');
|
||||
```
|
||||
|
||||
## 4. Removal of Stack Scroll Alias
|
||||
|
||||
- The `[data-cy="StackScroll"]` element is no longer reliably in the DOM at study load.
|
||||
- If needed, reintroduce or conditionally assert its presence when appropriate.
|
||||
|
||||
```diff
|
||||
- cy.get('[data-cy="StackScroll"]').as('stackScrollBtn');
|
||||
+ // Removed due to absence in DOM at study load
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
1. **Replace** all checks for `bg-primary-light` with `data-active="true"`.
|
||||
2. **Use** `data-tool` and `data-active` attributes for more robust DOM selection and assertions.
|
||||
3. **Update** MPR button checks to `cursor-not-allowed`.
|
||||
4. **Remove** the `[data-cy="StackScroll"]` alias (or only use it when the element is present).
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: Colors
|
||||
---
|
||||
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* **New Color System:** Migration from custom color names (e.g., `aqua-pale`, `common-bright`) to a semantic color palette using CSS variables (e.g., `--primary`, `--secondary`, `--muted-foreground`). Tailwind classes like `text-primary`, `bg-secondary`, `text-muted-foreground` should now be used.
|
||||
* **Deprecated Color Classes:** Custom color classes like `text-aqua-pale` and `text-common-bright` have been removed and need replacement.
|
||||
* **Simplified State Classes:** Explicit hover/active state classes like `bg-primary-main`, `hover:bg-primary-light`, `active:text-primary-light` seem to be replaced by simpler base classes (e.g., `bg-primary`) where Tailwind's state variants (`hover:`, `active:`) modify the base color, or these states are handled by component variants (e.g., in a Button component).
|
||||
* **Component Abstraction:** Some styling, especially for interactive elements like buttons, has been abstracted into components (e.g., `ViewportActionButton`, UI library buttons) which use predefined variants (`default`, `secondary`, `ghost`) instead of manual style combinations.
|
||||
|
||||
:::note
|
||||
You can look at the set of colors in the [Color System](/colors-and-type)
|
||||
:::
|
||||
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Identify Deprecated Color Classes:**
|
||||
Search your codebase for the old custom color classes. The most common ones identified in the diff are:
|
||||
* `text-aqua-pale`
|
||||
* `text-common-bright`
|
||||
* `text-primary-active`
|
||||
* `bg-primary-main`
|
||||
* `hover:bg-primary-light`
|
||||
* `hover:text-black` (when used with primary hover states)
|
||||
* Potentially others using similar custom names.
|
||||
|
||||
2. **Replace with New Semantic Colors:**
|
||||
Update the deprecated classes with their likely semantic equivalents from the new system. Use the table below as a guide. **Note:** The exact replacement might depend on the specific context and desired visual outcome. Inspect the element in the browser after changes to ensure it matches the intended design.
|
||||
|
||||
| Old Class | Likely New Class(es) | Notes |
|
||||
| :------------------------ | :-------------------------------------------------------- | :-------------------------------------------------------------------- |
|
||||
| `text-aqua-pale` | `text-muted-foreground` | Used for less prominent text, now uses the muted foreground color. |
|
||||
| `text-common-bright` | `text-foreground` or `text-primary-foreground` | Likely the default bright text color. |
|
||||
| `text-primary-active` | `text-primary` or `text-highlight` | Simplified to the base primary color or potentially a highlight color. |
|
||||
| `bg-primary-main` | `bg-primary` | Simplified to the base primary background color. |
|
||||
| `text-white` (on dark bg) | `text-foreground` or `text-primary-foreground` | Use the standard foreground color for the theme. |
|
||||
| `bg-black` (for elements) | `bg-background`, `bg-popover`, `bg-card`, or `bg-muted` | Use semantic background colors depending on the element's role. |
|
||||
|
||||
3. **Update State Variants and Interactions:**
|
||||
Classes managing hover, active, or focus states have likely been simplified or moved into component variants.
|
||||
|
||||
* **Remove Explicit Hover/Active Styles:** Search for combinations like `hover:bg-primary-light`, `hover:text-black`, `active:text-primary-light` and remove them if the element now uses a base class like `bg-primary` or component variants. Tailwind's built-in state modifiers (`hover:`, `active:`) might handle this automatically with the new base colors, or component variants encapsulate these states.
|
||||
* **Use Component Variants:** If the element is now a component from a UI library (like `Button` from `@ohif/ui-next`), use its variants (`variant="default"`, `variant="secondary"`, `variant="ghost"`) instead of manual style combinations.
|
||||
|
||||
*Example Diff:*
|
||||
```diff
|
||||
- <div className="bg-primary-main hover:bg-primary-light text-white hover:text-black rounded p-2">
|
||||
- Action Button
|
||||
- </div>
|
||||
|
||||
+ <Button variant="default">
|
||||
+ Action Button
|
||||
+ </Button>
|
||||
```
|
||||
|
||||
*Example Diff:*
|
||||
```diff
|
||||
// Before (in _getStatusComponent.tsx)
|
||||
- <div
|
||||
- className="bg-primary-main hover:bg-primary-light ml-1 cursor-pointer rounded px-1.5 hover:text-black"
|
||||
- onMouseUp={onStatusClick}
|
||||
- >
|
||||
- {loadStr}
|
||||
- </div>
|
||||
|
||||
// After (in OHIFCornerstoneRTViewport.tsx using the abstracted component)
|
||||
+ <ViewportActionButton onInteraction={onStatusClick}>
|
||||
+ {loadStr}
|
||||
+ </ViewportActionButton>
|
||||
```
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
---
|
||||
title: Icons
|
||||
---
|
||||
|
||||
## Migration Guide: Icon Component Updates
|
||||
|
||||
### General Overview
|
||||
|
||||
This migration involves changes to how icons are used within the OHIF platform. The core change is the move to a new icon component library, `@ohif/ui-next`, which provides more flexibility and a more consistent naming convention for icons.
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
1. **New Icon Library:** The primary change is the shift from using `<Icon>` from `@ohif/ui` to using the new `Icons` component from `@ohif/ui-next`.
|
||||
2. **`AbcDef` Naming Convention:** The new library uses a `AbcDef` (PascalCase) naming convention for the icons. For instance, `status-alert` is now `StatusAlert`.
|
||||
3. **Legacy Fallback:** To ease the transition, a legacy fallback has been provided using `Icons.ByName`. This allows you to continue using the old `name="status-alert"` format but is not the recommended way moving forward.
|
||||
4. **Direct Icon Component Access:** The recommended approach is to use `Icons.StatusAlert` instead of `<Icons.ByName name="status-alert"/>` this way will make code more clear and readable.
|
||||
|
||||
### Migration Strategies
|
||||
|
||||
You have two ways to approach the migration:
|
||||
|
||||
1. **Recommended Approach (Gradual Adoption):**
|
||||
* Start by updating your codebase to use the `Icons.Method` for the new icon naming convention.
|
||||
* For example, replace `<Icon name="status-alert" />` with `<Icons.StatusAlert />`.
|
||||
* This ensures your code is aligned with the new standard and provides optimal compatibility in the future.
|
||||
* This method can be rolled out in phases.
|
||||
|
||||
2. **Legacy Fallback Approach (Temporary):**
|
||||
* If a full migration is not immediately feasible, you can use the legacy fallback temporarily:
|
||||
* Replace `<Icon name="status-alert" />` with `<Icons.ByName name="status-alert" />`.
|
||||
* This option allows you to complete the migration with minimal disruption to the old code
|
||||
* However, it is highly recommended to move towards the `Icons.Method` approach to take advantage of all the new library offers and have a cleaner code base.
|
||||
|
||||
**Recommendation:** We strongly recommend using the *Recommended Approach* for a more maintainable and consistent codebase going forward.
|
||||
|
||||
### Specific Changes (Code Examples)
|
||||
|
||||
Here are some specific examples based on the diff you provided, illustrating both the legacy fallback and recommended approach:
|
||||
|
||||
**Example 1: Status Icons in `_getStatusComponent.tsx`**
|
||||
|
||||
**Old Code (`@ohif/ui`):**
|
||||
|
||||
```jsx
|
||||
import { Icon, Tooltip } from '@ohif/ui';
|
||||
|
||||
// ...
|
||||
case true:
|
||||
StatusIcon = () => <Icon name="status-alert" />;
|
||||
break;
|
||||
case false:
|
||||
StatusIcon = () => (
|
||||
<Icon
|
||||
className="text-aqua-pale"
|
||||
name="status-untracked"
|
||||
/>
|
||||
);
|
||||
break;
|
||||
//...
|
||||
|
||||
```
|
||||
|
||||
**Legacy Fallback Approach (`Icons.ByName`):**
|
||||
|
||||
```jsx
|
||||
import { Tooltip } from '@ohif/ui';
|
||||
import { Icons } from '@ohif/ui-next';
|
||||
|
||||
// ...
|
||||
case true:
|
||||
StatusIcon = () => <Icons.ByName name="status-alert" />;
|
||||
break;
|
||||
case false:
|
||||
StatusIcon = () => (
|
||||
<Icons.ByName
|
||||
className="text-aqua-pale"
|
||||
name="status-untracked"
|
||||
/>
|
||||
);
|
||||
break;
|
||||
//...
|
||||
```
|
||||
|
||||
**Recommended Approach (`Icons.StatusAlert`, `Icons.StatusUntracked`):**
|
||||
|
||||
```jsx
|
||||
import { Tooltip } from '@ohif/ui';
|
||||
import { Icons } from '@ohif/ui-next';
|
||||
|
||||
// ...
|
||||
case true:
|
||||
StatusIcon = () => <Icons.StatusAlert />;
|
||||
break;
|
||||
case false:
|
||||
StatusIcon = () => (
|
||||
<Icons.StatusUntracked
|
||||
className="text-aqua-pale"
|
||||
/>
|
||||
);
|
||||
break;
|
||||
//...
|
||||
```
|
||||
|
||||
|
||||
**Example 5: Icon usage in `WorkList.tsx`**
|
||||
|
||||
**Old Code (`@ohif/ui`):**
|
||||
|
||||
```jsx
|
||||
<Icon
|
||||
name="group-layers"
|
||||
```
|
||||
**Recommended Approach (`Icons.GroupLayers`):**
|
||||
|
||||
```jsx
|
||||
<Icons.GroupLayers
|
||||
```
|
||||
```jsx
|
||||
<Icons.ByName
|
||||
className="!h-[20px] !w-[20px] text-black"
|
||||
name={isValidMode ? 'launch-arrow' : 'launch-info'}
|
||||
/>
|
||||
```
|
||||
**Recommended Approach (`Icons.LaunchArrow`, `Icons.LaunchInfo`):**
|
||||
|
||||
```jsx
|
||||
isValidMode ? (
|
||||
<Icons.LaunchArrow className="!h-[20px] !w-[20px] text-black" />
|
||||
) : (
|
||||
<Icons.LaunchInfo className="!h-[20px] !w-[20px] text-black" />
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
### Detailed Renaming Table
|
||||
|
||||
| Old Icon Name | New Icon Component Name | Example Usage (`Icons.`) | Notes |
|
||||
| :------------------------------ | :------------------------------------------ | :--------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `status-alert` | `StatusAlert` | `Icons.StatusAlert` | |
|
||||
| `status-untracked` | `StatusUntracked` | `Icons.StatusUntracked` | |
|
||||
| `status-locked` | `StatusLocked` | `Icons.StatusLocked` | |
|
||||
| `icon-transferring` | `IconTransferring` | `Icons.IconTransferring` | |
|
||||
| `icon-alert-small` | `Alert` | `Icons.Alert` | |
|
||||
| `icon-alert-outline` | `AlertOutline` | `Icons.AlertOutline` | |
|
||||
| `icon-status-alert` | `Alert` | `Icons.Alert` | |
|
||||
| `action-new-dialog` | `ActionNewDialog` | `Icons.ActionNewDialog` | |
|
||||
| `VolumeRendering` | `VolumeRendering` | `Icons.VolumeRendering` | |
|
||||
| `chevron-left` | `ChevronClosed` | `Icons.ChevronClosed` | Use when arrow direction needs to point left |
|
||||
| `chevron-down` | `ChevronOpen` | `Icons.ChevronOpen` | Use when arrow direction needs to point down |
|
||||
| `launch-arrow` | `LaunchArrow` | `Icons.LaunchArrow` | |
|
||||
| `launch-info` | `LaunchInfo` | `Icons.LaunchInfo` | |
|
||||
| `group-layers` | `GroupLayers` | `Icons.GroupLayers` | |
|
||||
| `icon-upload` | `Upload` | `Icons.Upload` | |
|
||||
| `icon-search` | `Search` | `Icons.Search` | |
|
||||
| `icon-clear-field` | `Clear` | `Icons.Clear` | |
|
||||
| `icon-add` | `Add` | `Icons.Add` | |
|
||||
| `icon-close` | `Close` | `Icons.Close` | |
|
||||
| `icon-pause` | `Pause` | `Icons.Pause` | |
|
||||
| `icon-play` | `Play` | `Icons.Play` | |
|
||||
| `icon-multiple-patients` | `MultiplePatients` | `Icons.MultiplePatients` | |
|
||||
| `icon-settings` | `Settings` | `Icons.Settings` | |
|
||||
| `icon-more-menu` | `More` | `Icons.More` | |
|
||||
| `content-prev` | `ContentPrev` | `Icons.ContentPrev` | |
|
||||
| `content-next` | `ContentNext` | `Icons.ContentNext` | |
|
||||
| `checkbox-checked` | `CheckBoxChecked` | `Icons.CheckBoxChecked` | |
|
||||
| `checkbox-unchecked` | `CheckBoxUnchecked` | `Icons.CheckBoxUnchecked` | |
|
||||
| `checkbox-default` | `CheckBoxUnchecked` | `Icons.CheckBoxUnchecked` | |
|
||||
|`checkbox-active`| `CheckBoxChecked`| `Icons.CheckBoxChecked`| |
|
||||
| `sorting-active-up` | `SortingAscending` | `Icons.SortingAscending` | |
|
||||
| `sorting-active-down` | `SortingDescending` | `Icons.SortingDescending` | |
|
||||
| `sorting` | `Sorting` | `Icons.Sorting` | |
|
||||
|`link` | `Link` | `Icons.Link` | |
|
||||
|`unlink` | `Link` | `Icons.Link` | |
|
||||
|`info-action` | `Info` | `Icons.Info` | |
|
||||
|`database` | `Database`| `Icons.Database`| |
|
||||
|`tool-3d-rotate`| `Tool3DRotate`| `Icons.Tool3DRotate`| |
|
||||
|`tool-angle`| `ToolAngle`| `Icons.ToolAngle`| |
|
||||
|`tool-annotate`| `ToolAnnotate`| `Icons.ToolAnnotate`| |
|
||||
|`tool-bidirectional`| `ToolBidirectional`| `Icons.ToolBidirectional`| |
|
||||
|`tool-calibration`| `ToolCalibrate`| `Icons.ToolCalibrate`| |
|
||||
|`tool-capture`| `ToolCapture`| `Icons.ToolCapture`| |
|
||||
|`tool-cine`| `ToolCine`| `Icons.ToolCine`| |
|
||||
|`tool-circle`| `ToolCircle`| `Icons.ToolCircle`| |
|
||||
|`tool-cobb-angle`| `ToolCobbAngle`| `Icons.ToolCobbAngle`| |
|
||||
|`tool-create-threshold`| `ToolCreateThreshold` | `Icons.ToolCreateThreshold` | |
|
||||
|`tool-crosshair`| `ToolCrosshair`| `Icons.ToolCrosshair`| |
|
||||
|`dicom-tag-browser`| `ToolDicomTagBrowser` | `Icons.ToolDicomTagBrowser` | |
|
||||
|`tool-flip-horizontal`| `ToolFlipHorizontal` | `Icons.ToolFlipHorizontal` | |
|
||||
|`tool-freehand-polygon`| `ToolFreehandPolygon`| `Icons.ToolFreehandPolygon`| |
|
||||
|`tool-freehand-roi`| `ToolFreehandRoi` | `Icons.ToolFreehandRoi`| |
|
||||
|`tool-freehand`| `ToolFreehand`| `Icons.ToolFreehand`| |
|
||||
|`tool-fusion-color`| `ToolFusionColor`| `Icons.ToolFusionColor`| |
|
||||
|`tool-invert`| `ToolInvert`| `Icons.ToolInvert`| |
|
||||
|`tool-layout-default`| `ToolLayoutDefault`| `Icons.ToolLayoutDefault`| |
|
||||
|`tool-length`| `ToolLength`| `Icons.ToolLength`| |
|
||||
|`tool-magnetic-roi`| `ToolMagneticRoi` | `Icons.ToolMagneticRoi`| |
|
||||
|`tool-magnify`| `ToolMagnify`| `Icons.ToolMagnify`| |
|
||||
|`tool-measure-ellipse`| `ToolMeasureEllipse`| `Icons.ToolMeasureEllipse`| |
|
||||
|`tool-more-menu`| `ToolMoreMenu`| `Icons.ToolMoreMenu`| |
|
||||
|`tool-move`| `ToolMove`| `Icons.ToolMove`| |
|
||||
|`tool-polygon`| `ToolPolygon`| `Icons.ToolPolygon`| |
|
||||
|`tool-quick-magnify`| `ToolQuickMagnify` | `Icons.ToolQuickMagnify` | |
|
||||
|`tool-rectangle`| `ToolRectangle` | `Icons.ToolRectangle` | |
|
||||
|`tool-referenceLines`| `ToolReferenceLines`| `Icons.ToolReferenceLines`| |
|
||||
|`tool-reset`| `ToolReset`| `Icons.ToolReset`| |
|
||||
|`tool-rotate-right`| `ToolRotateRight`| `Icons.ToolRotateRight`| |
|
||||
|`tool-seg-brush`| `ToolSegBrush`| `Icons.ToolSegBrush`| |
|
||||
|`tool-seg-eraser`| `ToolSegEraser`| `Icons.ToolSegEraser`| |
|
||||
|`tool-seg-shape`| `ToolSegShape` | `Icons.ToolSegShape`| |
|
||||
|`tool-seg-threshold`| `ToolSegThreshold` | `Icons.ToolSegThreshold` | |
|
||||
|`tool-spline-roi`| `ToolSplineRoi`| `Icons.ToolSplineRoi`| |
|
||||
|`tool-stack-image-sync`| `ToolStackImageSync`| `Icons.ToolStackImageSync`| |
|
||||
|`tool-stack-scroll`| `ToolStackScroll` | `Icons.ToolStackScroll`| |
|
||||
|`tool-toggle-dicom-overlay`| `ToolToggleDicomOverlay`| `Icons.ToolToggleDicomOverlay`| |
|
||||
|`tool-ultrasound-bidirectional`| `ToolUltrasoundBidirectional`| `Icons.ToolUltrasoundBidirectional`| |
|
||||
|`tool-window-level`| `ToolWindowLevel`| `Icons.ToolWindowLevel`| |
|
||||
|`tool-window-region`| `ToolWindowRegion`| `Icons.ToolWindowRegion`| |
|
||||
|`tool-zoom` | `ToolZoom` | `Icons.ToolZoom`| |
|
||||
| `tool-layout` | `ToolLayout` | `Icons.ToolLayout` | |
|
||||
|`icon-tool-eraser`| `ToolEraser` | `Icons.ToolEraser`| |
|
||||
|`icon-tool-brush`| `ToolBrush`| `Icons.ToolBrush`| |
|
||||
|`icon-tool-threshold`| `ToolThreshold` | `Icons.ToolThreshold` | |
|
||||
|`icon-tool-shape`| `ToolShape`| `Icons.ToolShape` | |
|
||||
|`icon-color-lut`| `IconColorLUT` | `Icons.IconColorLUT` | |
|
||||
| `viewport-window-level`|`ViewportWindowLevel`|`Icons.ViewportWindowLevel`| |
|
||||
|`notifications-info`| `NotificationInfo`| `Icons.NotificationInfo`| |
|
||||
|`layout-advanced-3d-four-up` | `LayoutAdvanced3DFourUp` | `Icons.LayoutAdvanced3DFourUp` | |
|
||||
|`layout-advanced-3d-main` | `LayoutAdvanced3DMain` | `Icons.LayoutAdvanced3DMain` | |
|
||||
|`layout-advanced-3d-only` | `LayoutAdvanced3DOnly` | `Icons.LayoutAdvanced3DOnly`| |
|
||||
|`layout-advanced-3d-primary` | `LayoutAdvanced3DPrimary` | `Icons.LayoutAdvanced3DPrimary` | |
|
||||
|`layout-advanced-axial-primary` |`LayoutAdvancedAxialPrimary`| `Icons.LayoutAdvancedAxialPrimary` | |
|
||||
|`layout-advanced-mpr`| `LayoutAdvancedMPR` | `Icons.LayoutAdvancedMPR` | |
|
||||
|`layout-common-1x1` | `LayoutCommon1x1` | `Icons.LayoutCommon1x1` | |
|
||||
|`layout-common-1x2` | `LayoutCommon1x2`|`Icons.LayoutCommon1x2`| |
|
||||
|`layout-common-2x2` | `LayoutCommon2x2`|`Icons.LayoutCommon2x2` | |
|
||||
|`layout-common-2x3` | `LayoutCommon2x3`| `Icons.LayoutCommon2x3`| |
|
||||
|`illustration-investigational-use`|`InvestigationalUse`|`Icons.InvestigationalUse`| |
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
---
|
||||
title: Button
|
||||
---
|
||||
|
||||
## Key Changes:
|
||||
|
||||
* **Component Library:** The primary `Button` component likely now resides in `@ohif/ui-next` instead of `@ohif/ui`. Imports need to be updated.
|
||||
* **`ButtonEnums` Deprecated:** The `ButtonEnums.type` (e.g., `ButtonEnums.type.primary`) used for button styling is deprecated. Styling is now primarily controlled by the `variant` prop using string literals (`'default'`, `'secondary'`, `'ghost'`, `'link'`).
|
||||
* **Styling Approach:** Manual Tailwind CSS classes for styling (colors, hover states, sizing) are largely replaced by the `variant` and `size` props on the new `Button` component. Semantic color names are used internally.
|
||||
* **`IconButton` Replacement:** The pattern of using a dedicated `IconButton` component is often replaced by using `<Button variant="ghost" size="icon">` and embedding an icon component (like `<Icons.ByName name="..." />`) within it.
|
||||
* **`ButtonGroup` Deprecated:** The `ButtonGroup` component is deprecated and replaced by the `Tabs`, `TabsList`, and `TabsTrigger` components from `@ohif/ui-next` for creating selectable groups.
|
||||
* **Specific Action Buttons:** In certain contexts (like viewport actions or footers), generic buttons or styled `div` elements might be replaced by more specific components like `ViewportActionButton` or composite components like `FooterAction`.
|
||||
* **Color System:** Custom color classes (e.g., `text-primary-active`, `bg-primary-main`) are replaced by a new semantic color system (e.g., `text-primary`, `bg-primary`, `text-muted-foreground`). Variants often handle color states (hover, active) automatically.
|
||||
|
||||
## Migration Steps:
|
||||
|
||||
1. **Update Imports:**
|
||||
Replace imports for `Button` and related enums from `@ohif/ui` with the new `Button` component, likely from `@ohif/ui-next`.
|
||||
|
||||
```diff
|
||||
- import { Button, ButtonEnums, IconButton } from '@ohif/ui';
|
||||
+ import { Button, Icons } from '@ohif/ui-next';
|
||||
```
|
||||
|
||||
3. **Migrate Manual Styling to `variant` and `size` Props:**
|
||||
Remove custom Tailwind CSS classes for basic button appearance, hover states, and sizing. Use the `variant` (`'default'`, `'secondary'`, `'ghost'`, `'link'`) and `size` (`'sm'`, `'default'`, `'lg'`, `'icon'`) props instead.
|
||||
|
||||
*Example (`DynamicVolumeControls.tsx` change):*
|
||||
```diff
|
||||
- <Button
|
||||
- className="mt-2 !h-[26px] !w-[115px] self-start !p-0"
|
||||
- onClick={() => { onGenerate(computeViewMode); }}
|
||||
- >
|
||||
+ <Button
|
||||
+ variant="default"
|
||||
+ size="sm"
|
||||
+ className="mt-2 h-[26px] w-[115px] self-start p-0" // Keep only necessary layout/positioning classes
|
||||
+ onClick={handleGenerate}
|
||||
+ >
|
||||
Generate
|
||||
</Button>
|
||||
```
|
||||
|
||||
5. **Replace `IconButton`:**
|
||||
Update instances of `<IconButton>` to use `<Button variant="ghost" size="icon">`. Place the icon component from `@ohif/ui-next` (e.g., `<Icons.ByName name="icon-name" />`) inside the button.
|
||||
|
||||
*Example (`DynamicVolumeControls.tsx` change):*
|
||||
```diff
|
||||
- <IconButton
|
||||
- className="bg-customblue-30 h-[26px] w-[58px] rounded-[4px]"
|
||||
- onClick={() => onPlayPauseChange(!isPlaying)}
|
||||
- >
|
||||
- <Icon
|
||||
- name={getPlayPauseIconName()}
|
||||
- className="active:text-primary-light hover:bg-customblue-300 h-[24px] w-[24px] cursor-pointer text-white"
|
||||
- />
|
||||
- </IconButton>
|
||||
+ <Button
|
||||
+ id="play-pause-button"
|
||||
+ variant="secondary" // Or "ghost" depending on final desired style
|
||||
+ size="default" // Or "icon" if only icon is needed
|
||||
+ className="w-[58px]" // Keep specific width if necessary
|
||||
+ onClick={() => {
|
||||
+ if (typeof onPlayPauseChange === 'function') {
|
||||
+ onPlayPauseChange(!isPlaying);
|
||||
+ }
|
||||
+ }}
|
||||
+ >
|
||||
+ <Icons.ByName
|
||||
+ name={getPlayPauseIconName()}
|
||||
+ className="text-foreground h-[24px] w-[24px]" // Use semantic colors
|
||||
+ />
|
||||
+ </Button>
|
||||
```
|
||||
|
||||
6. **Replace `ButtonGroup` with `Tabs`:**
|
||||
Refactor sections using `ButtonGroup` to use the `Tabs`, `TabsList`, and `TabsTrigger` components. Manage the selected state using the `value` and `onValueChange` props of the `Tabs` component.
|
||||
|
||||
*Example (`DynamicVolumeControls.tsx` change):*
|
||||
```diff
|
||||
- <ButtonGroup className="mt-2 w-full">
|
||||
- <button className="w-1/2" onClick={() => setComputedView(false)}>4D</button>
|
||||
- <button className="w-1/2" onClick={() => setComputedView(true)}>Computed</button>
|
||||
- </ButtonGroup>
|
||||
|
||||
+ <Tabs
|
||||
+ value={computedView ? 'computed' : '4d'}
|
||||
+ onValueChange={value => setComputedView(value === 'computed')}
|
||||
+ className="my-2 w-full"
|
||||
+ >
|
||||
+ <TabsList className="w-full">
|
||||
+ <TabsTrigger value="4d" className="w-1/2">4D</TabsTrigger>
|
||||
+ <TabsTrigger value="computed" className="w-1/2">Computed</TabsTrigger>
|
||||
+ </TabsList>
|
||||
+ </Tabs>
|
||||
```
|
||||
|
||||
7. **Identify Specific Component Replacements:**
|
||||
Review areas where styled `div` elements were used as buttons. Replace them with appropriate components like `<Button>` or domain-specific ones if available (e.g., `ViewportActionButton`).
|
||||
|
||||
*Example (`_getStatusComponent.tsx` change):*
|
||||
```diff
|
||||
- <div
|
||||
- className="bg-primary-main hover:bg-primary-light ml-1 cursor-pointer rounded px-1.5 hover:text-black"
|
||||
- onMouseUp={onStatusClick}
|
||||
- >
|
||||
- {loadStr}
|
||||
- </div>
|
||||
+ <ViewportActionButton onInteraction={onStatusClick}>{loadStr}</ViewportActionButton>
|
||||
```
|
||||
|
||||
*Example (`VolumeRenderingPresetsContent.tsx` change):*
|
||||
```diff
|
||||
- <Button
|
||||
- name="Cancel"
|
||||
- size={ButtonEnums.size.medium}
|
||||
- type={ButtonEnums.type.secondary}
|
||||
- onClick={onClose}
|
||||
- > Cancel </Button>
|
||||
+ <FooterAction>
|
||||
+ <FooterAction.Right>
|
||||
+ <FooterAction.Secondary onClick={hide}>Cancel</FooterAction.Secondary>
|
||||
+ </FooterAction.Right>
|
||||
+ </FooterAction>
|
||||
```
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
---
|
||||
title: Input
|
||||
---
|
||||
|
||||
|
||||
# Migration Guide: Input Components to @ohif/ui-next
|
||||
|
||||
This guide explains how to migrate from the existing `Input`, `InputNumber`, `InputRange`, `InputDoubleRange`, `InputFilterText`, `InputGroup`, `InputLabelWrapper`, and `InputText` components to their new equivalents or patterns using `@ohif/ui-next`, including the `Numeric` meta component for numeric inputs.
|
||||
|
||||
|
||||
|
||||
|
||||
## Why Migrate?
|
||||
|
||||
See the full list of components in the [Numeric Component Showcase](/components-list#numeric)
|
||||
|
||||
|
||||
The old components relied heavily on props, making them complex and difficult to maintain and apply custom styles. The new `Numeric` component provides a structured approach with a context-based API, reducing prop clutter and improving reusability.
|
||||
|
||||
The `Numeric` component offers several advantages:
|
||||
- **Versatile Modes**: It supports basic number input (`Numeric.NumberInput`), stepper controls (`Numeric.NumberStepper`), single range sliders (`Numeric.SingleRange`), and double range sliders (`Numeric.DoubleRange`).
|
||||
- **Flexible Layout**: You have full control over the layout using standard CSS classes (`className`) on the container and its subcomponents like `Numeric.Label`, `Numeric.NumberInput`, etc., allowing for various arrangements (e.g., flex, grid).
|
||||
- **Enhanced Customization**: Easily customize the appearance and behavior, such as showing/hiding associated number inputs for sliders, displaying the current value within the label (`showValue`), and integrating icons.
|
||||
- **State Management**: Supports both controlled and uncontrolled component states.
|
||||
|
||||
|
||||
|
||||
|
||||
## `Input type="number"` > `Numeric.NumberInput`
|
||||
|
||||
### Basic Usage
|
||||
|
||||
**Old Usage:**
|
||||
|
||||
```tsx
|
||||
<Input
|
||||
id="example"
|
||||
label="Enter a number"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
type="number"
|
||||
/>
|
||||
```
|
||||
|
||||
**New Usage:**
|
||||
|
||||
```tsx
|
||||
<Numeric.Container mode="number" value={value} onChange={setValue}>
|
||||
<Numeric.Label>Enter a number</Numeric.Label>
|
||||
<Numeric.NumberInput />
|
||||
</Numeric.Container>
|
||||
```
|
||||
|
||||
|
||||
|
||||
### `Input` with Custom Classes
|
||||
|
||||
#### **Old Usage (with containerClassName, labelClassName, and className)**
|
||||
|
||||
In the old implementation, we manually applied `containerClassName`, `labelClassName`, and `className` to style the `Input` component:
|
||||
|
||||
```tsx
|
||||
<Input
|
||||
id="example"
|
||||
label="Enter a number"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
type="number"
|
||||
containerClassName="flex flex-col space-y-2"
|
||||
labelClassName="text-gray-500 text-sm"
|
||||
className="border rounded p-2"
|
||||
/>
|
||||
```
|
||||
|
||||
|
||||
**New Usage (Migrating to `Numeric.NumberInput`)**
|
||||
|
||||
With `Numeric`, you should wrap everything inside `Numeric.Container`, and you can directly apply class names to its subcomponents:
|
||||
|
||||
```tsx
|
||||
<Numeric.Container mode="number" value={value} onChange={setValue} className="flex flex-col space-y-2">
|
||||
<Numeric.Label className="text-gray-500 text-sm">Enter a number</Numeric.Label>
|
||||
<Numeric.NumberInput className="border rounded p-2" />
|
||||
</Numeric.Container>
|
||||
```
|
||||
|
||||
|
||||
## `Input` / `InputText` (General) > `@ohif/ui-next Input + Label`
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* The base `Input` component from `@ohif/ui` is replaced by the `Input` component from `@ohif/ui-next`.
|
||||
* Styling props like `labelClassName`, `containerClassName` are removed. Use standard `className` on the `Input` component and its container elements.
|
||||
* Labels provided via the `label` prop are removed. Use the separate `Label` component from `@ohif/ui-next` alongside the `Input`.
|
||||
* Layout is handled by standard HTML/Tailwind (Flexbox, Grid).
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Update Import:** Ensure you are importing `Input` and `Label` from `@ohif/ui-next`.
|
||||
2. **Replace Label Prop:** If you used the `label` prop, add a separate `<Label>` component before the `<Input>`.
|
||||
3. **Handle Layout:** Wrap the `<Label>` and `<Input>` in a container `div` and use layout utilities (e.g., `flex`, `items-center`, `space-x-2`, `flex-col`) to position them correctly.
|
||||
4. **Transfer Styling:** Migrate styles from `className`, `labelClassName`, and `containerClassName` to the `className` prop of the new `Input`, `Label`, and container `div` as appropriate.
|
||||
|
||||
*Example Diff (Conceptual - derived from PanelPetSUV):*
|
||||
|
||||
```diff
|
||||
- <Input
|
||||
- containerClassName={'flex flex-row justify-between items-center'}
|
||||
- label={'Weight'}
|
||||
- labelChildren={<span className="text-aqua-pale"> kg</span>}
|
||||
- labelClassName="text-[13px] text-white"
|
||||
- className="h-[26px] w-[117px]"
|
||||
- value={metadata.PatientWeight || ''}
|
||||
- onChange={handleWeightChange}
|
||||
- />
|
||||
|
||||
+ <div className="flex flex-row items-center space-x-4"> {/* Replaced containerClassName */}
|
||||
+ <Label className="min-w-32 flex-shrink-0 text-[13px] text-white"> {/* Replaced labelClassName */}
|
||||
+ Weight
|
||||
+ <span className="text-muted-foreground"> kg</span> {/* Replaced labelChildren */}
|
||||
+ </Label>
|
||||
+ <Input
|
||||
+ className="h-7 flex-1 h-[26px] w-[117px]" {/* Merged input className */}
|
||||
+ value={metadata.PatientWeight || ''}
|
||||
+ onChange={handleWeightChange}
|
||||
+ />
|
||||
+ </div>
|
||||
```
|
||||
|
||||
|
||||
## `InputNumber` > `Numeric.NumberStepper`
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* The `InputNumber` component is replaced by the `Numeric` component system using `mode="stepper"`.
|
||||
* Styling props like `sizeClassName`, `arrowsDirection`, and `labelPosition` are removed. Layout and styling are now controlled via standard `className` and parent container layouts (e.g., Flexbox).
|
||||
* Props like `value`, `onChange`, `minValue`, `maxValue`, and `step` are now typically set on the `Numeric.Container`.
|
||||
* Labels are handled by the separate `Numeric.Label` subcomponent.
|
||||
* Stepper controls are provided by the `Numeric.NumberStepper` subcomponent, which takes a `direction` prop (`horizontal` or `vertical`).
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Replace Component:** Replace `<InputNumber ... />` with `<Numeric.Container mode="stepper" ... >`.
|
||||
2. **Transfer Props:** Move `value`, `onChange`, `minValue` (as `min`), `maxValue` (as `max`), and `step` props to the `Numeric.Container`.
|
||||
3. **Add Subcomponents:**
|
||||
* Inside `Numeric.Container`, add `<Numeric.NumberStepper />`. Set its `direction` prop (`horizontal` or `vertical`) based on the old `arrowsDirection`. Apply sizing classes directly using `className`.
|
||||
* Add a `<Numeric.Label>` component for the label text.
|
||||
4. **Handle Layout:** Wrap the `Numeric.Container` or arrange its children using standard layout techniques (like Flexbox) to achieve the desired positioning (equivalent to the old `labelPosition`). Apply styling classes as needed.
|
||||
|
||||
*Example Diff:*
|
||||
|
||||
```diff
|
||||
- <InputNumber
|
||||
- value={currentFrameIndex}
|
||||
- onChange={onFrameChange}
|
||||
- minValue={0}
|
||||
- maxValue={framesLength - 1}
|
||||
- label="Frame"
|
||||
- sizeClassName="w-[58px] h-[28px]"
|
||||
- arrowsDirection="horizontal"
|
||||
- labelPosition="bottom"
|
||||
- />
|
||||
|
||||
+ <Numeric.Container
|
||||
+ mode="stepper"
|
||||
+ value={currentDimensionGroupNumber || 1}
|
||||
+ onChange={onDimensionGroupChange || (() => {})}
|
||||
+ min={1}
|
||||
+ max={numDimensionGroups || 1}
|
||||
+ step={1}
|
||||
+ >
|
||||
+ <div className="flex flex-col items-center">
|
||||
+ <Numeric.NumberStepper
|
||||
+ className="h-[28px] w-[58px]"
|
||||
+ direction="horizontal"
|
||||
+ />
|
||||
+ <Numeric.Label className="text-muted-foreground mt-1 text-sm">Frame</Numeric.Label>
|
||||
+ </div>
|
||||
+ </Numeric.Container>
|
||||
```
|
||||
|
||||
|
||||
## `InputRange` > `Numeric.SingleRange`
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* `InputRange` is replaced by the `Numeric` component system using `mode="singleRange"`.
|
||||
* Props like `value`, `onChange`, `minValue` (`min`), `maxValue` (`max`), and `step` are set on the `Numeric.Container`.
|
||||
* The slider element itself is rendered using `<Numeric.SingleRange />`.
|
||||
* The `showLabel` prop is replaced by explicitly adding a `<Numeric.Label>` subcomponent. The label text is passed as children to `Numeric.Label`. You can optionally show the current value(s) within the label using the `showValue` prop on `Numeric.Label`.
|
||||
* The `allowNumberEdit` prop is replaced by the `showNumberInput` prop on `<Numeric.SingleRange />`.
|
||||
* Layout props like `labelPosition` are removed; use standard CSS/Tailwind for layout.
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Replace Component:** Replace `<InputRange ... />` with `<Numeric.Container mode="singleRange" ... >`.
|
||||
2. **Transfer Props:** Move `value`, `onChange`, `minValue` (as `min`), `maxValue` (as `max`), and `step` to the `Numeric.Container`.
|
||||
3. **Add Subcomponents:**
|
||||
* Inside, add `<Numeric.SingleRange />`.
|
||||
* Use the `showNumberInput` prop on the range subcomponent if number editing was previously enabled (`allowNumberEdit={true}`).
|
||||
* If `showLabel` was true, add a `<Numeric.Label>` component. Pass the label text as children. Use the `showValue` prop on the label if you want to display the numeric value alongside the text.
|
||||
4. **Handle Layout:** Arrange the `Numeric.Label` and the Range subcomponent using standard layout techniques (Flexbox, Grid) as needed. Apply styling directly using `className`.
|
||||
|
||||
*Example Diff (Conceptual):*
|
||||
|
||||
```diff
|
||||
- <InputRange
|
||||
- value={opacity}
|
||||
- onChange={setOpacity}
|
||||
- minValue={0}
|
||||
- maxValue={100}
|
||||
- step={1}
|
||||
- showLabel={true}
|
||||
- label="Opacity"
|
||||
- allowNumberEdit={true}
|
||||
- />
|
||||
|
||||
+ <Numeric.Container
|
||||
+ mode="singleRange"
|
||||
+ value={opacity}
|
||||
+ onChange={setOpacity}
|
||||
+ min={0}
|
||||
+ max={100}
|
||||
+ step={1}
|
||||
+ >
|
||||
+ <div className="flex items-center space-x-2"> {/* Example layout */}
|
||||
+ <Numeric.Label showValue>Opacity</Numeric.Label>
|
||||
+ <Numeric.SingleRange showNumberInput />
|
||||
+ </div>
|
||||
+ </Numeric.Container>
|
||||
```
|
||||
|
||||
|
||||
## `InputDoubleRange` > `Numeric.DoubleRange`
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* `InputDoubleRange` is replaced by the `Numeric` component system using `mode="doubleRange"`.
|
||||
* Props like `values`, `onChange`, `minValue` (`min`), `maxValue` (`max`), and `step` are set on the `Numeric.Container`.
|
||||
* The slider element itself is rendered using `<Numeric.DoubleRange />`.
|
||||
* The `showLabel` prop is replaced by explicitly adding a `<Numeric.Label>` subcomponent. You can optionally show the current values within the label using the `showValue` prop on `Numeric.Label`.
|
||||
* Editing numbers is controlled by the `showNumberInputs` (plural) prop on `<Numeric.DoubleRange />`.
|
||||
* Layout props like `labelPosition` are removed; use standard CSS/Tailwind for layout.
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Replace Component:** Replace `<InputDoubleRange ... />` with `<Numeric.Container mode="doubleRange" ... >`.
|
||||
2. **Transfer Props:** Move `values`, `onChange`, `minValue` (as `min`), `maxValue` (as `max`), and `step` to the `Numeric.Container`.
|
||||
3. **Add Subcomponents:**
|
||||
* Inside, add `<Numeric.DoubleRange />`.
|
||||
* Use the `showNumberInputs` prop on the range subcomponent if number editing is desired.
|
||||
* If `showLabel` was true, add a `<Numeric.Label>` component. Pass the label text as children. Use the `showValue` prop on the label if you want to display the numeric values alongside the text.
|
||||
4. **Handle Layout:** Arrange the `Numeric.Label` and the Range subcomponent using standard layout techniques (Flexbox, Grid) as needed.
|
||||
|
||||
*Example Diff:*
|
||||
|
||||
```diff
|
||||
- <InputDoubleRange
|
||||
- values={rangeValues}
|
||||
- onChange={handleSliderChange}
|
||||
- minValue={1}
|
||||
- maxValue={numDimensionGroups || 1}
|
||||
- showLabel={false} // Assuming label wasn't shown, or handled separately
|
||||
- step={1}
|
||||
- // Assuming number edit might have been implicitly enabled or desired
|
||||
- />
|
||||
|
||||
+ <Numeric.Container
|
||||
+ mode="doubleRange"
|
||||
+ min={1}
|
||||
+ max={numDimensionGroups || 1}
|
||||
+ values={rangeValues || [1, numDimensionGroups || 1]}
|
||||
+ onChange={onDoubleRangeChange || (() => {})}
|
||||
+ >
|
||||
+ {/* Label could be added here if needed */}
|
||||
+ {/* <Numeric.Label>Range</Numeric.Label> */}
|
||||
+ <Numeric.DoubleRange showNumberInputs />
|
||||
+ </Numeric.Container>
|
||||
```
|
||||
|
||||
|
||||
## InputFilterText > InputFilter
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* `InputFilterText` is replaced by the more composable `InputFilter` component from `@ohif/ui-next`.
|
||||
* `InputFilter` uses subcomponents (`InputFilter.SearchIcon`, `InputFilter.Input`, `InputFilter.ClearButton`) which are included by default but can be customized.
|
||||
* The `onDebounceChange` prop is replaced by a standard `onChange` prop on the main `InputFilter` component, which handles debouncing internally (configurable via `debounceTime`).
|
||||
* Props like `placeholder` and `value` are passed to the `InputFilter.Input` subcomponent (or directly to `InputFilter` for simplicity if using the default structure).
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Replace Component:** Replace `<InputFilterText ... />` with `<InputFilter ... >`.
|
||||
2. **Transfer Props:**
|
||||
* Move `placeholder` to the `InputFilter` component or its `InputFilter.Input` subcomponent.
|
||||
* Handle `value` using controlled state if necessary, passing it to `InputFilter`.
|
||||
3. **Update Handler:** Replace the `onDebounceChange` handler with the `onChange` prop on the `InputFilter` component.
|
||||
4. **Styling:** Apply necessary classes for layout and positioning, especially padding on the input (e.g., `pl-9 pr-9`) to accommodate the default icon and clear button if using the defaults.
|
||||
|
||||
*Example Diff:*
|
||||
|
||||
```diff
|
||||
- <InputFilterText
|
||||
- value={searchValue}
|
||||
- onDebounceChange={handleSearchChange}
|
||||
- placeholder={'Search all'}
|
||||
- />
|
||||
|
||||
+ <InputFilter
|
||||
+ value={searchValue}
|
||||
+ onChange={setFilterValue} /* Direct state update or debounced handler */
|
||||
+ placeholder="Search all"
|
||||
+ >
|
||||
+ {/* Using default structure which includes Icon, Input, ClearButton */}
|
||||
+ {/* Example customization: */}
|
||||
+ {/* <InputFilter.SearchIcon /> */}
|
||||
+ {/* <InputFilter.Input placeholder="Search all" className="pl-9 pr-9" /> */}
|
||||
+ {/* <InputFilter.ClearButton /> */}
|
||||
+ </InputFilter>
|
||||
```
|
||||
|
||||
## InputGroup / InputLabelWrapper > Composition
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* These wrapper components (`InputGroup`, `InputLabelWrapper`) are deprecated.
|
||||
* Functionality (grouping label and input, optional sorting indicators) is now achieved through composition using standard layout techniques (Flexbox/Grid) and the base `@ohif/ui-next` components (`Label`, `Input`, `Icons`).
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Remove Wrapper:** Delete the `<InputGroup>` or `<InputLabelWrapper>` tags.
|
||||
2. **Create Container:** Use a standard `div` as the container.
|
||||
3. **Add Label and Input:** Place the `<Label>` and the corresponding input/control component (e.g., `<Input>`, `<Select>`, custom component) inside the `div`.
|
||||
4. **Apply Layout:** Use Tailwind classes (`flex`, `grid`, `space-x-*`, `items-center`, etc.) on the container `div` to arrange the label and input as needed.
|
||||
5. **Add Sorting Icons (if applicable):** If migrating from `InputLabelWrapper` with `isSortable`, manually add the appropriate `<Icons.ByName name="sorting-..." />` component next to the label text within the `<Label>` component's children. Import `Icons` from `@ohif/ui-next`.
|
||||
6. **Apply Styling:** Add necessary styling classes directly to the `Label`, input component, and container `div`.
|
||||
|
||||
*Example Diff (Conceptual - derived from InputLabelWrapper usage):*
|
||||
|
||||
```diff
|
||||
- <InputLabelWrapper
|
||||
- label="Patient Name"
|
||||
- isSortable={true}
|
||||
- sortDirection={sortDir}
|
||||
- onLabelClick={toggleSort}
|
||||
- >
|
||||
- <Input value={patientName} onChange={setPatientName} />
|
||||
- </InputLabelWrapper>
|
||||
|
||||
+ <div className="flex flex-col space-y-1"> {/* Example layout */}
|
||||
+ <Label
|
||||
+ onClick={toggleSort}
|
||||
+ className="flex cursor-pointer items-center"
|
||||
+ >
|
||||
+ Patient Name
|
||||
+ {sortDir === 'ascending' && <Icons.ByName name="sorting-ascending" className="ml-1 h-4 w-4" />}
|
||||
+ {sortDir === 'descending' && <Icons.ByName name="sorting-descending" className="ml-1 h-4 w-4" />}
|
||||
+ {sortDir === 'none' && <Icons.ByName name="sorting" className="ml-1 h-4 w-4" />}
|
||||
+ </Label>
|
||||
+ <Input value={patientName} onChange={setPatientName} />
|
||||
+ </div>
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Summary of Changes
|
||||
|
||||
| Old Component | New Component/Pattern Equivalent | Notes |
|
||||
|-----------------------|----------------------------------------------------|----------------------------------------------------------------------------|
|
||||
| `<Input type="number">` | `<Numeric.NumberInput>` | Use `Numeric.Container` with `mode="number"` |
|
||||
| `<Input>` / `<InputText>` | `@ohif/ui-next <Input>` + `<Label>` | Use standard `<div>` and CSS/Tailwind for layout |
|
||||
| `<InputNumber>` | `<Numeric.NumberStepper>` | Use `Numeric.Container` with `mode="stepper"` |
|
||||
| `<InputRange>` | `<Numeric.SingleRange>` | Use `Numeric.Container` with `mode="singleRange"` |
|
||||
| `<InputDoubleRange>` | `<Numeric.DoubleRange>` | Use `Numeric.Container` with `mode="doubleRange"` |
|
||||
| `<InputFilterText>` | `@ohif/ui-next <InputFilter>` | Composable component with internal debouncing |
|
||||
| `<InputGroup>` | Composition (`div`, `<Label>`, `<Input>`, etc.) | Replaced by standard layout techniques |
|
||||
| `<InputLabelWrapper>` | Composition (`div`, `<Label>`, `<Input>`, `<Icons>`) | Replaced by standard layout techniques; manually add sort icons if needed |
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
---
|
||||
title: Tooltip
|
||||
---
|
||||
|
||||
## Tooltip Updates
|
||||
|
||||
### Changes:
|
||||
- Updated Tooltip structure to use `Tooltip`, `TooltipTrigger`, and `TooltipContent`.
|
||||
- Removed deprecated `TooltipClipboard` and inline `content`/`position` properties.
|
||||
|
||||
### Migration Steps:
|
||||
1. Replace imports:
|
||||
```tsx
|
||||
// Before
|
||||
import { Tooltip } from '@ohif/ui';
|
||||
import { TooltipClipboard } from '@ohif/ui';
|
||||
|
||||
// After
|
||||
import { Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
|
||||
```
|
||||
|
||||
2. Update Tooltip usage:
|
||||
```tsx
|
||||
// Before
|
||||
<Tooltip content={<div>Tooltip Message</div>} position="bottom-left">
|
||||
<Component />
|
||||
</Tooltip>
|
||||
|
||||
// After
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Component />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
Tooltip Message
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
```
|
||||
|
||||
|
||||
3. TooltipClipboard Replacement:
|
||||
The `TooltipClipboard` component has been removed. Instead, use the `Clipboard` component inside `TooltipContent` for copying text functionality.
|
||||
|
||||
#### Before:
|
||||
```tsx
|
||||
<TooltipClipboard>{text}</TooltipClipboard>
|
||||
```
|
||||
|
||||
#### After:
|
||||
```tsx
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="cursor-pointer truncate">{text}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
{text}
|
||||
<Clipboard>{text}</Clipboard>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
```
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
---
|
||||
title: Tooltip
|
||||
---
|
||||
|
||||
|
||||
|
||||
This guide outlines the steps needed to migrate from the previous `<Select>` component (likely from `@ohif/ui`) to the new compound `<Select>` component provided by `@ohif/ui-next`.
|
||||
|
||||
## Key Changes
|
||||
|
||||
* **Deprecated Component:** The previous standalone `<Select>` component is deprecated.
|
||||
* **New Compound Component:** The new implementation uses a compound component pattern, requiring multiple specific sub-components (`Select`, `SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`).
|
||||
* **Option Definition:** Options are no longer passed as a single `options` prop. Instead, each option is rendered as an individual `<SelectItem>` component within `<SelectContent>`.
|
||||
* **Placeholder:** The `placeholder` prop is now applied to the `<SelectValue>` sub-component.
|
||||
* **Value Handling:** The `value` and `onValueChange` props are now managed by the root `<Select>` component. Note the change from `onChange` to `onValueChange`.
|
||||
|
||||
## Migration Steps
|
||||
|
||||
1. **Update Imports:**
|
||||
Replace the import for the old `Select` component with imports for the new compound components from `@ohif/ui-next`.
|
||||
|
||||
```diff
|
||||
- import { Select } from '@ohif/ui';
|
||||
+ import {
|
||||
+ Select,
|
||||
+ SelectContent,
|
||||
+ SelectItem,
|
||||
+ SelectTrigger,
|
||||
+ SelectValue,
|
||||
+ } from '@ohif/ui-next';
|
||||
|
||||
```
|
||||
|
||||
2. **Adapt Component Structure:**
|
||||
Replace the single `<Select>` tag with the new compound structure. Map your existing `options` array to individual `<SelectItem>` components.
|
||||
|
||||
*Example Diff:*
|
||||
|
||||
```diff
|
||||
- <Select
|
||||
- label={t('Strategy')}
|
||||
- closeMenuOnSelect={true}
|
||||
- className="border-primary-main mr-2 bg-black text-white"
|
||||
- options={options}
|
||||
- placeholder={options.find(option => option.value === config.strategy).placeHolder}
|
||||
- value={config.strategy}
|
||||
- onChange={({ value }) => {
|
||||
- dispatch({
|
||||
- type: 'setStrategy',
|
||||
- payload: {
|
||||
- strategy: value,
|
||||
- },
|
||||
- });
|
||||
- }}
|
||||
- />
|
||||
|
||||
+ <Select
|
||||
+ value={config.strategy}
|
||||
+ onValueChange={value => {
|
||||
+ dispatch({
|
||||
+ type: 'setStrategy',
|
||||
+ payload: {
|
||||
+ strategy: value,
|
||||
+ },
|
||||
+ });
|
||||
+ }}
|
||||
+ >
|
||||
+ <SelectTrigger className="w-full">
|
||||
+ <SelectValue
|
||||
+ placeholder={options.find(option => option.value === config.strategy)?.placeHolder}
|
||||
+ />
|
||||
+ </SelectTrigger>
|
||||
+ <SelectContent className="">
|
||||
+ {options.map(option => (
|
||||
+ <SelectItem
|
||||
+ key={option.value}
|
||||
+ value={option.value}
|
||||
+ >
|
||||
+ {option.label}
|
||||
+ </SelectItem>
|
||||
+ ))}
|
||||
+ </SelectContent>
|
||||
+ </Select>
|
||||
```
|
||||
|
||||
**Explanation:**
|
||||
* The main logic container is now the root `<Select>` component, which takes the `value` and the `onValueChange` handler (note: `onValueChange` directly receives the *value*, not an event object).
|
||||
* `<SelectTrigger>` wraps the element that opens the dropdown (often styled like the previous select input).
|
||||
* `<SelectValue>` displays the currently selected value or the `placeholder` text if no value is selected.
|
||||
* `<SelectContent>` contains the list of options.
|
||||
* Each option is rendered using `<SelectItem>`, where the `value` prop corresponds to the option's value and the children (`{option.label}`) represent the text displayed for that option.
|
||||
* Props like `closeMenuOnSelect` are generally handled by default in the new component.
|
||||
|
||||
3. **Adjust Styling:**
|
||||
The internal structure and default styling have changed. Remove or update previous CSS class names (`className`) applied to the old component and apply new Tailwind/CSS classes to the appropriate sub-components (`Select`, `SelectTrigger`, `SelectContent`, `SelectItem`) as needed to match your desired appearance. Note that `border-primary-main` and `bg-black` might no longer be necessary or applied differently with the new component's structure and variants.
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
---
|
||||
title: Switch
|
||||
---
|
||||
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* **Component Renaming & Import Path:** The component `SwitchButton` from `@ohif/ui` has been replaced by `Switch` in `@ohif/ui-next`. You will need to update your import statements accordingly.
|
||||
* **Removal of `label` Prop:** The integrated `label` prop has been removed. Labels should now be implemented externally using standard HTML elements (like `<span>` or `<label>`) or the `<Label>` component from `@ohif/ui-next`. Layout between the label and the `Switch` needs to be handled explicitly, typically using Flexbox utility classes.
|
||||
* **Event Handler Prop Renamed:** The `onChange` event handler prop has been replaced with `onCheckedChange`. The new prop provides the updated boolean `checked` state directly as its argument.
|
||||
* **Styling and Layout:** The new `Switch` component relies on standard `className` prop and Tailwind utility classes for styling and layout adjustments, rather than internal props or structures.
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Update Import Statement:**
|
||||
Change the import from `@ohif/ui` to `@ohif/ui-next` and rename the component.
|
||||
|
||||
```diff
|
||||
- import { SwitchButton } from '@ohif/ui';
|
||||
+ import { Switch } from '@ohif/ui-next';
|
||||
+ import { Label } from '@ohif/ui-next'; // Optional: If using the Label component
|
||||
```
|
||||
|
||||
2. **Replace Component Usage and Handle Label Externally:**
|
||||
Replace the `<SwitchButton>` tag with `<Switch>`. Remove the `label` prop and add an external element for the label. Use layout utilities (like `flex`) to position the label relative to the switch.
|
||||
|
||||
*Example Diff:*
|
||||
```diff
|
||||
- <SwitchButton
|
||||
- label="Enable Feature"
|
||||
- checked={isFeatureEnabled}
|
||||
- onChange={handleToggle}
|
||||
- />
|
||||
|
||||
+ <div className="flex items-center space-x-2">
|
||||
+ <Switch
|
||||
+ id="feature-toggle" // It's good practice to add an id
|
||||
+ checked={isFeatureEnabled}
|
||||
+ onCheckedChange={handleToggle}
|
||||
+ />
|
||||
+ <Label htmlFor="feature-toggle">Enable Feature</Label> {/* Or use a <span> */}
|
||||
+ </div>
|
||||
```
|
||||
*Explanation:* The `label` prop is gone. A `<div>` with `flex` is used to arrange the new `<Switch>` and an associated `<Label>`. The `htmlFor` attribute on the `<Label>` links it to the `<Switch>` via its `id` for accessibility.
|
||||
|
||||
3. **Update Event Handler Prop:**
|
||||
Rename the `onChange` prop to `onCheckedChange`. Ensure your handler function correctly receives the new boolean state as its argument.
|
||||
|
||||
*Example Diff (within the component usage):*
|
||||
```diff
|
||||
- onChange={checked => setIsEnabled(checked)}
|
||||
+ onCheckedChange={checked => setIsEnabled(checked)}
|
||||
```
|
||||
*Explanation:* The prop name changes from `onChange` to `onCheckedChange`. The callback function signature, receiving the boolean `checked` state, remains a common pattern and is directly supported by `onCheckedChange`. If your previous `onChange` did *not* receive the checked state directly (e.g., it just toggled existing state), you might need to adjust your handler logic slightly, but `onCheckedChange` directly provides the new state.
|
||||
+433
@@ -0,0 +1,433 @@
|
||||
---
|
||||
title: Toolbar
|
||||
---
|
||||
|
||||
# Toolbar
|
||||
|
||||
## New Toolbar uiType
|
||||
|
||||
We have two new toolbar button types: `ohif.toolButtonList` and `ohif.toolButton`, which are intended to replace the `ohif.radioGroup` and `ohif.splitButton` types.
|
||||
|
||||
Note that these are backward compatible, so if you are not ready to pick up the new ui types (which are more flexible and powerful), you can continue using the old types.
|
||||
|
||||
|
||||
```js
|
||||
// Old type
|
||||
{
|
||||
uiType: 'ohif.radioGroup',
|
||||
}
|
||||
|
||||
// New type
|
||||
{
|
||||
uiType: 'ohif.toolButton',
|
||||
}
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```js
|
||||
// Old type
|
||||
{
|
||||
uiType: 'ohif.splitButton',
|
||||
}
|
||||
|
||||
// New type
|
||||
{
|
||||
uiType: 'ohif.toolButtonList',
|
||||
}
|
||||
```
|
||||
|
||||
The `ohif.buttonGroup` and `ohif.radioGroup` types used in the Toolbox have been replaced with `ohif.toolBoxButtonGroup` and `ohif.toolBoxButton` to reflect their usage in the Toolbox, which has distinct styling.
|
||||
|
||||
```js
|
||||
// Old type
|
||||
{
|
||||
uiType: 'ohif.buttonGroup',
|
||||
}
|
||||
|
||||
// New type
|
||||
{
|
||||
uiType: 'ohif.toolBoxButtonGroup',
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```js
|
||||
// Old type
|
||||
{
|
||||
uiType: 'ohif.radioGroup',
|
||||
}
|
||||
|
||||
// New type
|
||||
{
|
||||
uiType: 'ohif.toolBoxButton',
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## getToolbarModule
|
||||
|
||||
|
||||
The `getToolbarModule` function previously returned `disabled`, `disabledText`, and `className` as part of its evaluation process for the button state. These properties will still be returned, but common class names are now handled internally by the new UI button components, including `ToolButton`, `ToolButtonList`, `Toolbox`, and `ToolBoxGroup`. You can override the `className` if you need to.
|
||||
|
||||
|
||||
## Tool Definitions: Moving to Section-Based Definitions
|
||||
|
||||
This migration represents a significant step toward a more extension-based toolbar system. We've moved away from the nested primary/items structure in favor of a flatter, more composable section-based approach.
|
||||
|
||||
### Deprecated: Nested Toolbar Structures
|
||||
|
||||
```diff
|
||||
- {
|
||||
- id: 'MeasurementTools',
|
||||
- uiType: 'ohif.toolButtonList',
|
||||
- props: {
|
||||
- groupId: 'MeasurementTools',
|
||||
- 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({ ... }),
|
||||
- createButton({ ... }),
|
||||
- // More nested buttons
|
||||
- ],
|
||||
- },
|
||||
- }
|
||||
```
|
||||
|
||||
### New Approach: Section-Based Definitions
|
||||
|
||||
```diff
|
||||
+ // 1. Define the toolbar section container
|
||||
+ {
|
||||
+ id: 'MeasurementTools',
|
||||
+ uiType: 'ohif.toolButtonList',
|
||||
+ props: {
|
||||
+ buttonSection: 'measurementSection',
|
||||
+ groupId: 'MeasurementTools',
|
||||
+ },
|
||||
+ },
|
||||
+ // 2. Register individual buttons separately
|
||||
+ {
|
||||
+ id: 'Length',
|
||||
+ uiType: 'ohif.toolButton',
|
||||
+ props: {
|
||||
+ icon: 'tool-length',
|
||||
+ label: 'Length',
|
||||
+ tooltip: 'Length Tool',
|
||||
+ commands: setToolActiveToolbar,
|
||||
+ evaluate: 'evaluate.cornerstoneTool',
|
||||
+ },
|
||||
+ },
|
||||
+ {
|
||||
+ id: 'Bidirectional',
|
||||
+ uiType: 'ohif.toolButton',
|
||||
+ props: {
|
||||
+ icon: 'tool-bidirectional',
|
||||
+ label: 'Bidirectional',
|
||||
+ tooltip: 'Bidirectional Tool',
|
||||
+ commands: setToolActiveToolbar,
|
||||
+ evaluate: 'evaluate.cornerstoneTool',
|
||||
+ },
|
||||
+ },
|
||||
```
|
||||
|
||||
and then in your mode you can compose the section and associate buttons
|
||||
|
||||
```diff
|
||||
+ // 3. In your mode, create the section and associate buttons
|
||||
+ toolbarService.createButtonSection('primary', [
|
||||
+ 'MeasurementTools',
|
||||
+ 'Pan',
|
||||
+ 'Zoom',
|
||||
+ ]);
|
||||
+
|
||||
+
|
||||
+ toolbarService.createButtonSection('measurementSection', [
|
||||
+ 'Length',
|
||||
+ 'Bidirectional',
|
||||
+ 'ArrowAnnotate',
|
||||
+ 'EllipticalROI',
|
||||
+ ]);
|
||||
```
|
||||
|
||||
:::note
|
||||
The `measurementSection` is defined in the tool button configuration of the `MeasurementTools` button.
|
||||
:::
|
||||
|
||||
|
||||
### Group Evaluators Deprecated
|
||||
|
||||
Group evaluator functions like `evaluate.group.promoteToPrimaryIfCornerstoneToolNotActiveInTheList` are now deprecated. Instead, the `uiType` component itself is responsible for grouping and displaying the buttons from a section as needed. This allows for more flexible UI implementations that aren't tied to specific evaluation logic.
|
||||
|
||||
|
||||
|
||||
## ToolBox
|
||||
|
||||
Previously, the segmentation toolbox was not using an `evaluator` property. This is now taken into account
|
||||
|
||||
|
||||
### evaluators in Toolbox
|
||||
|
||||
```js
|
||||
// old
|
||||
{
|
||||
id: 'BrushTools',
|
||||
uiType: 'ohif.buttonGroup',
|
||||
props: {
|
||||
groupId: 'BrushTools',
|
||||
}
|
||||
}
|
||||
|
||||
// now
|
||||
{
|
||||
id: 'BrushTools',
|
||||
uiType: 'ohif.buttonGroup',
|
||||
props: {
|
||||
groupId: 'BrushTools',
|
||||
evaluate: 'evaluate.cornerstone.hasSegmentation',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Replace Toolbox imports from ui-next with extension-default
|
||||
|
||||
```diff
|
||||
- import { Toolbox } from '@ohif/ui-next';
|
||||
+ import { Toolbox } from '@ohif/extension-default';
|
||||
```
|
||||
|
||||
|
||||
Ensure you're importing the Toolbox component from the correct location:
|
||||
|
||||
```javascript
|
||||
// New import pattern
|
||||
import { Toolbox } from '@ohif/extension-default';
|
||||
|
||||
// Usage remains similar
|
||||
<Toolbox
|
||||
servicesManager={servicesManager}
|
||||
buttonSectionId="segmentation"
|
||||
title="Segmentation Tools"
|
||||
/>
|
||||
```
|
||||
|
||||
### Stacked Sections in Toolbox
|
||||
|
||||
The new Toolbox component supports stacked sections, which allows for more complex UI organization. Instead of flat button groups, you can now create deep hierarchies of tool sections and subsections.
|
||||
|
||||
|
||||
Previously you were able to have something like this
|
||||
|
||||
```js
|
||||
// old
|
||||
// buttons for BrushTools were a giant group of buttons
|
||||
const buttons = {
|
||||
id: 'BrushTools',
|
||||
uiType: 'ohif.toolBoxButtonGroup',
|
||||
props: {
|
||||
groupId: 'BrushTools',
|
||||
evaluate: 'evaluate.cornerstone.hasSegmentation',
|
||||
items: [
|
||||
{
|
||||
id: 'Brush',
|
||||
icon: 'icon-tool-brush',
|
||||
label: 'Brush',
|
||||
evaluate: {
|
||||
// ...
|
||||
},
|
||||
options: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'Eraser',
|
||||
icon: 'icon-tool-eraser',
|
||||
label: 'Eraser',
|
||||
evaluate: {
|
||||
// ...
|
||||
},
|
||||
options: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'Threshold',
|
||||
icon: 'icon-tool-threshold',
|
||||
label: 'Threshold Tool',
|
||||
evaluate: {
|
||||
// ...
|
||||
},
|
||||
options: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'Shapes',
|
||||
uiType: 'ohif.toolBoxButton',
|
||||
props: {
|
||||
id: 'Shapes',
|
||||
icon: 'icon-tool-shape',
|
||||
label: 'Shapes',
|
||||
evaluate: {
|
||||
// ...
|
||||
},
|
||||
options: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
toolbarService.addButtons(buttons);
|
||||
|
||||
toolbarService.createButtonSection('segmentationToolbox', ['BrushTools', 'Shapes']);
|
||||
```
|
||||
|
||||
But now you should have at least one section defined in your toolbar buttons
|
||||
|
||||
|
||||
```js
|
||||
// separate flat definitions for each button and each section
|
||||
const buttons = [
|
||||
{
|
||||
id: 'Brush',
|
||||
uiType: 'ohif.toolButton',
|
||||
props: {
|
||||
icon: 'icon-tool-brush',
|
||||
label: 'Brush',
|
||||
evaluate: {
|
||||
// ...
|
||||
},
|
||||
options: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'Eraser',
|
||||
uiType: 'ohif.toolButton',
|
||||
props: {
|
||||
icon: 'icon-tool-eraser',
|
||||
label: 'Eraser',
|
||||
evaluate: {
|
||||
// ...
|
||||
},
|
||||
options: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'Threshold',
|
||||
uiType: 'ohif.toolButton',
|
||||
props: {
|
||||
icon: 'icon-tool-threshold',
|
||||
label: 'Threshold Tool',
|
||||
evaluate: {
|
||||
// ...
|
||||
},
|
||||
options: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'Shapes',
|
||||
uiType: 'ohif.toolBoxButton',
|
||||
props: {
|
||||
icon: 'icon-tool-shape',
|
||||
label: 'Shapes',
|
||||
evaluate: {
|
||||
name: 'evaluate.cornerstone.segmentation',
|
||||
toolNames: ['CircleScissor', 'SphereScissor', 'RectangleScissor'],
|
||||
disabledText: 'Create new segmentation to enable shapes tool.',
|
||||
},
|
||||
options: [
|
||||
// ...
|
||||
],
|
||||
},
|
||||
},
|
||||
// Sections
|
||||
{
|
||||
id: 'SegmentationTools',
|
||||
uiType: 'ohif.toolBoxButton',
|
||||
props: {
|
||||
groupId: 'SegmentationTools',
|
||||
buttonSection: 'segmentationToolboxToolsSection',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'BrushTools',
|
||||
uiType: 'ohif.toolBoxButtonGroup',
|
||||
props: {
|
||||
groupId: 'BrushTools',
|
||||
buttonSection: 'brushToolsSection',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
toolbarService.addButtons(buttons);
|
||||
```
|
||||
|
||||
and then
|
||||
|
||||
```js
|
||||
// Step 2: Create the section hierarchy
|
||||
// Top level toolbox section
|
||||
toolbarService.createButtonSection('segmentationToolbox', ['SegmentationTools']);
|
||||
|
||||
// Next level - subsections within the toolbox
|
||||
toolbarService.createButtonSection('segmentationToolboxToolsSection', ['BrushTools', 'Shapes']);
|
||||
|
||||
// Lowest level - buttons within a subsection
|
||||
toolbarService.createButtonSection('brushToolsSection', ['Brush', 'Eraser', 'Threshold']);
|
||||
```
|
||||
|
||||
|
||||
### Remove ToolboxProvider from composition root
|
||||
|
||||
If you have the ToolboxProvider in your application composition, remove it:
|
||||
|
||||
```diff
|
||||
// In App.tsx or similar
|
||||
const appComposition = [
|
||||
[ThemeWrapperNext],
|
||||
[ThemeWrapper],
|
||||
[SystemContextProvider, { commandsManager, extensionManager, hotkeysManager, servicesManager }],
|
||||
- [ToolboxProvider],
|
||||
[ViewportGridProvider, { service: viewportGridService }],
|
||||
// Other providers...
|
||||
];
|
||||
```
|
||||
|
||||
we now keep the state for toolbar inside the ToolbarService itself
|
||||
|
||||
### 3. Update tool option handlers to use onChange instead of commands
|
||||
|
||||
```diff
|
||||
- <RowSegmentedControl
|
||||
- key={option.id}
|
||||
- option={option}
|
||||
- />
|
||||
|
||||
+ <RowSegmentedControl
|
||||
+ key={option.id}
|
||||
+ option={option}
|
||||
+ onChange={option.onChange}
|
||||
+ />
|
||||
```
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
---
|
||||
title: Segmentation Table
|
||||
---
|
||||
|
||||
|
||||
# SegmentationTable Migration Guide
|
||||
|
||||
This guide will help you migrate your code to use the refactored SegmentationTable component.
|
||||
|
||||
## Key Changes
|
||||
|
||||
* **Context System Refactoring**: The context system has been completely redesigned with dedicated providers for different aspects of the segmentation UI
|
||||
* **Compound Component Pattern**: Components now follow a more structured compound component pattern with clearer parent-child relationships
|
||||
* **Header Components Changed**: The `SegmentationTable.SelectorHeader` has been replaced with specialized header components
|
||||
* **New Features**: Added segment statistics, hover cards, and better customization options
|
||||
* **Better Customization**: Added support for custom dropdown menus and segment statistics headers
|
||||
|
||||
## Migration Steps
|
||||
|
||||
|
||||
|
||||
### 1. Migrate from SelectorHeader to New Header Components
|
||||
|
||||
The `SegmentationTable.SelectorHeader` has been removed. Use the new Collapsed pattern instead:
|
||||
|
||||
```diff
|
||||
- <SegmentationTable.SelectorHeader>
|
||||
- <CustomDropdownMenuContent />
|
||||
- </SegmentationTable.SelectorHeader>
|
||||
|
||||
+ <SegmentationTable.Collapsed>
|
||||
+ <SegmentationTable.Collapsed.Header>
|
||||
+ <SegmentationTable.Collapsed.DropdownMenu>
|
||||
+ <CustomDropdownMenuContent />
|
||||
+ </SegmentationTable.Collapsed.DropdownMenu>
|
||||
+ <SegmentationTable.Collapsed.Selector />
|
||||
+ <SegmentationTable.Collapsed.Info />
|
||||
+ </SegmentationTable.Collapsed.Header>
|
||||
+ <SegmentationTable.Collapsed.Content>
|
||||
+ {/* Content here */}
|
||||
+ </SegmentationTable.Collapsed.Content>
|
||||
+ </SegmentationTable.Collapsed>
|
||||
```
|
||||
|
||||
### 2. Update Component Hierarchy for Expanded View
|
||||
|
||||
The expanded view structure has also changed:
|
||||
|
||||
```diff
|
||||
- <SegmentationTable.Expanded>
|
||||
- <SegmentationTable.Header>
|
||||
- <CustomDropdownMenuContent />
|
||||
- </SegmentationTable.Header>
|
||||
- <SegmentationTable.Segments />
|
||||
- </SegmentationTable.Expanded>
|
||||
|
||||
+ <SegmentationTable.Expanded>
|
||||
+ <SegmentationTable.Expanded.Header>
|
||||
+ <SegmentationTable.Expanded.DropdownMenu>
|
||||
+ <CustomDropdownMenuContent />
|
||||
+ </SegmentationTable.Expanded.DropdownMenu>
|
||||
+ <SegmentationTable.Expanded.Label />
|
||||
+ <SegmentationTable.Expanded.Info />
|
||||
+ </SegmentationTable.Expanded.Header>
|
||||
+ <SegmentationTable.Expanded.Content>
|
||||
+ <SegmentationTable.AddSegmentRow />
|
||||
+ <SegmentationTable.Segments />
|
||||
+ </SegmentationTable.Expanded.Content>
|
||||
+ </SegmentationTable.Expanded>
|
||||
```
|
||||
|
||||
### 3. Using the New Segment Statistics Component
|
||||
|
||||
The new `SegmentStatistics` component provides a way to display segment statistics:
|
||||
|
||||
```diff
|
||||
+ <SegmentationTable.Segments>
|
||||
+ <SegmentationTable.SegmentStatistics.Header>
|
||||
+ <CustomSegmentStatisticsHeader />
|
||||
+ </SegmentationTable.SegmentStatistics.Header>
|
||||
+ <SegmentationTable.SegmentStatistics.Body />
|
||||
+ </SegmentationTable.Segments>
|
||||
```
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
---
|
||||
title: Tours and Onboarding
|
||||
---
|
||||
|
||||
## Migration Guide: Tours
|
||||
|
||||
* Tours are no longer defined directly in `window.config.tours` but through the customization service under the key `ohif.tours`
|
||||
* The `waitForElement` utility function has been moved from the config file to a dedicated customization file
|
||||
* The structure of tour definitions (steps, options, etc.) remains largely the same
|
||||
|
||||
## Migration Steps:
|
||||
|
||||
|
||||
|
||||
### 1. Update any direct references to window.config.tours
|
||||
|
||||
If you have any code that directly references window.config.tours, update it to use the customization service:
|
||||
|
||||
```diff
|
||||
- const tours = window.config.tours;
|
||||
+ const tours = customizationService.getCustomization('ohif.tours');
|
||||
```
|
||||
|
||||
### 2. Use config update patterns for configuring tours
|
||||
|
||||
**Before:**
|
||||
```diff
|
||||
- window.config = {
|
||||
- tours: [
|
||||
- {
|
||||
- id: 'basicViewerTour',
|
||||
- route: '/viewer',
|
||||
- steps: [
|
||||
- // tour steps...
|
||||
- ],
|
||||
- tourOptions: {
|
||||
- // tour options...
|
||||
- },
|
||||
- },
|
||||
- ],
|
||||
- };
|
||||
```
|
||||
|
||||
**After:**
|
||||
```javascript
|
||||
window.config = {
|
||||
customizationService: {
|
||||
'ohif.tours': {
|
||||
$set: [
|
||||
{
|
||||
id: 'basicViewerTour',
|
||||
route: '/viewer',
|
||||
steps: [
|
||||
// Your tour steps
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
## Benefits of the Change
|
||||
|
||||
4. **Mode-specific Tours**: now you can have different tours for different modes
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
---
|
||||
title: uiDialogService
|
||||
---
|
||||
|
||||
|
||||
## DialogService
|
||||
|
||||
This guide details the migration steps for the `uiDialogService` API changes, based on the provided diff. The most significant change is a shift from methods like `.create()` and `.dismiss()` to `.show()` and `.hide()`, alongside structural changes in how dialogs are defined and rendered. The changes aim for a more streamlined and flexible dialog management, leveraging React context for state management.
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* **`uiDialogService.create()` and `uiDialogService.dismiss()` are deprecated.** They have been replaced with `uiDialogService.show()` and `uiDialogService.hide()`. This change
|
||||
makes it consistent with the `uiModalService` and `uiNotificationService` APIs.
|
||||
* **`content` property now expects a React Component Type**, not an instance. Props for the content component are passed via `contentProps`.
|
||||
* **The `dialogId` is now consistently passed as `id` within the options** to `uiDialogService.show()`.
|
||||
|
||||
|
||||
### Props Kept same as before
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `id` | Still required, but we don't return it from the `show` method anymore. |
|
||||
| `content` | This is now expected to be a *React component type* (a function or class that returns JSX) |
|
||||
| `contentProps` | This continues to be the way to pass data *to* your custom dialog component. However, several specific props that *used* to be passed here (like `onClose`, `actions`) are no longer valid. |
|
||||
| `isDraggable` | Controls whether the dialog can be moved by dragging. |
|
||||
| `defaultPosition` | Allows you to specify an initial `{ x, y }` position for the dialog. |
|
||||
| `title` | The title text to display in the dialog header. |
|
||||
| `showOverlay` | default true - if the dialog is draggable the overlay is not shown by default |
|
||||
|
||||
|
||||
### Removed Props:
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `centralize` | Dialogs are now centered by default via CSS if you don't want center you pass defaultPosition |
|
||||
| `preservePosition` | Work in progress and will be available in future |
|
||||
| `contentDimensions` | Removed - should be specified directly in dialogs |
|
||||
| `onStart` | Removed |
|
||||
| `onDrag` | Removed |
|
||||
| `onStop` | Removed |
|
||||
| `onClickOutside` | Removed - if you want to close the dialog on click outside, you can use the `shouldCloseOnOverlayClick` prop |
|
||||
|
||||
|
||||
### Renamed Props:
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `containerDimensions` | renamed to `containerClassName` |
|
||||
|
||||
|
||||
|
||||
|
||||
### New Props:
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `unstyled` | A boolean prop to render the dialog without the default styling. It is used for context menu dialogs |
|
||||
| `shouldCloseOnEsc` | Default off for dialogs - Controls whether pressing the Escape key will close the dialog. |
|
||||
| `shouldCloseOnOverlayClick` | Default off for dialogs - Controls whether clicking the overlay background will close the dialog. |
|
||||
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Replace `.create()` with `.show()`:**
|
||||
|
||||
```diff
|
||||
- const dialogId = uiDialogService.create({
|
||||
- id: 'my-dialog',
|
||||
- content: MyDialogComponent,
|
||||
- contentProps: { prop1: 'value1' },
|
||||
- // ... other options ...
|
||||
- });
|
||||
|
||||
+ uiDialogService.show({
|
||||
+ id: 'my-dialog',
|
||||
+ content: MyDialogComponent,
|
||||
+ contentProps: { prop1: 'value1' },
|
||||
+ // ... other options ...
|
||||
+ });
|
||||
```
|
||||
|
||||
2. Rename `containerDimensions` to `containerClassName`
|
||||
|
||||
```diff
|
||||
- containerDimensions: 'w-[70%] max-w-[900px]',
|
||||
+ containerClassName: 'w-[70%] max-w-[900px]',
|
||||
```
|
||||
|
||||
3. **Replace `.dismiss({ id: dialogId })` with `.hide(dialogId)`:**
|
||||
|
||||
```diff
|
||||
- uiDialogService.dismiss({ id: dialogId });
|
||||
|
||||
+ uiDialogService.hide(dialogId);
|
||||
```
|
||||
|
||||
4. **Replace `dismissAll` with `hideAll`**
|
||||
```diff
|
||||
- uiDialogService.dismissAll();
|
||||
+ uiDialogService.hideAll();
|
||||
```
|
||||
5. **Update Dialog Content:**
|
||||
|
||||
* Ensure your dialog content is defined as a React component (functional or class-based).
|
||||
* Pass props to the component via `contentProps`.
|
||||
* You don't need to pass in `onClose` or `hide` as they are now handled passed in automatically.
|
||||
|
||||
```javascript
|
||||
// Example: MyDialogComponent.tsx
|
||||
function MyDialogComponent({ prop1, hide }) {
|
||||
return (
|
||||
<div>
|
||||
<p>Value of prop1: {prop1}</p>
|
||||
<button onClick={hide}>Close</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
**Example: Updating a Simple Alert Dialog**
|
||||
|
||||
```javascript
|
||||
// Before (using deprecated API)
|
||||
let dialogId;
|
||||
const showAlert = (message) => {
|
||||
dialogId = uiDialogService.create({
|
||||
centralize: true,
|
||||
isDraggable: false,
|
||||
content: Dialog,
|
||||
contentProps: {
|
||||
title: 'Alert',
|
||||
body: () => <p>{message}</p>,
|
||||
onClose: () => uiDialogService.dismiss({ id: dialogId }),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// After (using new API)
|
||||
function AlertDialog({ message, hide }) {
|
||||
return (
|
||||
<div>
|
||||
<p>{message}</p>
|
||||
<button onClick={hide}>OK</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const showAlert = (message) => {
|
||||
uiDialogService.show({
|
||||
id: 'alert-dialog',
|
||||
title: 'Alert',
|
||||
content: AlertDialog,
|
||||
contentProps: { message },
|
||||
});
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Components
|
||||
|
||||
### CreateReportDialogPrompt
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
* **Function Signature Update:** The function now accepts an object with `servicesManager`, `extensionManager`, `title`(optional).
|
||||
* **Return Value Structure:** The function now returns an object containing `value` (the report name), `dataSourceName` (the selected data source, if applicable), and `action` (indicating the user's choice).
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
1. **Update Function Call:**
|
||||
|
||||
Previously, the function was called with separate arguments. You should now pass an object:
|
||||
|
||||
```diff
|
||||
- const promptResult = await createReportDialogPrompt(uiDialogService, {
|
||||
- extensionManager,
|
||||
- });
|
||||
|
||||
+ const promptResult = await createReportDialogPrompt({
|
||||
+ servicesManager,
|
||||
+ extensionManager,
|
||||
+ title: 'Store Segmentation', // Optional title
|
||||
+ });
|
||||
|
||||
```
|
||||
|
||||
### promptSaveReport
|
||||
|
||||
Not changed, just a javascript to typescript migration.
|
||||
|
||||
### callLabelAutocompleteDialog
|
||||
|
||||
`callLabelAutocompleteDialog` is deprecated and has been replaced by `callInputDialogAutoComplete`. This new function simplifies the asynchronous handling of user input by using `uiDialogService.show()` and returning a promise.
|
||||
|
||||
|
||||
|
||||
### showLabelAnnotationPopup
|
||||
|
||||
`showLabelAnnotationPopup` has been replaced with `callInputDialogAutoComplete`. This update also uses `uiDialogService.show()` and promises, and it removes the callback function.
|
||||
|
||||
- The function now expects an object with `measurement`, `uiDialogService`, `labelConfig`, and `renderContent`.
|
||||
|
||||
```diff
|
||||
- const value = await showLabelAnnotationPopup(
|
||||
- measurement,
|
||||
- servicesManager.services.uiDialogService,
|
||||
- labelConfig,
|
||||
- renderContent
|
||||
- );
|
||||
+ const value = await callInputDialogAutoComplete({
|
||||
+ measurement,
|
||||
+ uiDialogService,
|
||||
+ labelConfig,
|
||||
+ renderContent,
|
||||
+ });
|
||||
```
|
||||
|
||||
### callInputDialog
|
||||
|
||||
- expects an objects now and returns the value of the input which you can then use for actions
|
||||
|
||||
|
||||
```diff
|
||||
|
||||
- callInputDialog(
|
||||
- uiDialogService,
|
||||
- {
|
||||
- text: '',
|
||||
- label: `${length}`,
|
||||
- },
|
||||
- (value, id) => {
|
||||
- if (id === 'save') {
|
||||
- adjustCalibration(Number.parseFloat(value));
|
||||
- resolve(true);
|
||||
- } else {
|
||||
- reject('cancel');
|
||||
- }
|
||||
- },
|
||||
- false,
|
||||
- {
|
||||
- dialogTitle: 'Calibration',
|
||||
- inputLabel: 'Actual Physical distance (mm)',
|
||||
- validateFunc: val => {
|
||||
- const v = Number.parseFloat(val);
|
||||
- return !isNaN(v) && v !== 0.0;
|
||||
- },
|
||||
- }
|
||||
- );
|
||||
+ callInputDialog({
|
||||
+ uiDialogService,
|
||||
+ title: 'Calibration',
|
||||
+ placeholder: 'Actual Physical distance (mm)',
|
||||
+ defaultValue: `${length}`,
|
||||
+ }).then(newValue => {
|
||||
+ adjustCalibration(Number.parseFloat(newValue));
|
||||
+ resolve(true);
|
||||
+ });
|
||||
```
|
||||
|
||||
or another one
|
||||
|
||||
```diff
|
||||
- callInputDialog(
|
||||
- uiDialogService,
|
||||
- { text: '', label: 'Enter description' },
|
||||
- (value, action) => {
|
||||
- if (action === 'save') {
|
||||
- saveFunction(value);
|
||||
- }
|
||||
- }
|
||||
- );
|
||||
+ callInputDialog({
|
||||
+ uiDialogService,
|
||||
+ title: 'Enter description of the Series',
|
||||
+ defaultValue: '',
|
||||
+ }).then(value => {
|
||||
+ saveFunction(value);
|
||||
+ });
|
||||
```
|
||||
|
||||
|
||||
### colorPickerDialog
|
||||
|
||||
Instead of calling `colorPickerDialog(uiDialogService, rgbaColor, callback)`, use `uiDialogService.show()` with `ColorPickerDialog` as the content.
|
||||
|
||||
|
||||
```diff
|
||||
- colorPickerDialog(uiDialogService, rgbaColor, (newRgbaColor, actionId) => {
|
||||
- if (actionId === 'cancel') {
|
||||
- return;
|
||||
- }
|
||||
- const color = [newRgbaColor.r, newRgbaColor.g, newRgbaColor.b, newRgbaColor.a * 255.0];
|
||||
- segmentationService.setSegmentColor(viewportId, segmentationId, segmentIndex, color);
|
||||
- });
|
||||
|
||||
// after
|
||||
|
||||
+ uiDialogService.show({
|
||||
+ content: ColorPickerDialog,
|
||||
+ title: 'Segment Color',
|
||||
+ contentProps: {
|
||||
+ value: rgbaColor,
|
||||
+ onSave: newRgbaColor => {
|
||||
+ const color = [newRgbaColor.r, newRgbaColor.g, newRgbaColor.b, newRgbaColor.a * 255.0];
|
||||
+ segmentationService.setSegmentColor(viewportId, segmentationId, segmentIndex, color);
|
||||
+ },
|
||||
+ },
|
||||
+ });
|
||||
```
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
---
|
||||
title: uiModalService
|
||||
---
|
||||
|
||||
|
||||
## ModalService
|
||||
|
||||
|
||||
### Props Kept same as before
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `content` | This is now expected to be a *React component type* (a function or class that returns JSX) |
|
||||
| `contentProps` | This continues to be the way to pass data *to* your custom dialog component. However, several specific props that *used* to be passed here (like `onClose`, `actions`) are no longer valid. |
|
||||
| `title` | The title text to display in the dialog header. |
|
||||
| `shouldCloseOnEsc` | Allows closing the modal when the escape key is pressed. |
|
||||
| `shouldCloseOnOverlayClick` | Allows closing the modal when the overlay is clicked. |
|
||||
|
||||
### Renamed Props:
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `containerDimensions` | renamed to `containerClassName` |
|
||||
|
||||
|
||||
|
||||
### Removed Props:
|
||||
|
||||
| Prop | Description |
|
||||
|------|-------------|
|
||||
| `movable` | It's removed because modals shouldn't be movable. If you need to move a dialog, use `uidDialogService` and `dialogs` instead. |
|
||||
| `isOpen` | always assumed `true` when `show` is called. |
|
||||
| `contentDimensions` | Removed, it is now component's responsibility to set the size for the content |
|
||||
| `customClassName` | renamed to `className` |
|
||||
| `closeButton` | The component now manages modal closing internally. If you need a close button, you can add one, perhaps by checking out the `FooterActions` component. |
|
||||
|
||||
|
||||
|
||||
|
||||
**Migration Steps:**
|
||||
|
||||
|
||||
### Rename of `containerDimensions` to `containerClassName` and removal of `contentDimensions`
|
||||
|
||||
|
||||
Before
|
||||
|
||||
```js
|
||||
uiModalService.show({
|
||||
title: 'Download High-Quality Image',
|
||||
content: CornerstoneViewportDownloadForm,
|
||||
contentProps: {
|
||||
activeViewportId,
|
||||
},
|
||||
containerDimensions: 'w-[70%] max-w-[900px]',
|
||||
contentDimensions: 'h-[493px] w-[460px] pl-[12px] pr-[12px]',
|
||||
});
|
||||
```
|
||||
|
||||
After: the component is responsible for setting the size
|
||||
|
||||
```js
|
||||
function CornerstoneViewportDownloadForm({ activeViewportId }) {
|
||||
return (
|
||||
<div className="h-[493px] w-[460px] pl-[12px] pr-[12px]">
|
||||
<h2 className="text-lg font-bold">Download Image</h2>
|
||||
<p>Viewport ID: {activeViewportId}</p>
|
||||
<button className="mt-4 bg-blue-500 text-white p-2 rounded">Download</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show the modal
|
||||
uiModalService.show({
|
||||
title: 'Download High-Quality Image',
|
||||
content: CornerstoneViewportDownloadForm,
|
||||
contentProps: { activeViewportId },
|
||||
containerClassName: 'w-[70%] max-w-[900px]',
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### onClose
|
||||
Previously, you had to pass in the `onClose` as `hide` function automatically added to the component.
|
||||
|
||||
```diff
|
||||
- uiModalService.show({
|
||||
- title: 'Untrack Series',
|
||||
- content: UntrackSeriesModal,
|
||||
- contentProps: { onConfirm },
|
||||
- onClose: () => uiModalService.hide(),
|
||||
- });
|
||||
|
||||
+ uiModalService.show({
|
||||
+ title: 'Untrack Series',
|
||||
+ content: UntrackSeriesModal,
|
||||
+ contentProps: {
|
||||
+ onConfirm,
|
||||
+ hide, // passed in automatically in the background
|
||||
+ },
|
||||
+ });
|
||||
```
|
||||
Reference in new issue
Block a user