feat(image capture): Enhance SEG and RTSS Support with Dependency Updates and Bug Fixes (#4924)

This commit is contained in:
Alireza authored and GitHub committed 2025-04-04 12:11:29 -04:00
1 parent 479e280fdf
commit fc33bd0d59
57 files changed
+1247 -935

No files matched your search

+1 -1
View File
@@ -54,7 +54,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^3.8.4",
"@cornerstonejs/dicom-image-loader": "^3.8.5",
"@emotion/serialize": "^1.1.3",
"@ohif/core": "3.10.0-beta.148",
"@ohif/extension-cornerstone": "3.10.0-beta.148",
+1 -1
View File
@@ -15,7 +15,7 @@ function Debug() {
/>
<div className="space-y-2 pt-4 text-center">
<div className="flex flex-col items-center justify-center">
<p className="text-primary-active mt-4 text-xl font-semibold">Debug Information</p>
<p className="text-primary mt-4 text-xl font-semibold">Debug Information</p>
<div className="mt-4 flex items-center space-x-2">
<p className="text-md text-white">Cross Origin Isolated (COOP/COEP)</p>
<Icons.ByName
@@ -343,7 +343,7 @@ function WorkList({
<>
<Icons.GroupLayers
className={classnames('mr-2 inline-flex w-4', {
'text-primary-active': isExpanded,
'text-primary': isExpanded,
'text-secondary-light': !isExpanded,
})}
/>
+1 -1
View File
@@ -37,7 +37,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^3.8.4",
"@cornerstonejs/dicom-image-loader": "^3.8.5",
"@ohif/ui": "3.10.0-beta.148",
"cornerstone-math": "0.1.9",
"dicom-parser": "^1.8.21"
@@ -0,0 +1,503 @@
'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,538 +2,72 @@
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 { panelSegmentationData } from '../../../../ui-next/assets/data';
import { SegmentationTable } from '../../../../ui-next/src/components/SegmentationTable';
import { TooltipProvider } from '../../../../ui-next/src/components/Tooltip';
import {
HoverCard,
HoverCardTrigger,
HoverCardContent,
} from '../../../../ui-next/src/components/HoverCard';
interface DataItem {
id: number;
title: string;
description: string;
optionalField?: string;
colorHex?: string;
details?: { primary: string[]; secondary: string[] };
series?: string;
statistics?: {
centroidX: string;
centroidY: string;
centroidZ: string;
frameDuration: string;
kurtosis: string;
max: string;
maxSlice: string;
mean: string;
median: string;
min: string;
regions: string;
skewness: string;
sphereDiameter: string;
standardDeviation: string;
suvPeak: string;
total: string;
glycolysis: string;
volume: string;
voxelCount: string;
};
}
interface ListGroup {
type: string;
items: DataItem[];
}
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
const tableProps = panelSegmentationData;
tableProps.mode = 'expanded';
const renderSegments = () => {
return (
<SegmentationTable.Segments>
<SegmentationTable.SegmentStatistics.Header></SegmentationTable.SegmentStatistics.Header>
<SegmentationTable.SegmentStatistics.Body />
</SegmentationTable.Segments>
);
};
// 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;
// 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>
);
}
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>
<>
<SegmentationTable.Expanded>
<SegmentationTable.Expanded.Header>
<SegmentationTable.Expanded.DropdownMenu>
{/* <CustomDropdownMenuContent /> */}
</SegmentationTable.Expanded.DropdownMenu>
<SegmentationTable.Expanded.Label />
<SegmentationTable.Expanded.Info />
</SegmentationTable.Expanded.Header>
<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>
<SegmentationTable.Expanded.Content>
<SegmentationTable.AddSegmentRow />
{renderSegments()}
</SegmentationTable.Expanded.Content>
</SegmentationTable.Expanded>
</>
);
};
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 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>
);
}
+179 -1
View File
@@ -1,3 +1,39 @@
interface DataItem {
id: number;
title: string;
description: string;
optionalField?: string;
colorHex?: string;
details?: { primary: string[]; secondary: string[] };
series?: string;
statistics?: {
centroidX: { value: number; unit: string };
centroidY: { value: number; unit: string };
centroidZ: { value: number; unit: string };
frameDuration: { value: number; unit: string };
kurtosis: { value: number; unit: string };
max: { value: number; unit: string };
maxSlice: { value: number; unit: string };
mean: { value: number; unit: string };
median: { value: number; unit: string };
min: { value: number; unit: string };
regions: { value: number; unit: string };
skewness: { value: number; unit: string };
sphereDiameter: { value: number; unit: string };
standardDeviation: { value: number; unit: string };
suvPeak: { value: number; unit: string };
total: { value: number; unit: string };
glycolysis: { value: number; unit: string };
volume: { value: number; unit: string };
voxelCount: { value: number; unit: string };
};
}
interface ListGroup {
type: string;
items: DataItem[];
}
const actionOptionsMap: { [key: string]: string[] } = {
Measurement: ['Rename', 'Lock', 'Delete'],
Segmentation: ['Rename', 'Lock', 'Export', 'Delete'],
@@ -547,4 +583,146 @@ const dataList = [
},
];
export { actionOptionsMap, dataList };
const segmentationData = {
representation: {
id: '791aaa59-1df6-60f8-3c28-6d870e6a4585-Labelmap-default',
segmentationId: '791aaa59-1df6-60f8-3c28-6d870e6a4585',
label: 'Segmentation',
active: true,
type: 'Labelmap',
visible: true,
segments: {
'1': {
color: [255, 0, 0, 255],
segmentIndex: 1,
opacity: 255,
visible: true,
},
'2': {
color: [0, 255, 0, 255],
segmentIndex: 2,
opacity: 255,
visible: true,
},
'3': {
color: [255, 255, 0, 255],
segmentIndex: 3,
opacity: 255,
visible: true,
},
'4': {
color: [0, 255, 255, 255],
segmentIndex: 4,
opacity: 255,
visible: true,
},
},
viewportId: 'default',
colorLUTIndex: 0,
config: {},
},
segmentation: {
segmentationId: '791aaa59-1df6-60f8-3c28-6d870e6a4585',
label: 'Segmentation',
cachedStats: {},
segments: {
'1': {
segmentIndex: 1,
label: 'Liver',
locked: false,
cachedStats: {
category: 'Anatomical Structure',
type: 'Liver',
algorithmType: 'SEMIAUTOMATIC',
algorithmName: 'Amira',
},
active: true,
},
'2': {
segmentIndex: 2,
label: 'Mass',
locked: false,
cachedStats: {
category: 'Morphologically Altered Structure',
type: 'Mass',
algorithmType: 'SEMIAUTOMATIC',
algorithmName: 'Amira',
},
active: false,
},
'3': {
segmentIndex: 3,
label: 'Portal vein',
locked: false,
cachedStats: {
category: 'Anatomical Structure',
type: 'Portal vein',
algorithmType: 'SEMIAUTOMATIC',
algorithmName: 'Amira',
},
active: false,
},
'4': {
segmentIndex: 4,
label: 'Abdominal aorta',
locked: false,
cachedStats: {
category: 'Anatomical Structure',
type: 'Abdominal aorta',
algorithmType: 'SEMIAUTOMATIC',
algorithmName: 'Amira',
},
active: false,
},
},
representationData: {
Labelmap: {
imageIds: [],
referencedImageIds: [],
},
},
},
};
const getSegmentationData = ({
active = false,
segmentationId = '791aaa59-1df6-60f8-3c28-6d870e6a4585',
}) => {
return {
segmentation: {
...segmentationData.segmentation,
active,
segmentationId,
},
representation: {
...segmentationData.representation,
active,
},
};
};
const panelSegmentationData = {
disabled: false,
data: [
getSegmentationData({ active: true }),
getSegmentationData({ active: false, segmentationId: 'babas' }),
],
mode: 'collapsed',
title: 'Segmentations',
exportOptions: [
{
segmentationId: '791aaa59-1df6-60f8-3c28-6d870e6a4585',
isExportable: true,
},
{
segmentationId: '791aaa59-1df6-60f8-3c28-6d870e6a4585',
isExportable: true,
},
],
disableEditing: false,
showAddSegment: true,
renderInactiveSegmentations: true,
};
export { actionOptionsMap, dataList, panelSegmentationData };
export type { DataItem, ListGroup };
@@ -65,7 +65,7 @@ function Header({
onClick={onClickReturn}
data-cy="return-to-work-list"
>
{isReturnEnabled && <Icons.ArrowLeft className="text-primary-active w-8" />}
{isReturnEnabled && <Icons.ArrowLeft className="text-primary w-8" />}
<div className="ml-1">
{WhiteLabeling?.createLogoComponentFn?.(React, props) || <Icons.OHIFLogo />}
</div>
@@ -86,7 +86,7 @@ function Header({
<Button
variant="ghost"
size="icon"
className="text-primary-active hover:bg-primary-dark mt-2 h-full w-full"
className="text-primary hover:bg-primary-dark mt-2 h-full w-full"
>
<Icons.GearSettings />
</Button>
@@ -27,7 +27,7 @@ export const AddSegmentationRow: React.FC<{ children?: React.ReactNode }> = ({
onClick={() => !disabled && onSegmentationAdd('')}
>
{children}
<div className="text-primary-active group-hover:bg-secondary-dark flex items-center rounded-[4px] pl-1 group-hover:cursor-pointer">
<div className="text-primary group-hover:bg-secondary-dark flex items-center rounded-[4px] pl-1 group-hover:cursor-pointer">
<div className="grid h-[28px] w-[28px] place-items-center">
{disabled ? <Icons.Info /> : <Icons.Add />}
</div>
@@ -17,7 +17,7 @@ const SegmentationExpandedHeader = ({ children }: { children: React.ReactNode })
return (
<PanelSection.Header
className={`border-input border-t-2 bg-transparent pl-1 ${isActive ? 'border-primary' : ''} py-0`}
className={`bg-muted my-0 rounded-none border-l-[2px] pl-0 ${isActive ? 'border-primary/70' : 'border-primary/35'}`}
onClick={e => {
e.stopPropagation();
onSegmentationClick(segmentation.segmentationId);
@@ -59,7 +59,7 @@ const SegmentationExpandedInfo = () => {
const { segmentation } = useSegmentationExpanded('SegmentationExpandedInfo');
return (
<div className="ml-auto mr-1">
<div className="ml-auto mr-2">
<Tooltip>
<TooltipTrigger asChild>
<Button
@@ -79,8 +79,11 @@ const SegmentationExpandedInfo = () => {
// Content component
const SegmentationExpandedContent = ({ children }: { children: React.ReactNode }) => {
const { isActive } = useSegmentationExpanded('SegmentationExpandedContent');
return (
<PanelSection.Content className="py-0">
<PanelSection.Content
className={`border-l-[2px] py-0 pb-6 pl-[8px] ${isActive ? 'border-primary/70' : 'border-primary/35'}`}
>
<div className="segmentation-expanded-section">{children}</div>
</PanelSection.Content>
);
@@ -97,14 +100,14 @@ const SegmentationExpandedRoot = ({ children }) => {
}
return (
<div className="space-y-0">
<div className={`space-y-0 pl-0.5`}>
{data.map(segmentationInfo => {
const isActive = segmentationInfo.segmentation.segmentationId === activeSegmentationId;
return (
<PanelSection
key={segmentationInfo.segmentation.segmentationId}
className="mb-0"
className=""
>
<SegmentationExpandedProvider
segmentation={segmentationInfo.segmentation}
@@ -65,42 +65,42 @@ export const SegmentationSegments = ({ children = null }: { children?: React.Rea
const { locked, active, label, displayText } = segmentFromSegmentation;
const cssColor = `rgb(${color[0]},${color[1]},${color[2]})`;
return (
const hasStats = segmentFromSegmentation.cachedStats?.namedStats;
const DataRowComponent = (
<DataRow
key={segmentIndex}
number={segmentIndex}
title={label}
details={displayText}
description={displayText}
colorHex={cssColor}
isSelected={active}
isVisible={visible}
isLocked={locked}
disableEditing={disableEditing}
className={!isActiveSegmentation ? 'opacity-80' : ''}
onColor={() => onSegmentColorClick(segmentation.segmentationId, segmentIndex)}
onToggleVisibility={() =>
onToggleSegmentVisibility(
segmentation.segmentationId,
segmentIndex,
representation.type
)
}
onToggleLocked={() => onToggleSegmentLock(segmentation.segmentationId, segmentIndex)}
onSelect={() => onSegmentClick(segmentation.segmentationId, segmentIndex)}
onRename={() => onSegmentEdit(segmentation.segmentationId, segmentIndex)}
onDelete={() => onSegmentDelete(segmentation.segmentationId, segmentIndex)}
/>
);
return hasStats ? (
<HoverCard
key={`hover-${segmentIndex}`}
openDelay={300}
>
<HoverCardTrigger asChild>
{/* The div here is not random, it is needed for making it work with the hover card */}
<div>
<DataRow
key={segmentIndex}
number={segmentIndex}
title={label}
details={displayText}
description={displayText}
colorHex={cssColor}
isSelected={active}
isVisible={visible}
isLocked={locked}
disableEditing={disableEditing}
className={!isActiveSegmentation ? 'opacity-80' : ''}
onColor={() => onSegmentColorClick(segmentation.segmentationId, segmentIndex)}
onToggleVisibility={() =>
onToggleSegmentVisibility(
segmentation.segmentationId,
segmentIndex,
representation.type
)
}
onToggleLocked={() =>
onToggleSegmentLock(segmentation.segmentationId, segmentIndex)
}
onSelect={() => onSegmentClick(segmentation.segmentationId, segmentIndex)}
onRename={() => onSegmentEdit(segmentation.segmentationId, segmentIndex)}
onDelete={() => onSegmentDelete(segmentation.segmentationId, segmentIndex)}
/>
</div>
<div>{DataRowComponent}</div>
</HoverCardTrigger>
<HoverCardContent
side="left"
@@ -126,6 +126,8 @@ export const SegmentationSegments = ({ children = null }: { children?: React.Rea
</SegmentStatistics>
</HoverCardContent>
</HoverCard>
) : (
DataRowComponent
);
})}
</ScrollArea>
@@ -112,7 +112,7 @@ export const SegmentationTableRoot = (props: SegmentationTableProps) => {
{hasConfigComponent && (
<div className="ml-auto mr-2">
<Icons.Settings
className="text-primary-active h-4 w-4"
className="text-primary h-4 w-4"
onClick={e => {
e.stopPropagation();
toggleShowConfig();
@@ -61,13 +61,13 @@ export const SegmentationTableConfig: React.FC<{ children?: React.ReactNode }> =
>
<TabsList>
<TabsTrigger value="fill-and-outline">
<Icons.FillAndOutline className="text-primary-active" />
<Icons.FillAndOutline className="text-primary" />
</TabsTrigger>
<TabsTrigger value="outline">
<Icons.OutlineOnly className="text-primary-active" />
<Icons.OutlineOnly className="text-primary" />
</TabsTrigger>
<TabsTrigger value="fill">
<Icons.FillOnly className="text-primary-active" />
<Icons.FillOnly className="text-primary" />
</TabsTrigger>
</TabsList>
</Tabs>
@@ -112,7 +112,7 @@ const getTabClassNames = (
isTabDisabled: boolean
) =>
classnames('h-[28px] mb-[2px] cursor-pointer text-white bg-black', {
'hover:text-primary-active': !isActiveTab && !isTabDisabled,
'hover:text-primary': !isActiveTab && !isTabDisabled,
'rounded-l': tabIndex % numColumns === 0,
'rounded-r': (tabIndex + 1) % numColumns === 0 || tabIndex === numTabs - 1,
});
@@ -294,7 +294,7 @@ const SidePanel = ({
data-cy={`side-panel-header-${side}`}
>
<Icons.NavigationPanelReveal
className={classnames('text-primary-active', side === 'left' && 'rotate-180 transform')}
className={classnames('text-primary', side === 'left' && 'rotate-180 transform')}
/>
</div>
<div className={classnames('mt-3 flex flex-col space-y-3')}>
@@ -304,14 +304,14 @@ const SidePanel = ({
<div
id={`${childComponent.name}-btn`}
data-cy={`${childComponent.name}-btn`}
className="text-primary-active hover:cursor-pointer"
className="text-primary hover:cursor-pointer"
onClick={() => {
return childComponent.disabled ? null : updateActiveTabIndex(index, true);
}}
>
{React.createElement(Icons[childComponent.iconName] || Icons.MissingIcon, {
className: classnames({
'text-primary-active': true,
'text-primary': true,
'ohif-disabled': childComponent.disabled,
}),
style: {
@@ -352,7 +352,7 @@ const SidePanel = ({
data-cy={`side-panel-header-${side}`}
>
{React.createElement(Icons[openStateIconName[side]] || Icons.MissingIcon, {
className: 'text-primary-active',
className: 'text-primary',
})}
</div>
);
@@ -365,7 +365,7 @@ const SidePanel = ({
<>
{getCloseIcon()}
<div className={classnames('flex grow justify-center')}>
<div className={classnames('bg-primary-dark text-primary-active flex flex-wrap')}>
<div className={classnames('bg-primary-dark text-primary flex flex-wrap')}>
{tabs.map((tab, tabIndex) => {
const { disabled } = tab;
return (
@@ -401,7 +401,7 @@ const SidePanel = ({
>
{React.createElement(Icons[tab.iconName] || Icons.MissingIcon, {
className: classnames({
'text-primary-active': true,
'text-primary': true,
'ohif-disabled': disabled,
}),
style: {
@@ -429,7 +429,7 @@ const SidePanel = ({
return (
<div
className={classnames(
'text-primary-active flex grow cursor-pointer select-none justify-center self-center text-[13px]'
'text-primary flex grow cursor-pointer select-none justify-center self-center text-[13px]'
)}
data-cy={`${tabs[0].name}-btn`}
onClick={() => updatePanelOpen(!panelOpen)}
@@ -111,7 +111,7 @@ const Thumbnail = ({
<TooltipContent side="right">
<div className="flex flex-1 flex-row">
<div className="flex-2 flex items-center justify-center pr-4">
<Icons.InfoLink className="text-primary-active" />
<Icons.InfoLink className="text-primary" />
</div>
<div className="flex flex-1 flex-col">
<span>
@@ -226,7 +226,7 @@ const Thumbnail = ({
<TooltipContent side="right">
<div className="flex flex-1 flex-row">
<div className="flex-2 flex items-center justify-center pr-4">
<Icons.InfoLink className="text-primary-active" />
<Icons.InfoLink className="text-primary" />
</div>
<div className="flex flex-1 flex-col">
<span>
@@ -1,6 +1,6 @@
const getToggledClassName = isToggled => {
return isToggled
? '!text-primary-active'
? '!text-primary'
: '!text-common-bright hover:!bg-primary-dark hover:text-primary-light';
};