docs: migration guides 3p10 (#4937)
This commit is contained in:
parent
a69dd8b1a0
commit
c453c29449
@ -27,7 +27,6 @@ export function Colormap({
|
||||
|
||||
const onSetColorLUT = useCallback(
|
||||
props => {
|
||||
debugger;
|
||||
// TODO: Better way to check if it's a fusion
|
||||
const oneOpacityColormaps = ['Grayscale', 'X Ray'];
|
||||
const opacity =
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
---
|
||||
title: Commands
|
||||
---
|
||||
|
||||
|
||||
# Commands
|
||||
|
||||
## Measurements
|
||||
|
||||
* The `deleteMeasurement` command has been completely removed from the codebase It has been replaced by `removeMeasurement` command with enhanced functionality
|
||||
|
||||
1. Replace any usage of `deleteMeasurement` with `removeMeasurement` in your custom code
|
||||
|
||||
```diff
|
||||
- commandsManager.run('deleteMeasurement', { uid });
|
||||
+ commandsManager.run('removeMeasurement', { uid });
|
||||
```
|
||||
|
||||
|
||||
## Important Notes:
|
||||
|
||||
* This change is part of a broader refactoring of the measurement system to provide more consistent and powerful APIs
|
||||
* The new command structure follows a more consistent pattern throughout the codebase
|
||||
* If you were using `measurementServiceSource.remove(uid)` directly, you should now use `measurementService.remove(uid)` instead
|
||||
* The changes affect both UI components and any extensions that integrate with the measurement system
|
||||
* Removal functionality now works with both individual UIDs and arrays of UIDs for batch operations
|
||||
|
||||
|
||||
|
||||
## `setSourceViewportForReferenceLinesTool`
|
||||
|
||||
* `setSourceViewportForReferenceLinesTool` has been replaced by the more generic `setViewportForToolConfiguration`
|
||||
* The new API allows configuration of any tool, not just the ReferenceLinesTool
|
||||
* Tool name is now a required parameter, not hardcoded to ReferenceLinesTool
|
||||
|
||||
## Migration Steps:
|
||||
|
||||
1. Update command references from `setSourceViewportForReferenceLinesTool` to `setViewportForToolConfiguration`
|
||||
|
||||
```diff
|
||||
- {
|
||||
- commandName: 'setSourceViewportForReferenceLinesTool',
|
||||
- context: 'CORNERSTONE',
|
||||
- }
|
||||
|
||||
+ {
|
||||
+ commandName: 'setViewportForToolConfiguration',
|
||||
+ commandOptions: {
|
||||
+ toolName: 'ReferenceLines'
|
||||
+ },
|
||||
+ context: 'CORNERSTONE',
|
||||
+ }
|
||||
```
|
||||
@ -0,0 +1,118 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
title: General
|
||||
---
|
||||
|
||||
## Node.js Version Update
|
||||
|
||||
We have updated the recommended Node.js version from `18.16.1` to `20.9.0`. Please ensure your development and build environments are using Node.js `20.9.0` or later.
|
||||
|
||||
## HTML Template Update
|
||||
We have modified the `template.html` file so if you are using a custom template, you will need to update it.
|
||||
|
||||
Here are the key changes needed in the migration:
|
||||
|
||||
1. Added `window.PUBLIC_URL` declaration:
|
||||
```javascript
|
||||
window.PUBLIC_URL = '<%= PUBLIC_URL %>';
|
||||
```
|
||||
|
||||
Was added before the `<!-- EXTENSIONS -->` comment block.
|
||||
|
||||
## Bundled Google Fonts
|
||||
|
||||
Previously, OHIF relied on the Google Fonts API to load the required fonts. To improve privacy, performance, and offline availability, we now bundle the necessary font files as assets within the application. No explicit action is required for this change unless you were specifically overriding or manipulating the font loading process.
|
||||
|
||||
You **might** need to update your `module` rule in your webpack
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(woff|woff2|eot|ttf|otf)$/i,
|
||||
type: 'asset/resource',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## OHIF Docs
|
||||
|
||||
OHIF platform/docs is no longer part of the workspace.
|
||||
|
||||
- Builds are faster for 99.99% of users since only maintainers need to run the docs development.
|
||||
|
||||
If you need to run the docs website locally, you must install it first, as it is not installed by default.
|
||||
|
||||
Before:
|
||||
```bash
|
||||
yarn run dev
|
||||
```
|
||||
|
||||
After:
|
||||
```bash
|
||||
yarn install
|
||||
yarn run dev
|
||||
```
|
||||
|
||||
|
||||
## Experimental Fast Development Build (`dev:fast`)
|
||||
|
||||
We have introduced a new experimental command, `yarn run dev:fast`, which utilizes `rsbuild` and its Rust-based approach to significantly speed up development server start and hot module replacement times.
|
||||
|
||||
Here's a comparison of the performance improvements:
|
||||
|
||||
| Scenario | Load Time | Update Time |
|
||||
| -------- | ----------- | ----------- |
|
||||
| Before | ~12 seconds | ~5 seconds |
|
||||
| After | ~4 seconds | ~1 second |
|
||||
|
||||
**Note:** This command is currently experimental. While functional, it may not yet support all features or configurations of the standard `yarn run dev` command. We are continuing to develop and test this feature.
|
||||
|
||||
|
||||
## Webpack Configuration
|
||||
|
||||
To use our new Segmentation AI models, you'll need `onnxruntime-web`. If you're using a custom webpack configuration, make sure to update it with the new `copyPlugin` to copy the `onnxruntime-web` `dist` folder to your output directory.
|
||||
|
||||
|
||||
```javascript
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: '../../../node_modules/onnxruntime-web/dist',
|
||||
to: `${DIST_DIR}/ort`,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
Also, if you're running the viewer from a sub-route, you'll need to update the `dicom-microscopy-viewer` package in the dev server, so it knows where to load the assets from.
|
||||
|
||||
|
||||
```javascript
|
||||
devServer: {
|
||||
proxy: {
|
||||
'/dicom-microscopy-viewer': {
|
||||
target: 'http://localhost:3000',
|
||||
pathRewrite: {
|
||||
'^/dicom-microscopy-viewer': `/${PUBLIC_URL}/dicom-microscopy-viewer`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
:::note
|
||||
Also, the `writePluginImportFile` function has been updated so that the dicom-microscopy-viewer package works correctly with the new webpack configuration. If you have a custom `writePluginImportFile` function, please update it to match.
|
||||
:::
|
||||
@ -1,40 +0,0 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
title: General
|
||||
---
|
||||
|
||||
## HTML Template Update
|
||||
We have modified the `template.html` file so if you are using a custom template, you will need to update it.
|
||||
|
||||
Here are the key changes needed in the migration:
|
||||
|
||||
1. Added `window.PUBLIC_URL` declaration:
|
||||
```javascript
|
||||
window.PUBLIC_URL = '<%= PUBLIC_URL %>';
|
||||
```
|
||||
|
||||
Was added before the `<!-- EXTENSIONS -->` comment block.
|
||||
|
||||
|
||||
## OHIF Docs
|
||||
|
||||
OHIF platform/docs is no longer part of the workspace.
|
||||
|
||||
- Builds are faster for 99.99% of users since only maintainers need to run the docs development.
|
||||
|
||||
If you need to run the docs website locally, you must install it first, as it is not installed by default.
|
||||
|
||||
Before:
|
||||
```bash
|
||||
yarn run dev
|
||||
```
|
||||
|
||||
After:
|
||||
```bash
|
||||
yarn install
|
||||
yarn run dev
|
||||
```
|
||||
|
||||
## CommandsModule
|
||||
|
||||
1. Removed the `deleteMeasurements` command from the `CORNERSTONE` context. It should be replaced by `removeMeasurements` command.
|
||||
@ -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.
|
||||
@ -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>
|
||||
```
|
||||
@ -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>
|
||||
```
|
||||
@ -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 |
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -1,199 +0,0 @@
|
||||
---
|
||||
title: Input Number, Range, and Double Range
|
||||
---
|
||||
|
||||
|
||||
# Migration Guide: Moving to `Numeric` Component
|
||||
|
||||
This guide explains how to migrate from the existing `Input`, `InputRange`, and `InputDoubleRange` components to the new `Numeric` meta component.
|
||||
|
||||
|
||||
## Why Migrate?
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## `Input` > `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>
|
||||
```
|
||||
|
||||
|
||||
## `InputRange` > `Numeric.SingleRange`
|
||||
|
||||
### Basic Usage
|
||||
|
||||
**Old Usage:**
|
||||
|
||||
```tsx
|
||||
<InputRange
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
step={1}
|
||||
showLabel
|
||||
/>
|
||||
```
|
||||
|
||||
**New Usage:**
|
||||
|
||||
```tsx
|
||||
<Numeric.Container mode="singleRange" value={value} onChange={setValue} min={0} max={100} step={1}>
|
||||
<Numeric.Label showValue>Range</Numeric.Label>
|
||||
<Numeric.SingleRange showNumberInput />
|
||||
</Numeric.Container>
|
||||
```
|
||||
|
||||
|
||||
### Custom Classes
|
||||
|
||||
**Old Usage**
|
||||
|
||||
```tsx
|
||||
<InputRange
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
step={1}
|
||||
containerClassName="flex flex-col space-y-2"
|
||||
inputClassName="w-full bg-gray-700"
|
||||
labelClassName="text-gray-500 text-sm"
|
||||
labelVariant="body1"
|
||||
showLabel={true}
|
||||
labelPosition="left"
|
||||
/>
|
||||
```
|
||||
|
||||
**New Usage**
|
||||
|
||||
```tsx
|
||||
<Numeric.Container mode="singleRange" value={value} onChange={setValue} min={0} max={100} step={1} className="flex flex-col space-y-2">
|
||||
<Numeric.Label className="text-gray-500 text-sm">Range</Numeric.Label>
|
||||
<Numeric.SingleRange sliderClassName="w-full bg-gray-700" />
|
||||
</Numeric.Container>
|
||||
```
|
||||
|
||||
:::note
|
||||
You now have more control over the position of the label and the slider. You can use pretty much any layout you want, whether that's flex, grid, or something else. Instead of relying on `labelPosition` to position the label, you're free to use the layout that works best for you.
|
||||
:::
|
||||
|
||||
|
||||
### AllowNumberEdit
|
||||
|
||||
**Old Usage:**
|
||||
|
||||
```tsx
|
||||
<InputRange
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
step={1}
|
||||
allowNumberEdit={true}
|
||||
showAdjustmentArrows={true}
|
||||
/>
|
||||
```
|
||||
|
||||
**New Usage:**
|
||||
|
||||
Using `Numeric.SingleRange` and `showNumberInput`
|
||||
|
||||
```tsx
|
||||
<Numeric.Container mode="singleRange" value={value} onChange={setValue} min={0} max={100} step={1}>
|
||||
<Numeric.Label showValue>Range</Numeric.Label>
|
||||
<Numeric.SingleRange showNumberInput />
|
||||
</Numeric.Container>
|
||||
```
|
||||
|
||||
|
||||
## `InputDoubleRange` > `Numeric.DoubleRange`
|
||||
|
||||
|
||||
### Basic Usage
|
||||
**Old Usage:**
|
||||
|
||||
```tsx
|
||||
<InputDoubleRange
|
||||
values={rangeValues}
|
||||
onChange={setRangeValues}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
step={5}
|
||||
showLabel
|
||||
/>
|
||||
```
|
||||
|
||||
**New Usage:**
|
||||
|
||||
```tsx
|
||||
<Numeric.Container mode="doubleRange" values={rangeValues} onChange={setRangeValues} min={0} max={100} step={5}>
|
||||
<Numeric.Label showValue>Range</Numeric.Label>
|
||||
<Numeric.DoubleRange showNumberInputs />
|
||||
</Numeric.Container>
|
||||
```
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Summary of Changes
|
||||
|
||||
| Old Component | New Component Equivalent |
|
||||
|--------------------|------------------------|
|
||||
| `<Input>` | `<Numeric.NumberInput>` |
|
||||
| `<InputRange>` | `<Numeric.SingleRange>` |
|
||||
| `<InputDoubleRange>` | `<Numeric.DoubleRange>` |
|
||||
@ -1,503 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { DataRow } from '../../../../ui-next/src/components/DataRow';
|
||||
import { Button } from '../../../../ui-next/src/components/Button';
|
||||
import {
|
||||
Select,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
} from '../../../../ui-next/src/components/Select';
|
||||
import { Icons } from '../../../../ui-next/src/components/Icons';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuPortal,
|
||||
} from '../../../../ui-next/src/components/DropdownMenu';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
AccordionContent,
|
||||
} from '../../../../ui-next/src/components/Accordion';
|
||||
import { Slider } from '../../../../ui-next/src/components/Slider';
|
||||
import { Switch } from '../../../../ui-next/src/components/Switch';
|
||||
import { Label } from '../../../../ui-next/src/components/Label';
|
||||
import { Input } from '../../../../ui-next/src/components/Input';
|
||||
import { Tabs, TabsList, TabsTrigger } from '../../../../ui-next/src/components/Tabs';
|
||||
import { actionOptionsMap, dataList } from '../../../../ui-next/assets/data';
|
||||
import { TooltipProvider } from '../../../../ui-next/src/components/Tooltip';
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardTrigger,
|
||||
HoverCardContent,
|
||||
} from '../../../../ui-next/src/components/HoverCard';
|
||||
import { DataItem, ListGroup } from '../../../../ui-next/assets/data';
|
||||
export default function SegmentationPanel() {
|
||||
const [selectedRowId, setSelectedRowId] = useState<string | null>(null);
|
||||
const [selectedTab, setSelectedTab] = useState<string>('Fill & Outline');
|
||||
const handleAction = (id: string, action: string) => {
|
||||
console.log(`Action "${action}" triggered for item with id: ${id}`);
|
||||
// Implement actual action logic here
|
||||
};
|
||||
|
||||
// Handle row selection
|
||||
const handleRowSelect = (id: string) => {
|
||||
setSelectedRowId(prevSelectedId => (prevSelectedId === id ? null : id));
|
||||
};
|
||||
|
||||
const organSegmentationGroup = dataList.find(
|
||||
(listGroup: any) => listGroup.type === 'Organ Segmentation'
|
||||
) as unknown as ListGroup;
|
||||
|
||||
if (!organSegmentationGroup) {
|
||||
return <div className="text-red-500">Organ Segmentation data not found.</div>;
|
||||
}
|
||||
|
||||
// Create a state to track which item's statistics to show
|
||||
|
||||
// Function to render statistics panel
|
||||
const renderStatisticsPanel = (item: DataItem) => {
|
||||
if (!item.statistics) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const stats = item.statistics;
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="mb-4 flex items-center space-x-2">
|
||||
<div
|
||||
className="h-2.5 w-2.5 flex-shrink-0 rounded-full"
|
||||
style={{ backgroundColor: item.colorHex }}
|
||||
></div>
|
||||
<h3 className="text-muted-foreground break-words text-lg font-semibold">{item.title}</h3>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between">
|
||||
<div className="">Centroid X</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.centroidX.value}</span>{' '}
|
||||
<span className="">{stats.centroidX.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Centroid Y</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.centroidY.value}</span>{' '}
|
||||
<span className="">{stats.centroidY.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Centroid Z</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.centroidZ.value}</span>{' '}
|
||||
<span className="">{stats.centroidZ.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Frame Duration</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.frameDuration.value}</span>{' '}
|
||||
<span className="">{stats.frameDuration.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Kurtosis</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.kurtosis.value}</span>{' '}
|
||||
<span className="">{stats.kurtosis.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Max</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.max.value}</span>{' '}
|
||||
<span className="">{stats.max.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Max Slice</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.maxSlice.value}</span>{' '}
|
||||
<span className="">{stats.maxSlice.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Mean</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.mean.value}</span>{' '}
|
||||
<span className="">{stats.mean.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Median</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.median.value}</span>{' '}
|
||||
<span className="">{stats.median.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Min</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.min.value}</span>{' '}
|
||||
<span className="">{stats.min.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Regions</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.regions.value}</span>{' '}
|
||||
<span className="">{stats.regions.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Skewness</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.skewness.value}</span>{' '}
|
||||
<span className="">{stats.skewness.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Sphere Diameter</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.sphereDiameter.value}</span>{' '}
|
||||
<span className="">{stats.sphereDiameter.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Standard Deviation</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.standardDeviation.value}</span>{' '}
|
||||
<span className="">{stats.standardDeviation.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">SUV Peak</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.suvPeak.value}</span>{' '}
|
||||
<span className="">{stats.suvPeak.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Total</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.total.value}</span>{' '}
|
||||
<span className="">{stats.total.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Glycolysis</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.glycolysis.value}</span>{' '}
|
||||
<span className="">{stats.glycolysis.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Volume</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.volume.value}</span>{' '}
|
||||
<span className="">{stats.volume.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Voxel Count</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.voxelCount.value}</span>{' '}
|
||||
<span className="">{stats.voxelCount.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="px-auto flex min-h-screen w-full justify-center bg-black py-12">
|
||||
<div className="w-64 space-y-0">
|
||||
<TooltipProvider>
|
||||
<Accordion
|
||||
type="multiple"
|
||||
defaultValue={['segmentation-tools', 'segmentation-list']}
|
||||
>
|
||||
{/* Segmentation Tools */}
|
||||
<AccordionItem value="segmentation-tools">
|
||||
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
|
||||
<span>Segmentation Tools</span>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="bg-muted mb-0.5 h-32 rounded-b pb-3"></div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
{/* Segmentation List */}
|
||||
<AccordionItem value="segmentation-list">
|
||||
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
|
||||
<span>Segmentation List</span>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="mb-0">
|
||||
{/* Header Controls */}
|
||||
<div className="bg-muted flex h-10 w-full items-center space-x-1 rounded-t px-1.5">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
>
|
||||
<Icons.More className="h-6 w-6" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem>
|
||||
<Icons.Add className="text-foreground" />
|
||||
<span className="pl-2">Create New Segmentation</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>Manage Current Segmentation</DropdownMenuLabel>
|
||||
<DropdownMenuItem>
|
||||
<Icons.Series className="text-foreground" />
|
||||
<span className="pl-2">Remove from Viewport</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Icons.Rename className="text-foreground" />
|
||||
<span className="pl-2">Rename</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<Icons.Export className="text-foreground" />
|
||||
<span className="pl-2">Export & Download</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem>Export DICOM SEG</DropdownMenuItem>
|
||||
<DropdownMenuItem>Download DICOM SEG</DropdownMenuItem>
|
||||
<DropdownMenuItem>Download DICOM RTSTRUCT</DropdownMenuItem>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<Icons.Delete className="text-red-600" />
|
||||
<span className="pl-2 text-red-600">Delete</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Select>
|
||||
<SelectTrigger className="w-full overflow-hidden">
|
||||
<SelectValue placeholder="Segmentation 1" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="seg1">Segmentation 1</SelectItem>
|
||||
<SelectItem value="seg2">Segmentation 2</SelectItem>
|
||||
<SelectItem value="seg3">Segmentation Long Name 123</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
>
|
||||
<Icons.Info className="h-6 w-6" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Appearance Settings */}
|
||||
<AccordionItem value="segmentation-display">
|
||||
<AccordionTrigger className="bg-muted hover:bg-accent mt-0.5 flex h-7 w-full items-center justify-between rounded-b pr-1 pl-2 font-normal text-white">
|
||||
<div className="flex space-x-2">
|
||||
<Icons.Controls className="text-primary" />
|
||||
<span className="text-primary pr-1">Appearance Settings</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="bg-muted mb-0.5 space-y-2 rounded-b px-1.5 pt-0.5 pb-3">
|
||||
<div className="mx-1 mb-2.5 mt-1 flex items-center justify-between space-x-4">
|
||||
{/* Display Label with Selected Tab */}
|
||||
<div className="text-muted-foreground text-xs">Show: {selectedTab}</div>
|
||||
{/* Tabs Controls */}
|
||||
<Tabs
|
||||
value={selectedTab}
|
||||
onValueChange={setSelectedTab}
|
||||
>
|
||||
<TabsList>
|
||||
<TabsTrigger value="Fill & Outline">
|
||||
<Icons.DisplayFillAndOutline className="text-primary" />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="Outline Only">
|
||||
<Icons.DisplayOutlineOnly className="text-primary" />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="Fill Only">
|
||||
<Icons.DisplayFillOnly className="text-primary" />
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
{/* Opacity Slider */}
|
||||
<div className="my-2 flex items-center">
|
||||
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Opacity
|
||||
</Label>
|
||||
<Slider
|
||||
className="mx-1 flex-1"
|
||||
defaultValue={[85]}
|
||||
max={100}
|
||||
step={1}
|
||||
/>
|
||||
<Input
|
||||
className="mx-1 w-10 flex-none"
|
||||
placeholder="85"
|
||||
/>
|
||||
</div>
|
||||
{/* Border Slider */}
|
||||
<div className="my-2 flex items-center">
|
||||
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Border
|
||||
</Label>
|
||||
<Slider
|
||||
className="mx-1 flex-1"
|
||||
defaultValue={[10]}
|
||||
max={100}
|
||||
step={1}
|
||||
/>
|
||||
<Input
|
||||
className="mx-1 w-10 flex-none"
|
||||
placeholder="2"
|
||||
/>
|
||||
</div>
|
||||
{/* Sync Changes Switch */}
|
||||
<div className="my-2 flex items-center pl-1 pb-1">
|
||||
<Switch defaultChecked />
|
||||
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Sync changes in all viewports
|
||||
</Label>
|
||||
</div>
|
||||
<div className="border-input w-full border"></div>
|
||||
{/* Display Inactive Segmentations Switch */}
|
||||
<div className="my-2 flex items-center pl-1">
|
||||
<Switch defaultChecked />
|
||||
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Display inactive segmentations
|
||||
</Label>
|
||||
</div>
|
||||
{/* Additional Opacity Slider */}
|
||||
<div className="my-2 flex items-center">
|
||||
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Opacity
|
||||
</Label>
|
||||
<Slider
|
||||
className="mx-1 flex-1"
|
||||
defaultValue={[65]}
|
||||
max={100}
|
||||
step={1}
|
||||
/>
|
||||
<Input
|
||||
className="mx-1 w-10 flex-none"
|
||||
placeholder="65"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
{/* Action Buttons */}
|
||||
<div className="my-px flex h-9 w-full items-center justify-between rounded pl-0.5 pr-7">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="pr pl-0.5"
|
||||
>
|
||||
<Icons.Add />
|
||||
Add Segment
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<Icons.Hide className="h-6 w-6" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Data Rows */}
|
||||
<div className="space-y-px">
|
||||
{organSegmentationGroup.items.map((item, index) => {
|
||||
const compositeId = `${organSegmentationGroup.type}-${item.id}-panel`; // Ensure unique composite ID
|
||||
return (
|
||||
<HoverCard
|
||||
key={`hover-${compositeId}`}
|
||||
openDelay={300}
|
||||
closeDelay={200}
|
||||
// open={true}
|
||||
>
|
||||
<HoverCardTrigger asChild>
|
||||
<div>
|
||||
<DataRow
|
||||
key={`panel-${compositeId}`} // Prefix to ensure uniqueness
|
||||
number={index + 1}
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
colorHex={item.colorHex}
|
||||
details={item.details || { primary: [], secondary: [] }}
|
||||
actionOptions={
|
||||
actionOptionsMap[organSegmentationGroup.type] || ['Action']
|
||||
}
|
||||
onAction={(action: string) => handleAction(compositeId, action)}
|
||||
isSelected={selectedRowId === compositeId}
|
||||
onSelect={() => handleRowSelect(compositeId)}
|
||||
isVisible={true}
|
||||
isLocked={false}
|
||||
onToggleVisibility={() => console.debug('Toggle visibility')}
|
||||
onToggleLocked={() => console.debug('Toggle locked')}
|
||||
onRename={() => console.debug('Rename')}
|
||||
onDelete={() => console.debug('Delete')}
|
||||
onColor={() => console.debug('Color')}
|
||||
disableEditing={false}
|
||||
/>
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent
|
||||
side="left"
|
||||
align="start"
|
||||
className="w-72 border"
|
||||
>
|
||||
{renderStatisticsPanel(item)}
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -2,72 +2,502 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { panelSegmentationData } from '../../../../ui-next/assets/data';
|
||||
import { SegmentationTable } from '../../../../ui-next/src/components/SegmentationTable';
|
||||
import { DataRow } from '../../../../ui-next/src/components/DataRow';
|
||||
import { Button } from '../../../../ui-next/src/components/Button';
|
||||
import {
|
||||
Select,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
} from '../../../../ui-next/src/components/Select';
|
||||
import { Icons } from '../../../../ui-next/src/components/Icons';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuPortal,
|
||||
} from '../../../../ui-next/src/components/DropdownMenu';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
AccordionContent,
|
||||
} from '../../../../ui-next/src/components/Accordion';
|
||||
import { Slider } from '../../../../ui-next/src/components/Slider';
|
||||
import { Switch } from '../../../../ui-next/src/components/Switch';
|
||||
import { Label } from '../../../../ui-next/src/components/Label';
|
||||
import { Input } from '../../../../ui-next/src/components/Input';
|
||||
import { Tabs, TabsList, TabsTrigger } from '../../../../ui-next/src/components/Tabs';
|
||||
import { actionOptionsMap, dataList } from '../../../../ui-next/assets/data';
|
||||
import { TooltipProvider } from '../../../../ui-next/src/components/Tooltip';
|
||||
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardTrigger,
|
||||
HoverCardContent,
|
||||
} from '../../../../ui-next/src/components/HoverCard';
|
||||
import { DataItem, ListGroup } from '../../../../ui-next/assets/data';
|
||||
export default function SegmentationPanel() {
|
||||
const tableProps = panelSegmentationData;
|
||||
tableProps.mode = 'expanded';
|
||||
|
||||
const renderSegments = () => {
|
||||
return (
|
||||
<SegmentationTable.Segments>
|
||||
<SegmentationTable.SegmentStatistics.Header></SegmentationTable.SegmentStatistics.Header>
|
||||
<SegmentationTable.SegmentStatistics.Body />
|
||||
</SegmentationTable.Segments>
|
||||
);
|
||||
const [selectedRowId, setSelectedRowId] = useState<string | null>(null);
|
||||
const [selectedTab, setSelectedTab] = useState<string>('Fill & Outline');
|
||||
const handleAction = (id: string, action: string) => {
|
||||
console.log(`Action "${action}" triggered for item with id: ${id}`);
|
||||
// Implement actual action logic here
|
||||
};
|
||||
|
||||
// Render content based on mode
|
||||
const renderModeContent = () => {
|
||||
if (tableProps.mode === 'collapsed') {
|
||||
return (
|
||||
<SegmentationTable.Collapsed>
|
||||
<SegmentationTable.Collapsed.Header>
|
||||
<SegmentationTable.Collapsed.DropdownMenu>
|
||||
{/* <CustomDropdownMenuContent /> */}
|
||||
</SegmentationTable.Collapsed.DropdownMenu>
|
||||
<SegmentationTable.Collapsed.Selector />
|
||||
<SegmentationTable.Collapsed.Info />
|
||||
</SegmentationTable.Collapsed.Header>
|
||||
<SegmentationTable.Collapsed.Content>
|
||||
<SegmentationTable.AddSegmentRow />
|
||||
{renderSegments()}
|
||||
</SegmentationTable.Collapsed.Content>
|
||||
</SegmentationTable.Collapsed>
|
||||
);
|
||||
// Handle row selection
|
||||
const handleRowSelect = (id: string) => {
|
||||
setSelectedRowId(prevSelectedId => (prevSelectedId === id ? null : id));
|
||||
};
|
||||
|
||||
const organSegmentationGroup = dataList.find(
|
||||
(listGroup: any) => listGroup.type === 'Organ Segmentation'
|
||||
) as unknown as ListGroup;
|
||||
|
||||
if (!organSegmentationGroup) {
|
||||
return <div className="text-red-500">Organ Segmentation data not found.</div>;
|
||||
}
|
||||
|
||||
// Create a state to track which item's statistics to show
|
||||
|
||||
// Function to render statistics panel
|
||||
const renderStatisticsPanel = (item: DataItem) => {
|
||||
if (!item.statistics) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const stats = item.statistics;
|
||||
return (
|
||||
<>
|
||||
<SegmentationTable.Expanded>
|
||||
<SegmentationTable.Expanded.Header>
|
||||
<SegmentationTable.Expanded.DropdownMenu>
|
||||
{/* <CustomDropdownMenuContent /> */}
|
||||
</SegmentationTable.Expanded.DropdownMenu>
|
||||
<SegmentationTable.Expanded.Label />
|
||||
<SegmentationTable.Expanded.Info />
|
||||
</SegmentationTable.Expanded.Header>
|
||||
<div className="w-full">
|
||||
<div className="mb-4 flex items-center space-x-2">
|
||||
<div
|
||||
className="h-2.5 w-2.5 flex-shrink-0 rounded-full"
|
||||
style={{ backgroundColor: item.colorHex }}
|
||||
></div>
|
||||
<h3 className="text-muted-foreground break-words text-lg font-semibold">{item.title}</h3>
|
||||
</div>
|
||||
|
||||
<SegmentationTable.Expanded.Content>
|
||||
<SegmentationTable.AddSegmentRow />
|
||||
{renderSegments()}
|
||||
</SegmentationTable.Expanded.Content>
|
||||
</SegmentationTable.Expanded>
|
||||
</>
|
||||
<div className="space-y-1">
|
||||
<div className="flex justify-between">
|
||||
<div className="">Centroid X</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.centroidX.value}</span>{' '}
|
||||
<span className="">{stats.centroidX.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Centroid Y</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.centroidY.value}</span>{' '}
|
||||
<span className="">{stats.centroidY.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Centroid Z</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.centroidZ.value}</span>{' '}
|
||||
<span className="">{stats.centroidZ.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Frame Duration</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.frameDuration.value}</span>{' '}
|
||||
<span className="">{stats.frameDuration.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Kurtosis</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.kurtosis.value}</span>{' '}
|
||||
<span className="">{stats.kurtosis.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Max</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.max.value}</span>{' '}
|
||||
<span className="">{stats.max.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Max Slice</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.maxSlice.value}</span>{' '}
|
||||
<span className="">{stats.maxSlice.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Mean</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.mean.value}</span>{' '}
|
||||
<span className="">{stats.mean.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Median</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.median.value}</span>{' '}
|
||||
<span className="">{stats.median.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Min</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.min.value}</span>{' '}
|
||||
<span className="">{stats.min.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Regions</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.regions.value}</span>{' '}
|
||||
<span className="">{stats.regions.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Skewness</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.skewness.value}</span>{' '}
|
||||
<span className="">{stats.skewness.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Sphere Diameter</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.sphereDiameter.value}</span>{' '}
|
||||
<span className="">{stats.sphereDiameter.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Standard Deviation</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.standardDeviation.value}</span>{' '}
|
||||
<span className="">{stats.standardDeviation.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">SUV Peak</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.suvPeak.value}</span>{' '}
|
||||
<span className="">{stats.suvPeak.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Total</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.total.value}</span>{' '}
|
||||
<span className="">{stats.total.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Glycolysis</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.glycolysis.value}</span>{' '}
|
||||
<span className="">{stats.glycolysis.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Volume</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.volume.value}</span>{' '}
|
||||
<span className="">{stats.volume.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="">Voxel Count</div>
|
||||
<div>
|
||||
<span className="text-white">{stats.voxelCount.value}</span>{' '}
|
||||
<span className="">{stats.voxelCount.unit}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="px-auto flex min-h-screen w-full justify-center border-2 bg-black py-64">
|
||||
<TooltipProvider>
|
||||
<SegmentationTable {...tableProps}>
|
||||
<SegmentationTable.Config />
|
||||
<SegmentationTable.AddSegmentationRow />
|
||||
{renderModeContent()}
|
||||
</SegmentationTable>
|
||||
</TooltipProvider>
|
||||
<div className="px-auto flex min-h-screen w-full justify-center bg-black py-12">
|
||||
<div className="w-64 space-y-0">
|
||||
<TooltipProvider>
|
||||
<Accordion
|
||||
type="multiple"
|
||||
defaultValue={['segmentation-tools', 'segmentation-list']}
|
||||
>
|
||||
{/* Segmentation Tools */}
|
||||
<AccordionItem value="segmentation-tools">
|
||||
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
|
||||
<span>Segmentation Tools</span>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="bg-muted mb-0.5 h-32 rounded-b pb-3"></div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
||||
{/* Segmentation List */}
|
||||
<AccordionItem value="segmentation-list">
|
||||
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
|
||||
<span>Segmentation List</span>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="mb-0">
|
||||
{/* Header Controls */}
|
||||
<div className="bg-muted flex h-10 w-full items-center space-x-1 rounded-t px-1.5">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
>
|
||||
<Icons.More className="h-6 w-6" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem>
|
||||
<Icons.Add className="text-foreground" />
|
||||
<span className="pl-2">Create New Segmentation</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>Manage Current Segmentation</DropdownMenuLabel>
|
||||
<DropdownMenuItem>
|
||||
<Icons.Series className="text-foreground" />
|
||||
<span className="pl-2">Remove from Viewport</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Icons.Rename className="text-foreground" />
|
||||
<span className="pl-2">Rename</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<Icons.Export className="text-foreground" />
|
||||
<span className="pl-2">Export & Download</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem>Export DICOM SEG</DropdownMenuItem>
|
||||
<DropdownMenuItem>Download DICOM SEG</DropdownMenuItem>
|
||||
<DropdownMenuItem>Download DICOM RTSTRUCT</DropdownMenuItem>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<Icons.Delete className="text-red-600" />
|
||||
<span className="pl-2 text-red-600">Delete</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Select>
|
||||
<SelectTrigger className="w-full overflow-hidden">
|
||||
<SelectValue placeholder="Segmentation 1" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="seg1">Segmentation 1</SelectItem>
|
||||
<SelectItem value="seg2">Segmentation 2</SelectItem>
|
||||
<SelectItem value="seg3">Segmentation Long Name 123</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
>
|
||||
<Icons.Info className="h-6 w-6" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Appearance Settings */}
|
||||
<AccordionItem value="segmentation-display">
|
||||
<AccordionTrigger className="bg-muted hover:bg-accent mt-0.5 flex h-7 w-full items-center justify-between rounded-b pr-1 pl-2 font-normal text-white">
|
||||
<div className="flex space-x-2">
|
||||
<Icons.Controls className="text-primary" />
|
||||
<span className="text-primary pr-1">Appearance Settings</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="bg-muted mb-0.5 space-y-2 rounded-b px-1.5 pt-0.5 pb-3">
|
||||
<div className="mx-1 mb-2.5 mt-1 flex items-center justify-between space-x-4">
|
||||
{/* Display Label with Selected Tab */}
|
||||
<div className="text-muted-foreground text-xs">Show: {selectedTab}</div>
|
||||
{/* Tabs Controls */}
|
||||
<Tabs
|
||||
value={selectedTab}
|
||||
onValueChange={setSelectedTab}
|
||||
>
|
||||
<TabsList>
|
||||
<TabsTrigger value="Fill & Outline">
|
||||
<Icons.DisplayFillAndOutline className="text-primary" />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="Outline Only">
|
||||
<Icons.DisplayOutlineOnly className="text-primary" />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="Fill Only">
|
||||
<Icons.DisplayFillOnly className="text-primary" />
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
{/* Opacity Slider */}
|
||||
<div className="my-2 flex items-center">
|
||||
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Opacity
|
||||
</Label>
|
||||
<Slider
|
||||
className="mx-1 flex-1"
|
||||
defaultValue={[85]}
|
||||
max={100}
|
||||
step={1}
|
||||
/>
|
||||
<Input
|
||||
className="mx-1 w-10 flex-none"
|
||||
placeholder="85"
|
||||
/>
|
||||
</div>
|
||||
{/* Border Slider */}
|
||||
<div className="my-2 flex items-center">
|
||||
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Border
|
||||
</Label>
|
||||
<Slider
|
||||
className="mx-1 flex-1"
|
||||
defaultValue={[10]}
|
||||
max={100}
|
||||
step={1}
|
||||
/>
|
||||
<Input
|
||||
className="mx-1 w-10 flex-none"
|
||||
placeholder="2"
|
||||
/>
|
||||
</div>
|
||||
{/* Sync Changes Switch */}
|
||||
<div className="my-2 flex items-center pl-1 pb-1">
|
||||
<Switch defaultChecked />
|
||||
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Sync changes in all viewports
|
||||
</Label>
|
||||
</div>
|
||||
<div className="border-input w-full border"></div>
|
||||
{/* Display Inactive Segmentations Switch */}
|
||||
<div className="my-2 flex items-center pl-1">
|
||||
<Switch defaultChecked />
|
||||
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Display inactive segmentations
|
||||
</Label>
|
||||
</div>
|
||||
{/* Additional Opacity Slider */}
|
||||
<div className="my-2 flex items-center">
|
||||
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
|
||||
Opacity
|
||||
</Label>
|
||||
<Slider
|
||||
className="mx-1 flex-1"
|
||||
defaultValue={[65]}
|
||||
max={100}
|
||||
step={1}
|
||||
/>
|
||||
<Input
|
||||
className="mx-1 w-10 flex-none"
|
||||
placeholder="65"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
{/* Action Buttons */}
|
||||
<div className="my-px flex h-9 w-full items-center justify-between rounded pl-0.5 pr-7">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="pr pl-0.5"
|
||||
>
|
||||
<Icons.Add />
|
||||
Add Segment
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<Icons.Hide className="h-6 w-6" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Data Rows */}
|
||||
<div className="space-y-px">
|
||||
{organSegmentationGroup.items.map((item, index) => {
|
||||
const compositeId = `${organSegmentationGroup.type}-${item.id}-panel`; // Ensure unique composite ID
|
||||
return (
|
||||
<HoverCard
|
||||
key={`hover-${compositeId}`}
|
||||
openDelay={300}
|
||||
closeDelay={200}
|
||||
// open={true}
|
||||
>
|
||||
<HoverCardTrigger asChild>
|
||||
<div>
|
||||
<DataRow
|
||||
key={`panel-${compositeId}`} // Prefix to ensure uniqueness
|
||||
number={index + 1}
|
||||
title={item.title}
|
||||
description={item.description}
|
||||
colorHex={item.colorHex}
|
||||
details={item.details || { primary: [], secondary: [] }}
|
||||
actionOptions={
|
||||
actionOptionsMap[organSegmentationGroup.type] || ['Action']
|
||||
}
|
||||
onAction={(action: string) => handleAction(compositeId, action)}
|
||||
isSelected={selectedRowId === compositeId}
|
||||
onSelect={() => handleRowSelect(compositeId)}
|
||||
isVisible={true}
|
||||
isLocked={false}
|
||||
onToggleVisibility={() => console.debug('Toggle visibility')}
|
||||
onToggleLocked={() => console.debug('Toggle locked')}
|
||||
onRename={() => console.debug('Rename')}
|
||||
onDelete={() => console.debug('Delete')}
|
||||
onColor={() => console.debug('Color')}
|
||||
disableEditing={false}
|
||||
/>
|
||||
</div>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent
|
||||
side="left"
|
||||
align="start"
|
||||
className="w-72 border"
|
||||
>
|
||||
{renderStatisticsPanel(item)}
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user