init: sudah ganti logo, hilangin setting, dan investigational use dialog

This commit is contained in:
one
2025-03-06 11:32:45 +07:00
commit 8f31d4ed41
2857 changed files with 355646 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body class="bg-gray-100">
<div
id="root"
class="min-h-screen"
></div>
</body>
</html>

View File

@@ -0,0 +1,11 @@
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.base.js');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
const ENTRY = {
app: `${SRC_DIR}/index.ts`,
};
module.exports = (env, argv) => {
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY });
};

View File

@@ -0,0 +1,60 @@
const { merge } = require('webpack-merge');
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const webpackCommon = require('./../../../.webpack/webpack.base.js');
const pkg = require('./../package.json');
const ROOT_DIR = path.join(__dirname, './..');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
const ENTRY = {
app: `${SRC_DIR}/index.ts`,
};
const outputName = `ohif-${pkg.name.split('/').pop()}`;
module.exports = (env, argv) => {
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY });
return merge(commonConfig, {
stats: {
colors: true,
hash: true,
timings: true,
assets: true,
chunks: false,
chunkModules: false,
modules: false,
children: false,
warnings: true,
},
optimization: {
minimize: true,
sideEffects: false,
},
output: {
path: ROOT_DIR,
library: 'ohif-ui',
libraryTarget: 'umd',
filename: pkg.main,
},
externals: [
/\b(dcmjs)/,
/\b(gl-matrix)/,
{
react: 'React',
'react-dom': 'ReactDOM',
},
],
plugins: [
new MiniCssExtractPlugin({
filename: `./dist/${outputName}.css`,
chunkFilename: `./dist/${outputName}.css`,
}),
// new BundleAnalyzerPlugin({}),
],
});
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,256 @@
const actionOptionsMap: { [key: string]: string[] } = {
Measurement: ['Rename', 'Lock', 'Delete'],
Segmentation: ['Rename', 'Lock', 'Export', 'Delete'],
'ROI Tools': ['Rename', 'Lock', 'Delete'],
'Organ Segmentation': ['Rename', 'Lock', 'Export', 'Delete'],
// Add more types and their corresponding actions as needed
};
const dataList = [
{
type: 'Measurement',
items: [
{
id: 1,
title: 'Measurement Label',
description: 'Description for Measurement One.',
optionalField: 'Optional Info 1',
details: { primary: ['Data'], secondary: [] },
},
{
id: 2,
title: 'Measurement Label',
description: 'Description for Measurement Two.',
details: { primary: ['Data'], secondary: [] },
},
],
},
{
type: 'Segmentation',
items: [
{
id: 3,
title: 'Segmentation One',
colorHex: '#FF5733',
description: 'Description for Segmentation One.',
},
{
id: 4,
title: 'Segmentation Two',
colorHex: '#FF5733',
description: 'Description for Segmentation Two.',
},
{
id: 5,
title: 'Segmentation Three',
colorHex: '#FF5733',
description: 'Description for Segmentation Three.',
},
],
},
{
type: 'ROI Tools',
items: [
{
id: 6,
title: 'Linear',
description: 'Description for Linear.',
details: { primary: ['49.2 mm'], secondary: ['S2 I:1'] },
},
{
id: 7,
title: 'Bidirectional',
description: 'Description for Bidirectional.',
details: { primary: ['L: 34.5 mm', 'W: 23.0 mm'], secondary: ['S:2 I:2'] },
},
{
id: 8,
title: 'Ellipse',
description: 'Description for Ellipse.',
details: { primary: ['2641 mm²', 'Max: 1087 HU'], secondary: ['S:2 I:4'] },
},
{
id: 9,
title: 'Rectangle',
description: 'Description for Rectangle.',
details: { primary: ['1426 mm²', 'Max: 718 HU'], secondary: ['S:2 I:5'] },
},
{
id: 10,
title: 'Circle',
description: 'Description for Circle.',
details: { primary: ['7339 mm²', 'Max: 871 HU'], secondary: ['S:2 I:6'] },
},
{
id: 11,
title: 'Freehand ROI',
description: 'Description for Freehand ROI.',
details: {
primary: ['Mean: 215 HU', 'Max: 947 HU', 'Area: 839 mm²'],
secondary: ['S:2 I:7', 'S:3 I:7'],
},
},
{
id: 12,
title: 'Spline Tool',
description: 'Description for Spline Tool.',
details: { primary: ['Area: 203 mm²'], secondary: ['S:2 I:8'] },
},
{
id: 13,
title: 'Livewire Tool',
description: 'Description for Livewire Tool.',
details: { primary: ['Area: 203 mm²'], secondary: ['S:2 I:3'] },
},
{
id: 14,
title: 'Annotation Lorem ipsum dolor sit amet long measurement name continues here',
description: 'Description for Annotation.',
details: { primary: ['Area: 203 mm²'], secondary: ['S:2 I:3'] },
},
],
},
{
type: 'Organ Segmentation',
items: [
{
id: 15,
title: 'Spleen',
description: 'Description for Spleen.',
colorHex: '#6B8E23',
},
{
id: 16,
title: 'Kidney',
description: 'Description for Kidney.',
colorHex: '#4682B4',
},
{
id: 17,
title: 'Kidney very long title name lorem ipsum dolor sit amet segmentation',
description: 'Description for Kidney.',
colorHex: '#9ACD32',
},
{
id: 18,
title: 'Gallbladder',
description: 'Description for Gallbladder.',
colorHex: '#20B2AA',
},
{
id: 19,
title: 'Esophagus',
description: 'Description for Esophagus.',
colorHex: '#DAA520',
},
{
id: 20,
title: 'Liver',
description: 'Description for Liver.',
colorHex: '#CD5C5C',
},
{
id: 21,
title: 'Stomach',
description: 'Description for Stomach.',
colorHex: '#778899',
},
{
id: 22,
title: 'Abdominal aorta',
description: 'Description for Abdominal Aorta.',
colorHex: '#B8860B',
},
{
id: 23,
title: 'Inferior vena cava',
description: 'Description for Inferior Vena Cava.',
colorHex: '#556B2F',
},
{
id: 24,
title: 'Portal vein',
description: 'Description for Portal Vein.',
colorHex: '#8B4513',
},
{
id: 25,
title: 'Pancreas',
description: 'Description for Pancreas.',
colorHex: '#2F4F4F',
},
{
id: 26,
title: 'Adrenal gland',
description: 'Description for Adrenal Gland.',
colorHex: '#708090',
},
{
id: 27,
title: 'Adrenal gland',
description: 'Description for Adrenal Gland.',
colorHex: '#6A5ACD',
},
{
id: 28,
title: 'New Seg Test New Seg Test New Seg Test New Seg Test New Seg Test New Seg Test ',
description: 'Description for New Seg Test.',
colorHex: '#4682B4',
},
],
},
{
type: 'TMTV1',
items: [
{
id: 29,
title: 'Segment 1',
colorHex: '#FF6F61',
description: 'Description for Segmentation One.',
details: { primary: ['SUV Peak: NaN', 'Volume: 21.56mm³'], secondary: [] },
},
{
id: 30,
title: 'Segment 2',
colorHex: '#00CED1',
description: 'Description for Segmentation Two.',
details: { primary: ['SUV Peak: NaN', 'Volume: 21.56mm³'], secondary: [] },
},
{
id: 31,
title: 'Segment 3',
colorHex: '#88B04B',
description: 'Description for Segmentation One.',
details: { primary: ['SUV Peak: NaN', 'Volume: 21.56mm³'], secondary: [] },
},
],
},
{
type: 'TMTV2',
items: [
{
id: 32,
title: 'Segment A',
colorHex: '#FF6F61',
description: 'Description for Segmentation One.',
details: { primary: ['SUV Peak: NaN', 'Volume: 21.56mm³'], secondary: [] },
},
{
id: 33,
title: 'Segment B',
colorHex: '#00CED1',
description: 'Description for Segmentation Two.',
details: { primary: ['SUV Peak: NaN', 'Volume: 21.56mm³'], secondary: [] },
},
{
id: 34,
title: 'Segment C',
colorHex: '#88B04B',
description: 'Description for Segmentation One.',
details: { primary: ['SUV Peak: NaN', 'Volume: 21.56mm³'], secondary: [] },
},
],
},
];
export { actionOptionsMap, dataList };

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,56 @@
// https://babeljs.io/docs/en/options#babelrcroots
const { extendDefaultPlugins } = require('svgo');
module.exports = {
babelrcRoots: ['./platform/*', './extensions/*', './modes/*'],
presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'],
plugins: [
['@babel/plugin-proposal-class-properties', { loose: true }],
'@babel/plugin-transform-typescript',
['@babel/plugin-proposal-private-methods', { loose: true }],
'@babel/plugin-transform-class-static-block',
],
env: {
test: {
presets: [
[
// TODO: https://babeljs.io/blog/2019/03/19/7.4.0#migration-from-core-js-2
'@babel/preset-env',
{
modules: 'commonjs',
debug: false,
},
],
'@babel/preset-react',
'@babel/preset-typescript',
],
plugins: [
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-transform-regenerator',
'@babel/transform-destructuring',
'@babel/plugin-transform-runtime',
'@babel/plugin-transform-typescript',
'@babel/plugin-transform-class-static-block',
],
},
production: {
presets: [
// WebPack handles ES6 --> Target Syntax
['@babel/preset-env', { modules: false }],
'@babel/preset-react',
'@babel/preset-typescript',
],
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
},
development: {
presets: [
// WebPack handles ES6 --> Target Syntax
['@babel/preset-env', { modules: false }],
'@babel/preset-react',
'@babel/preset-typescript',
],
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
},
},
};

View File

@@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/tailwind.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "src/components",
"utils": "src/lib/utils"
}
}

View File

@@ -0,0 +1,70 @@
{
"name": "@ohif/ui-next",
"version": "3.10.0-beta.111",
"description": "Next version of OHIF Viewers UI, more customizable using shadcn/ui",
"main": "dist/ohif-ui-next.umd.js",
"module": "src/index.ts",
"publishConfig": {
"access": "public"
},
"files": [
"dist",
"README.md"
],
"scripts": {
"clean": "rm -rf node_modules/.cache/storybook && shx rm -rf dist",
"clean:deep": "yarn run clean && shx rm -rf node_modules",
"start": "yarn run build --watch",
"dev": "cross-env NODE_ENV=development webpack serve --config .webpack/webpack.playground.js",
"test": "echo \"Error: no test specified\" && exit 1",
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
"build:package": "yarn run build"
},
"exports": {
"./tailwind.config": "./tailwind.config.ts",
"./lib/*": "./src/lib/*.ts",
"./components/*": "./src/components/*.tsx",
".": "./src/index.ts"
},
"dependencies": {
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-context-menu": "^2.2.4",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slider": "^1.2.0",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"class-variance-authority": "^0.7.0",
"clsx": "*",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
"framer-motion": "6.2.4",
"lucide-react": "^0.379.0",
"next-themes": "^0.3.0",
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"react-resizable-panels": "^2.1.7",
"react-shepherd": "6.1.1",
"shepherd.js": "13.0.3",
"sonner": "^1.5.0",
"tailwind-merge": "^2.3.0",
"tailwindcss": "3.2.4",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.16.7"
},
"keywords": [],
"author": "OHIF",
"license": "MIT"
}

View File

@@ -0,0 +1,59 @@
'use client';
import * as React from 'react';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
import { ChevronDownIcon } from '@radix-ui/react-icons';
import { cn } from '../../lib/utils';
const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn(className)}
{...props}
/>
));
AccordionItem.displayName = 'AccordionItem';
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
'flex flex-1 items-center justify-between py-2 px-2 text-base font-medium transition-transform duration-200',
className,
'[&[data-state=open]>svg]:rotate-270',
'[&[data-state=closed]>svg]:rotate-90'
)}
{...props}
>
{children}
<ChevronDownIcon className="text-primary h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-base"
{...props}
>
<div className={cn(className)}>{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

View File

@@ -0,0 +1,3 @@
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from './Accordion';
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };

View File

@@ -0,0 +1,38 @@
'use client';
import * as React from 'react';
import { useState, useEffect } from 'react';
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../Select';
const BackgroundColorSelect: React.FC = () => {
const [selectedColor, setSelectedColor] = useState('#050615');
useEffect(() => {
const rows = document.querySelectorAll('.row') as NodeListOf<HTMLElement>;
rows.forEach(row => {
row.style.backgroundColor = selectedColor;
});
}, [selectedColor]);
const handleColorChange = (value: string) => {
setSelectedColor(value);
};
return (
<div>
<Select onValueChange={handleColorChange}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Select Color" />
</SelectTrigger>
<SelectContent>
<SelectItem value="black">Viewport (Black)</SelectItem>
<SelectItem value="#050615">Base</SelectItem>
<SelectItem value="#090C29">Medium</SelectItem>
<SelectItem value="#041C4A">Header</SelectItem>
</SelectContent>
</Select>
</div>
);
};
export default BackgroundColorSelect;

View File

@@ -0,0 +1 @@
export { default as BackgroundColorSelect } from './BackgroundColorSelect';

View File

@@ -0,0 +1,54 @@
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '../../lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded text-base font-normal leading-tight transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'bg-primary/60 text-primary-foreground hover:bg-primary/100',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline:
'border border-primary/25 bg-background hover:bg-primary/25 text-primary hover:text-primary',
secondary: 'bg-primary/40 text-secondary-foreground hover:bg-primary/60',
ghost: 'font-normal text-primary hover:bg-primary/25',
link: 'font-normal text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-7 px-2 py-2',
sm: 'h-6 rounded px-2',
lg: 'h-9 rounded px-2',
icon: 'h-6 w-6',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, forwardRef) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(buttonVariants({ variant, size }), className)}
ref={forwardRef}
{...props}
/>
);
}
);
Button.displayName = 'Button';
export { Button, buttonVariants };

View File

@@ -0,0 +1 @@
export { Button, buttonVariants } from './Button';

View File

@@ -0,0 +1,61 @@
import * as React from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
import { DayPicker } from 'react-day-picker';
import { cn } from '../../lib/utils';
import { buttonVariants } from '../Button';
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
return (
<DayPicker
showOutsideDays={showOutsideDays}
className={cn('p-3', className)}
captionLayout="dropdown"
fromYear={1945}
toYear={new Date().getFullYear()}
labels={{
labelMonthDropdown: () => undefined,
labelYearDropdown: () => undefined,
}}
classNames={{
months: 'flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0',
month: 'space-y-4',
caption: 'flex justify-between items-center px-2',
caption_dropdowns: 'flex space-x-2 text-black',
caption_label: 'hidden',
nav: 'space-x-1 flex items-center',
table: 'w-full border-collapse space-y-1',
head_row: 'flex',
head_cell: 'text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]',
row: 'flex w-full mt-2',
cell: 'h-9 w-9 text-center text-base p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20',
day: cn(
buttonVariants({ variant: 'ghost' }),
'h-9 w-9 p-0 font-normal aria-selected:opacity-100'
),
day_range_end: 'day-range-end',
day_selected:
'bg-primary/60 text-primary-foreground hover:bg-primary/80 hover:text-primary-foreground focus:bg-primary/80 focus:text-primary-foreground',
day_today: 'bg-accent text-accent-foreground',
day_outside:
'day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30',
day_disabled: 'text-muted-foreground opacity-50',
day_range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground',
day_hidden: 'invisible',
...classNames,
}}
components={{
IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
}}
{...props}
/>
);
}
Calendar.displayName = 'Calendar';
export { Calendar };

View File

@@ -0,0 +1,3 @@
import { Calendar } from './Calendar';
export { Calendar};

View File

@@ -0,0 +1,75 @@
import * as React from 'react';
import { cn } from '../../lib/utils';
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
'bg-card text-card-foreground border-input rounded-lg border shadow',
className
)}
{...props}
/>
)
);
Card.displayName = 'Card';
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('flex flex-col space-y-1.5 p-6', className)}
{...props}
/>
)
);
CardHeader.displayName = 'CardHeader';
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn('font-semibold leading-none tracking-tight', className)}
{...props}
/>
)
);
CardTitle.displayName = 'CardTitle';
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn('text-muted-foreground text-base', className)}
{...props}
/>
));
CardDescription.displayName = 'CardDescription';
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('p-6 pt-0', className)}
{...props}
/>
)
);
CardContent.displayName = 'CardContent';
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('flex items-center p-6 pt-0', className)}
{...props}
/>
)
);
CardFooter.displayName = 'CardFooter';
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };

View File

@@ -0,0 +1,2 @@
import { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from './Card';
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };

View File

@@ -0,0 +1,26 @@
import * as React from 'react';
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import { CheckIcon } from '@radix-ui/react-icons';
import { cn } from '../../lib/utils';
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
'border-primary hover:bg-primary/20 focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground peer h-4 w-4 shrink-0 rounded-sm border shadow focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50',
className
)}
{...props}
>
<CheckboxPrimitive.Indicator className={cn('text-background flex items-center justify-center')}>
<CheckIcon className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
export { Checkbox };

View File

@@ -0,0 +1,3 @@
import { Checkbox } from './Checkbox';
export { Checkbox };

View File

@@ -0,0 +1,55 @@
import React, { ReactNode } from 'react';
import { Button } from '../Button';
import { Icons } from '../Icons';
interface ClipboardProps {
children: ReactNode;
}
const Clipboard: React.FC<ClipboardProps> = ({ children }) => {
const [copyState, setCopyState] = React.useState<'idle' | 'success' | 'error'>('idle');
const copyText = React.useMemo(() => {
if (typeof children === 'string') {
return children.trim();
}
return '';
}, [children]);
const handleCopy = React.useCallback(async () => {
if (!copyText) {
return;
}
try {
await navigator.clipboard.writeText(copyText);
setCopyState('success');
} catch {
setCopyState('error');
} finally {
setTimeout(() => setCopyState('idle'), 1500); // Reset state after feedback
}
}, [copyText]);
return (
<Button
variant="ghost"
size="icon"
onClick={e => {
e.stopPropagation();
handleCopy();
}}
className="text-white"
title="Copy"
>
{copyState === 'idle' && <Icons.Copy className="h-6 w-6" />}
{copyState === 'success' && <Icons.FeedbackComplete className="h-6 w-6 text-white" />}
{copyState === 'error' && (
<Icons.ByName
name="Error"
className="h-6 w-6 text-white"
/>
)}
</Button>
);
};
export { Clipboard };

View File

@@ -0,0 +1,3 @@
import { Clipboard } from './Clipboard';
export { Clipboard };

View File

@@ -0,0 +1,66 @@
import * as React from 'react';
import { Check, ChevronsUpDown } from 'lucide-react';
import { cn } from '../../lib/utils';
import { Button } from '../Button/Button';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '../Command/Command';
import { Popover, PopoverContent, PopoverTrigger } from '../Popover/Popover';
export function Combobox({ data = [], placeholder = 'Select item...' }) {
const [open, setOpen] = React.useState(false);
const [value, setValue] = React.useState('');
return (
<Popover
open={open}
onOpenChange={setOpen}
>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-[200px] justify-between"
>
{value ? data.find(item => item.value === value)?.label : placeholder}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput placeholder={`Search ${placeholder.toLowerCase()}...`} />
<CommandEmpty>No {placeholder.toLowerCase()} found.</CommandEmpty>
<CommandList>
<CommandGroup>
{data.map(item => (
<CommandItem
key={item.value}
value={item.value}
onSelect={currentValue => {
setValue(currentValue === value ? '' : currentValue);
setOpen(false);
}}
>
<Check
className={cn(
'mr-2 h-4 w-4',
value === item.value ? 'opacity-100' : 'opacity-0'
)}
/>
{item.label}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}

View File

@@ -0,0 +1,3 @@
import { Combobox } from './Combobox';
export { Combobox};

View File

@@ -0,0 +1,150 @@
import * as React from 'react';
import { type DialogProps } from '@radix-ui/react-dialog';
import { MagnifyingGlassIcon } from '@radix-ui/react-icons';
import { Command as CommandPrimitive } from 'cmdk';
import { cn } from '../../lib/utils';
import { Dialog, DialogContent } from '../Dialog/Dialog';
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
'bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md',
className
)}
{...props}
/>
));
Command.displayName = CommandPrimitive.displayName;
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0">
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
);
};
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div
className="flex items-center border-b px-3"
cmdk-input-wrapper=""
>
<MagnifyingGlassIcon className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
'placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-base outline-none disabled:cursor-not-allowed disabled:opacity-50',
className
)}
{...props}
/>
</div>
));
CommandInput.displayName = CommandPrimitive.Input.displayName;
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn('max-h-[300px] overflow-y-auto overflow-x-hidden', className)}
{...props}
/>
));
CommandList.displayName = CommandPrimitive.List.displayName;
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-base"
{...props}
/>
));
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
'text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-sm [&_[cmdk-group-heading]]:font-medium',
className
)}
{...props}
/>
));
CommandGroup.displayName = CommandPrimitive.Group.displayName;
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn('bg-border -mx-1 h-px', className)}
{...props}
/>
));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
'aria-selected:bg-accent aria-selected:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-base outline-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50',
className
)}
{...props}
/>
));
CommandItem.displayName = CommandPrimitive.Item.displayName;
const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn('text-muted-foreground ml-auto text-sm tracking-widest', className)}
{...props}
/>
);
};
CommandShortcut.displayName = 'CommandShortcut';
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
};

View File

@@ -0,0 +1,23 @@
import {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
} from './Command';
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
};

View File

@@ -0,0 +1,187 @@
import * as React from 'react';
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
import { cn } from '../../lib/utils';
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from '@radix-ui/react-icons';
const ContextMenu = ContextMenuPrimitive.Root;
const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
const ContextMenuGroup = ContextMenuPrimitive.Group;
const ContextMenuPortal = ContextMenuPrimitive.Portal;
const ContextMenuSub = ContextMenuPrimitive.Sub;
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
const ContextMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
<ContextMenuPrimitive.SubTrigger
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none',
inset && 'pl-8',
className
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto h-4 w-4" />
</ContextMenuPrimitive.SubTrigger>
));
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
const ContextMenuSubContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.SubContent
ref={ref}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg',
className
)}
{...props}
/>
));
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
const ContextMenuContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Portal>
<ContextMenuPrimitive.Content
ref={ref}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
className
)}
{...props}
/>
</ContextMenuPrimitive.Portal>
));
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
const ContextMenuItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Item
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className
)}
{...props}
/>
));
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
const ContextMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<ContextMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
));
ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName;
const ContextMenuRadioItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<ContextMenuPrimitive.RadioItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<DotFilledIcon className="h-4 w-4 fill-current" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
));
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
const ContextMenuLabel = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label
ref={ref}
className={cn('text-foreground px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
{...props}
/>
));
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator
ref={ref}
className={cn('bg-border -mx-1 my-1 h-px', className)}
{...props}
/>
));
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
const ContextMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)}
{...props}
/>
);
};
ContextMenuShortcut.displayName = 'ContextMenuShortcut';
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
};

View File

@@ -0,0 +1,35 @@
import {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
} from './ContextMenu';
export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
};

View File

@@ -0,0 +1,338 @@
import React, { useState, useEffect, useRef } from 'react';
import { Button } from '../../components/Button/Button';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
} from '../../components/DropdownMenu';
import { Icons } from '../../components/Icons/Icons';
import { Tooltip, TooltipTrigger, TooltipContent } from '../../components/Tooltip/Tooltip';
/**
* DataRow is a complex UI component that displays a selectable, interactive row with hierarchical data.
* It's designed to show a numbered item with a title, optional color indicator, and expandable details.
* The row supports various interactive features like visibility toggling, locking, and contextual actions.
*
* @component
* @example
* ```tsx
* <DataRow
* number={1}
* title="My Item"
* details={{
* primary: ["Main detail", " Sub detail"],
* secondary: []
* }}
* isVisible={true}
* isLocked={false}
* onToggleVisibility={() => {}}
* onToggleLocked={() => {}}
* onRename={() => {}}
* onDelete={() => {}}
* onColor={() => {}}
* />
* ```
*/
/**
* Props for the DataRow component
* @interface DataRowProps
* @property {number} number - The display number/index of the row
* @property {string} title - The main text label for the row
* @property {boolean} disableEditing - When true, prevents rename and delete operations
* @property {string} [colorHex] - Optional hex color code to display a color indicator
* @property {Object} [details] - Optional hierarchical details to display below the row
* @property {string[]} details.primary - Primary details shown immediately below the row
* @property {string[]} details.secondary - Secondary details (currently unused)
* @property {boolean} [isSelected] - Whether the row is currently selected
* @property {() => void} [onSelect] - Callback when the row is clicked/selected
* @property {boolean} isVisible - Controls the row's visibility state
* @property {() => void} onToggleVisibility - Callback to toggle visibility
* @property {boolean} isLocked - Controls the row's locked state
* @property {() => void} onToggleLocked - Callback to toggle locked state
* @property {() => void} onRename - Callback when rename is requested
* @property {() => void} onDelete - Callback when delete is requested
* @property {() => void} onColor - Callback when color change is requested
*/
interface DataRowProps {
number: number;
disableEditing: boolean;
description: string;
details?: { primary: string[]; secondary: string[] };
//
isSelected?: boolean;
onSelect?: () => void;
//
isVisible: boolean;
onToggleVisibility: () => void;
//
isLocked: boolean;
onToggleLocked: () => void;
//
title: string;
onRename: () => void;
//
onDelete: () => void;
//
colorHex?: string;
onColor: () => void;
}
const DataRow: React.FC<DataRowProps> = ({
number,
title,
colorHex,
details,
onSelect,
isLocked,
onToggleVisibility,
onToggleLocked,
onRename,
onDelete,
onColor,
isSelected = false,
isVisible = true,
disableEditing = false,
}) => {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const isTitleLong = title?.length > 25;
const rowRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (isSelected && rowRef.current) {
rowRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}, [isSelected]);
const handleAction = (action: string, e: React.MouseEvent) => {
e.stopPropagation();
switch (action) {
case 'Rename':
onRename();
break;
case 'Lock':
onToggleLocked();
break;
case 'Delete':
onDelete();
break;
case 'Color':
onColor();
break;
}
};
const decodeHTML = (html: string) => {
const txt = document.createElement('textarea');
txt.innerHTML = html;
return txt.value;
};
const renderDetailText = (text: string, indent: number = 0) => {
const indentation = ' '.repeat(indent);
if (text === '') {
return (
<div
key={`empty-${indent}`}
className="h-2"
></div>
);
}
const cleanText = decodeHTML(text);
return (
<div
key={cleanText}
className="whitespace-pre-wrap"
>
{indentation}
<span className="font-medium">{cleanText}</span>
</div>
);
};
const renderDetails = (details: string[]) => {
const visibleLines = details.slice(0, 4);
const hiddenLines = details.slice(4);
return (
<Tooltip>
<TooltipTrigger asChild>
<div className="cursor-help">
<div className="flex flex-col space-y-1">
{visibleLines.map((line, lineIndex) =>
renderDetailText(line, line.startsWith(' ') ? 1 : 0)
)}
</div>
{hiddenLines.length > 0 && (
<div className="text-muted-foreground mt-1 flex items-center text-sm">
<span>...</span>
<Icons.Info className="mr-1 h-5 w-5" />
</div>
)}
</div>
</TooltipTrigger>
<TooltipContent
side="right"
align="start"
className="max-w-md"
>
<div className="text-secondary-foreground flex flex-col space-y-1 text-sm leading-normal">
{details.map((line, lineIndex) =>
renderDetailText(line, line.startsWith(' ') ? 1 : 0)
)}
</div>
</TooltipContent>
</Tooltip>
);
};
return (
<div
ref={rowRef}
className={`flex flex-col ${isVisible ? '' : 'opacity-60'}`}>
<div
className={`flex items-center ${
isSelected ? 'bg-popover' : 'bg-muted'
} group relative cursor-pointer`}
onClick={onSelect}
data-cy="data-row"
>
{/* Hover Overlay */}
<div className="bg-primary/20 pointer-events-none absolute inset-0 opacity-0 transition-opacity group-hover:opacity-100"></div>
{/* Number Box */}
<div
className={`flex h-7 max-h-7 w-7 flex-shrink-0 items-center justify-center rounded-l border-r border-black text-base ${
isSelected ? 'bg-highlight text-black' : 'bg-muted text-muted-foreground'
} overflow-hidden`}
>
{number}
</div>
{/* Color Circle (Optional) */}
{colorHex && (
<div className="flex h-7 w-5 items-center justify-center">
<span
className="ml-2 h-2 w-2 rounded-full"
style={{ backgroundColor: colorHex }}
></span>
</div>
)}
{/* Label with Conditional Tooltip */}
<div className="ml-2 flex-1 overflow-hidden">
{isTitleLong ? (
<Tooltip>
<TooltipTrigger asChild>
<span
className={`cursor-default text-base ${
isSelected ? 'text-highlight' : 'text-muted-foreground'
} [overflow:hidden] [display:-webkit-box] [-webkit-line-clamp:2] [-webkit-box-orient:vertical]`}
>
{title}
</span>
</TooltipTrigger>
<TooltipContent
side="top"
align="center"
>
{title}
</TooltipContent>
</Tooltip>
) : (
<span
className={`text-base ${
isSelected ? 'text-highlight' : 'text-muted-foreground'
} [overflow:hidden] [display:-webkit-box] [-webkit-line-clamp:2] [-webkit-box-orient:vertical]`}
>
{title}
</span>
)}
</div>
{/* Actions and Visibility Toggle */}
<div className="relative ml-2 flex items-center space-x-1">
{/* Visibility Toggle Icon */}
<Button
size="icon"
variant="ghost"
className={`h-6 w-6 transition-opacity ${
isSelected || !isVisible ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'
}`}
aria-label={isVisible ? 'Hide' : 'Show'}
onClick={e => {
e.stopPropagation();
onToggleVisibility();
}}
>
{isVisible ? <Icons.Hide className="h-6 w-6" /> : <Icons.Show className="h-6 w-6" />}
</Button>
{/* Lock Icon (if needed) */}
{isLocked && !disableEditing && <Icons.Lock className="text-muted-foreground h-6 w-6" />}
{/* Actions Dropdown Menu */}
{disableEditing && <div className="h-6 w-6"></div>}
{!disableEditing && (
<DropdownMenu onOpenChange={open => setIsDropdownOpen(open)}>
<DropdownMenuTrigger asChild>
<Button
size="icon"
variant="ghost"
className={`h-6 w-6 transition-opacity ${
isSelected || isDropdownOpen
? 'opacity-100'
: 'opacity-0 group-hover:opacity-100'
}`}
aria-label="Actions"
onClick={e => e.stopPropagation()} // Prevent row selection on button click
>
<Icons.More className="h-6 w-6" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<>
<DropdownMenuItem onClick={e => handleAction('Rename', e)}>
<Icons.Rename className="text-foreground" />
<span className="pl-2">Rename</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={e => handleAction('Delete', e)}>
<Icons.Delete className="text-foreground" />
<span className="pl-2">Delete</span>
</DropdownMenuItem>
{onColor && (
<DropdownMenuItem onClick={e => handleAction('Color', e)}>
<Icons.ColorChange className="text-foreground" />
<span className="pl-2">Change Color</span>
</DropdownMenuItem>
)}
<DropdownMenuItem onClick={e => handleAction('Lock', e)}>
<Icons.Lock className="text-foreground" />
<span className="pl-2">{isLocked ? 'Unlock' : 'Lock'}</span>
</DropdownMenuItem>
</>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</div>
{/* Details Section */}
{details && (details.primary?.length > 0 || details.secondary?.length > 0) && (
<div className="ml-7 px-2 py-2">
<div className="text-secondary-foreground flex items-center gap-1 text-base leading-normal">
{details.primary?.length > 0 && renderDetails(details.primary)}
{details.secondary?.length > 0 && (
<div className="text-muted-foreground ml-auto text-sm">
{renderDetails(details.secondary)}
</div>
)}
</div>
</div>
)}
</div>
);
};
export default DataRow;

View File

@@ -0,0 +1,3 @@
import DataRow from './DataRow';
export { DataRow };

View File

@@ -0,0 +1,31 @@
/**
* Represents a single data item in a list or table structure
*
* @interface DataItem
*/
export type DataItem = {
/** Unique identifier for the data item */
id: number;
/** Primary text or name of the data item */
title: string;
/** Detailed text description of the data item */
description: string;
/** Additional optional field for extra information */
optionalField?: string;
/** Hex color code (e.g., '#FF0000') for visual representation */
colorHex?: string;
/** Additional details or metadata about the item */
details?: string;
};
/**
* Represents a group of related data items with a common type
*
* @interface ListGroup
*/
export type ListGroup = {
/** The type or category of the group */
type: string;
/** Array of DataItem objects belonging to this group */
items: DataItem[];
};

View File

@@ -0,0 +1,153 @@
import * as React from 'react';
import { format, parse, isValid } from 'date-fns';
import { Calendar as CalendarIcon } from 'lucide-react';
import { cn } from '../../lib/utils';
import { Calendar } from '../Calendar';
import * as Popover from '../Popover';
export type DatePickerWithRangeProps = {
id: string;
/** YYYYMMDD (19921022) */
startDate: string;
/** YYYYMMDD (19921022) */
endDate: string;
/** Callback that received { startDate: string(YYYYMMDD), endDate: string(YYYYMMDD)} */
onChange: (value: { startDate: string; endDate: string }) => void;
};
export function DatePickerWithRange({
className,
id,
startDate,
endDate,
onChange,
...props
}: React.HTMLAttributes<HTMLDivElement> & DatePickerWithRangeProps) {
const [start, setStart] = React.useState<string>(
startDate ? format(parse(startDate, 'yyyyMMdd', new Date()), 'yyyy-MM-dd') : ''
);
const [end, setEnd] = React.useState<string>(
endDate ? format(parse(endDate, 'yyyyMMdd', new Date()), 'yyyy-MM-dd') : ''
);
const [openEnd, setOpenEnd] = React.useState(false);
const handleStartSelect = (selectedDate: Date | undefined) => {
if (selectedDate) {
const formattedDate = format(selectedDate, 'yyyy-MM-dd');
setStart(formattedDate);
setOpenEnd(true);
onChange({
startDate: format(selectedDate, 'yyyyMMdd'),
endDate: end.replace(/-/g, ''),
});
}
};
const handleEndSelect = (selectedDate: Date | undefined) => {
if (selectedDate) {
const formattedDate = format(selectedDate, 'yyyy-MM-dd');
setEnd(formattedDate);
setOpenEnd(false);
onChange({
startDate: start.replace(/-/g, ''),
endDate: format(selectedDate, 'yyyyMMdd'),
});
}
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>, type: 'start' | 'end') => {
const value = e.target.value;
const date = parse(value, 'yyyy-MM-dd', new Date());
if (type === 'start') {
setStart(value);
if (isValid(date)) {
handleStartSelect(date);
}
} else {
setEnd(value);
if (isValid(date)) {
handleEndSelect(date);
}
}
};
React.useEffect(() => {
setStart(startDate ? format(parse(startDate, 'yyyyMMdd', new Date()), 'yyyy-MM-dd') : '');
setEnd(endDate ? format(parse(endDate, 'yyyyMMdd', new Date()), 'yyyy-MM-dd') : '');
}, [startDate, endDate]);
return (
<div className={cn('flex gap-2', className)}>
<Popover.Popover>
<Popover.PopoverTrigger asChild>
<div className="relative w-full">
<CalendarIcon className="absolute right-2 top-1/2 h-4 w-4 -translate-y-1/2 transform text-white" />
<input
id={`${id}-start`}
type="text"
placeholder="Start date"
autoComplete="off"
value={start}
onChange={e => handleInputChange(e, 'start')}
className={cn(
'border-inputfield-main focus:border-inputfield-focus h-[32px] w-full justify-start rounded border bg-black py-[6.5px] pl-[6.5px] pr-[6.5px] text-left text-base font-normal hover:bg-black hover:text-white',
!start && 'text-muted-foreground'
)}
data-cy="input-date-range-start"
/>
</div>
</Popover.PopoverTrigger>
<Popover.PopoverContent
className="w-auto p-0"
align="start"
>
<Calendar
initialFocus
mode="single"
defaultMonth={start ? parse(start, 'yyyy-MM-dd', new Date()) : new Date()}
selected={start ? parse(start, 'yyyy-MM-dd', new Date()) : undefined}
onSelect={handleStartSelect}
numberOfMonths={1}
/>
</Popover.PopoverContent>
</Popover.Popover>
<Popover.Popover
open={openEnd}
onOpenChange={setOpenEnd}
>
<Popover.PopoverTrigger asChild>
<div className="relative w-full">
<CalendarIcon className="absolute right-2 top-1/2 h-4 w-4 -translate-y-1/2 transform text-white" />
<input
id={`${id}-end`}
type="text"
placeholder="End date"
autoComplete="off"
value={end}
onChange={e => handleInputChange(e, 'end')}
className={cn(
'border-inputfield-main focus:border-inputfield-focus h-full w-full justify-start rounded border bg-black py-[6.5px] pl-[6.5px] pr-[6.5px] text-left text-base font-normal hover:bg-black hover:text-white',
!end && 'text-muted-foreground'
)}
data-cy="input-date-range-end"
/>
</div>
</Popover.PopoverTrigger>
<Popover.PopoverContent
className="w-auto p-0"
align="start"
>
<Calendar
initialFocus
mode="single"
defaultMonth={start ? parse(start, 'yyyy-MM-dd', new Date()) : new Date()}
selected={end ? parse(end, 'yyyy-MM-dd', new Date()) : undefined}
onSelect={handleEndSelect}
numberOfMonths={1}
/>
</Popover.PopoverContent>
</Popover.Popover>
</div>
);
}

View File

@@ -0,0 +1,3 @@
import { DatePickerWithRange } from './DateRange';
export { DatePickerWithRange };

View File

@@ -0,0 +1,105 @@
import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { Cross2Icon } from '@radix-ui/react-icons';
import { cn } from '../../lib/utils';
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
className
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg',
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:pointer-events-none">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)}
{...props}
/>
);
DialogHeader.displayName = 'DialogHeader';
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
{...props}
/>
);
DialogFooter.displayName = 'DialogFooter';
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn('text-xl font-semibold leading-none tracking-tight', className)}
{...props}
/>
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn('text-muted-foreground text-base', className)}
{...props}
/>
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
};

View File

@@ -0,0 +1,25 @@
import {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
} from './Dialog';
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
};

View File

@@ -0,0 +1,68 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Icons } from '../Icons';
import { useTranslation } from 'react-i18next';
import { Tooltip, TooltipTrigger, TooltipContent } from '../Tooltip';
/**
* Displays a tooltip with a list of messages of a displaySet
* @param param0
* @returns
*/
const DisplaySetMessageListTooltip = ({ messages, id }): React.ReactNode => {
const { t } = useTranslation('Messages');
if (messages?.size()) {
return (
<>
<Tooltip>
<TooltipTrigger id={id}>
<Icons.StatusWarning
className="h-[20px] w-[20px]"
aria-hidden="true"
role="presentation"
/>
</TooltipTrigger>
<TooltipContent side="right">
<div className="max-w-68 text-left text-lg text-white">
<div
className="break-normal text-lg font-semibold text-blue-300"
style={{
marginLeft: '4px',
marginTop: '4px',
}}
>
{t('Display Set Messages')}
</div>
<ol
style={{
marginLeft: '4px',
marginRight: '4px',
}}
>
{messages.messages.map((message, index) => (
<li
style={{
marginTop: '6px',
marginBottom: '6px',
}}
key={index}
>
{index + 1}. {t(message.id)}
</li>
))}
</ol>
</div>
</TooltipContent>
</Tooltip>
</>
);
}
return <></>;
};
DisplaySetMessageListTooltip.propTypes = {
messages: PropTypes.object,
id: PropTypes.string,
};
export { DisplaySetMessageListTooltip };

View File

@@ -0,0 +1,3 @@
import { DisplaySetMessageListTooltip } from './DisplaySetMessageListTooltip';
export { DisplaySetMessageListTooltip };

View File

@@ -0,0 +1,136 @@
import React from 'react';
import * as SliderPrimitive from '@radix-ui/react-slider';
import { cn } from '../../lib/utils';
import { Input } from '../Input';
interface DoubleSliderProps {
className?: string;
min: number;
max: number;
step?: number;
defaultValue?: [number, number];
onValueChange?: (value: [number, number]) => void;
showNumberInputs?: boolean;
}
const DoubleSlider = React.forwardRef<HTMLDivElement, DoubleSliderProps>(
(
{
className,
min,
max,
onValueChange,
step = 1,
defaultValue = [min, max],
showNumberInputs = false,
},
ref
) => {
const [value, setValue] = React.useState<[number, number]>(defaultValue);
const prevDefaultValueRef = React.useRef<[number, number] | null>(null);
const isInteger = step % 1 === 0;
React.useEffect(() => {
// Only update if defaultValue has actually changed
if (
!prevDefaultValueRef.current ||
prevDefaultValueRef.current[0] !== defaultValue[0] ||
prevDefaultValueRef.current[1] !== defaultValue[1]
) {
setValue(defaultValue);
prevDefaultValueRef.current = defaultValue;
}
}, [defaultValue]);
const roundToStep = (num: number): number => {
const inverse = 1 / step;
return Math.round(num * inverse) / inverse;
};
const handleSliderChange = React.useCallback(
(newValue: number[]) => {
const clampedValue: [number, number] = [
roundToStep(Math.max(min, Math.min(newValue[0], max))),
roundToStep(Math.min(max, Math.max(newValue[1], min))),
];
setValue(clampedValue);
onValueChange?.(clampedValue);
},
[min, max, onValueChange, step]
);
const handleInputChange = React.useCallback(
(index: 0 | 1, inputValue: string) => {
const newValue = parseFloat(inputValue);
if (!isNaN(newValue)) {
const clampedValue: [number, number] = [...value];
clampedValue[index] = roundToStep(Math.min(Math.max(newValue, min), max));
if (index === 0 && clampedValue[0] > clampedValue[1]) {
clampedValue[1] = clampedValue[0];
} else if (index === 1 && clampedValue[1] < clampedValue[0]) {
clampedValue[0] = clampedValue[1];
}
setValue(clampedValue);
onValueChange?.(clampedValue);
}
},
[value, min, max, onValueChange, step]
);
const formatValue = (val: number) => {
return isInteger ? Math.round(val) : val;
};
return (
<div
ref={ref}
className={cn('flex w-full items-center space-x-2', className)}
>
{showNumberInputs && (
<Input
type="number"
value={formatValue(value[0])}
onChange={e => handleInputChange(0, e.target.value)}
onBlur={() => handleInputChange(0, value[0].toString())}
className="w-14"
min={min}
max={max}
step={step}
/>
)}
<SliderPrimitive.Root
className="relative flex h-4 w-full touch-none select-none items-center"
min={min}
max={max}
step={step}
value={value}
onValueChange={handleSliderChange}
>
<SliderPrimitive.Track className="bg-primary/30 relative h-1 w-full grow overflow-hidden rounded-full">
<SliderPrimitive.Range className="bg-primary absolute h-full" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="border-background bg-primary focus-visible:ring-ring block h-4 w-4 rounded-full border-2 shadow transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50" />
<SliderPrimitive.Thumb className="border-background bg-primary focus-visible:ring-ring block h-4 w-4 rounded-full border-2 shadow transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50" />
</SliderPrimitive.Root>
{showNumberInputs && (
<Input
type="number"
value={formatValue(value[1])}
onChange={e => handleInputChange(1, e.target.value)}
onBlur={() => handleInputChange(1, value[1].toString())}
className="w-14"
min={min}
max={max}
step={step}
/>
)}
</div>
);
}
);
DoubleSlider.displayName = 'DoubleSlider';
export { DoubleSlider };

View File

@@ -0,0 +1,3 @@
import { DoubleSlider } from './DoubleSlider';
export { DoubleSlider };

View File

@@ -0,0 +1,191 @@
import * as React from 'react';
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { CheckIcon, ChevronRightIcon, DotFilledIcon } from '@radix-ui/react-icons';
import { cn } from '../../lib/utils';
const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
disabled?: boolean;
}
>(({ className, inset, children, disabled, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
'focus:bg-accent data-[state=open]:bg-accent flex cursor-default select-none items-center rounded px-2 py-1 text-base outline-none',
inset && 'pl-8',
disabled && 'pointer-events-none opacity-50',
className
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
));
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 border-input z-50 min-w-[8rem] overflow-hidden rounded border p-1 shadow-lg',
className
)}
{...props}
/>
));
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground border-input z-50 min-w-[8rem] overflow-hidden rounded border p-1 shadow-md',
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded px-1 py-1 text-base outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && 'pl-8',
className
)}
{...props}
/>
));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded py-1 pl-8 pr-2 text-base outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
));
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default select-none items-center rounded py-1 pl-8 pr-2 text-base outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<DotFilledIcon className="h-4 w-4 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn('text-muted-foreground px-2 py-1 text-sm', inset && 'pl-8', className)}
{...props}
/>
));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn('bg-muted my-1 mx-2 h-px', className)}
{...props}
/>
));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn('ml-auto text-sm tracking-widest opacity-60', className)}
{...props}
/>
);
};
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
};

View File

@@ -0,0 +1,35 @@
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
} from './DropdownMenu';
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
};

View File

@@ -0,0 +1,198 @@
import React, { useState, useEffect } from 'react';
import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';
import { useTranslation } from 'react-i18next';
import { toast } from 'sonner';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from '../Dialog/Dialog';
import { ScrollArea } from '../ScrollArea/ScrollArea';
import { Icons } from '../Icons';
const isProduction = process.env.NODE_ENV === 'production';
interface ErrorBoundaryError extends Error {
message: string;
stack?: string;
}
interface DefaultFallbackProps {
error: ErrorBoundaryError;
context: string;
resetErrorBoundary: () => void;
}
interface ErrorBoundaryProps {
context?: string;
onReset?: () => void;
onError?: (error: ErrorBoundaryError, componentStack: string, context: string) => void;
fallbackComponent?: React.ComponentType<DefaultFallbackProps>;
children: React.ReactNode;
fallbackRoute?: string | null;
isPage?: boolean;
}
const DefaultFallback = ({
error,
context,
resetErrorBoundary = () => {},
}: DefaultFallbackProps) => {
const { t } = useTranslation('ErrorBoundary');
const [showDetails, setShowDetails] = useState(false);
const title = `${t('Something went wrong')}${!isProduction && ` ${t('in')} ${context}`}.`;
const subtitle = t('Sorry, something went wrong there. Try again.');
const copyErrorDetails = () => {
const errorDetails = `
Context: ${context}
Error Message: ${error.message}
Stack: ${error.stack}
`;
navigator.clipboard.writeText(errorDetails);
toast.success(t('Copied to clipboard'));
};
useEffect(() => {
toast.error(title, {
description: subtitle,
action: {
label: t('Show Details'),
onClick: () => setShowDetails(true),
},
duration: 0,
});
}, [error]);
if (isProduction) {
return null;
}
return (
<>
<Dialog
open={showDetails}
onOpenChange={setShowDetails}
>
<DialogContent className="border-input h-[50vh] w-[90vw] border-2 sm:max-w-[900px]">
<DialogHeader>
<DialogTitle className="text-muted-foreground flex justify-between text-xl">
<div className="flex items-center">{title}</div>
<button
onClick={() => {
copyErrorDetails();
setShowDetails(false);
}}
className="text-aqua-pale hover:text-aqua-pale/80 flex items-center gap-2 rounded bg-gray-800 px-4 py-2 font-light"
>
<Icons.Code className="h-4 w-4" />
{t('Copy Details')}
</button>
</DialogTitle>
<DialogDescription className="text-lg">{subtitle}</DialogDescription>
</DialogHeader>
<ScrollArea className="h-[100%]">
<div className="space-y-4 pr-4 font-mono text-base">
<div className="space-y-4">
<p className="text-aqua-pale break-words text-lg">
{t('Context')}: {context}
</p>
<p className="text-aqua-pale break-words text-lg">
{t('Error Message')}: {error.message}
</p>
<pre className="text-aqua-pale whitespace-pre-wrap break-words rounded bg-gray-900 p-4">
Stack: {error.stack}
</pre>
</div>
</div>
</ScrollArea>
</DialogContent>
</Dialog>
</>
);
};
const ErrorBoundary = ({
context = 'OHIF',
onReset = () => {},
onError = () => {},
fallbackComponent: FallbackComponent = DefaultFallback,
children,
fallbackRoute = null,
isPage,
}: ErrorBoundaryProps) => {
const [error, setError] = useState<ErrorBoundaryError | null>(null);
const onResetHandler = () => {
setError(null);
onReset();
};
// Add error event listener to window
useEffect(() => {
let errorTimeout: NodeJS.Timeout;
const handleError = (event: ErrorEvent) => {
event.preventDefault();
clearTimeout(errorTimeout);
errorTimeout = setTimeout(() => {
setError(event.error);
onErrorHandler(event.error, null);
}, 100);
};
const handleRejection = (event: PromiseRejectionEvent) => {
event.preventDefault();
clearTimeout(errorTimeout);
errorTimeout = setTimeout(() => {
setError(event.reason);
onErrorHandler(event.reason, null);
}, 100);
};
window.addEventListener('error', handleError);
window.addEventListener('unhandledrejection', handleRejection);
return () => {
clearTimeout(errorTimeout);
window.removeEventListener('error', handleError);
window.removeEventListener('unhandledrejection', handleRejection);
};
}, []);
const onErrorHandler = (error: ErrorBoundaryError, componentStack: string) => {
console.debug(`${context} Error Boundary`, error, componentStack, context);
onError(error, componentStack, context);
};
return (
<ReactErrorBoundary
fallbackRender={props => (
<FallbackComponent
error={props.error}
context={context}
resetErrorBoundary={props.resetErrorBoundary}
/>
)}
onReset={onResetHandler}
onError={onErrorHandler}
>
<>
{children}
{error && (
<FallbackComponent
error={error}
context={context}
resetErrorBoundary={() => setError(null)}
/>
)}
</>
</ReactErrorBoundary>
);
};
export { ErrorBoundary };

View File

@@ -0,0 +1,3 @@
import { ErrorBoundary } from './ErrorBoundary';
export { ErrorBoundary };

View File

@@ -0,0 +1,121 @@
import React, { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import classNames from 'classnames';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
Icons,
Button,
} from '../';
import NavBar from '../NavBar';
// Todo: we should move this component to composition and remove props base
interface HeaderProps {
children?: ReactNode;
menuOptions: Array<{
title: string;
icon?: string;
onClick: () => void;
}>;
isReturnEnabled?: boolean;
onClickReturnButton?: () => void;
isSticky?: boolean;
WhiteLabeling?: {
createLogoComponentFn?: (React: any, props: any) => ReactNode;
};
PatientInfo?: ReactNode;
Secondary?: ReactNode;
}
function Header({
children,
menuOptions,
isReturnEnabled = true,
onClickReturnButton,
isSticky = false,
WhiteLabeling,
PatientInfo,
Secondary,
...props
}: HeaderProps): ReactNode {
const { t } = useTranslation('Header');
const onClickReturn = () => {
if (isReturnEnabled && onClickReturnButton) {
onClickReturnButton();
}
};
return (
<NavBar
isSticky={isSticky}
{...props}
>
<div className="relative h-[48px] items-center">
<div className="absolute left-0 top-1/2 flex -translate-y-1/2 items-center">
<div
className={classNames(
'mr-3 inline-flex items-center',
isReturnEnabled && 'cursor-pointer'
)}
onClick={onClickReturn}
data-cy="return-to-work-list"
>
{isReturnEnabled && <Icons.ArrowLeft className="text-primary-active w-8" />}
<div className="ml-1">
{WhiteLabeling?.createLogoComponentFn?.(React, props) || <Icons.OHIFLogo />}
</div>
</div>
</div>
<div className="absolute top-1/2 left-[250px] h-8 -translate-y-1/2">{Secondary}</div>
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transform">
<div className="flex items-center justify-center space-x-2">{children}</div>
</div>
<div className="absolute right-0 top-1/2 flex -translate-y-1/2 select-none items-center">
{PatientInfo}
<div className="border-primary-dark mx-1.5 h-[25px] border-r"></div>
<div className="flex-shrink-0">
{/* <DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="text-primary-active hover:bg-primary-dark mt-2 h-full w-full"
>
<Icons.GearSettings />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{menuOptions.map((option, index) => {
const IconComponent = option.icon
? Icons[option.icon as keyof typeof Icons]
: null;
return (
<DropdownMenuItem
key={index}
onSelect={option.onClick}
className="flex items-center gap-2 py-2"
>
{IconComponent && (
<span className="flex h-4 w-4 items-center justify-center">
<Icons.ByName name={IconComponent.name} />
</span>
)}
<span className="flex-1">{option.title}</span>
</DropdownMenuItem>
);
})}
</DropdownMenuContent>
</DropdownMenu> */}
</div>
</div>
</div>
</NavBar>
);
}
export default Header;

View File

@@ -0,0 +1,2 @@
import Header from './Header';
export { Header };

View File

@@ -0,0 +1,741 @@
import React from 'react';
import Actions from './Sources/Actions';
import Add from './Sources/Add';
import Cancel from './Sources/Cancel';
import ChevronClosed from './Sources/ChevronClosed';
import ChevronOpen from './Sources/ChevronOpen';
import Code from './Sources/Code';
import ColorChange from './Sources/ColorChange';
import Controls from './Sources/Controls';
import Copy from './Sources/Copy';
import Delete from './Sources/Delete';
import DicomTagBrowser from './Sources/DicomTagBrowser';
import DisplayFillAndOutline from './Sources/DisplayFillAndOutline';
import DisplayFillOnly from './Sources/DisplayFillOnly';
import DisplayOutlineOnly from './Sources/DisplayOutlineOnly';
import Download from './Sources/Download';
import Export from './Sources/Export';
import EyeHidden from './Sources/EyeHidden';
import EyeVisible from './Sources/EyeVisible';
import FeedbackComplete from './Sources/FeedbackComplete';
import GearSettings from './Sources/GearSettings';
import Hide from './Sources/Hide';
import IconMPR from './Sources/IconMPR';
import Info from './Sources/Info';
import InfoLink from './Sources/InfoLink';
import InfoSeries from './Sources/InfoSeries';
import ListView from './Sources/ListView';
import LoadingSpinner from './Sources/LoadingSpinner';
import Lock from './Sources/Lock';
import Minus from './Sources/Minus';
import MissingIcon from './Sources/MissingIcon';
import More from './Sources/More';
import MultiplePatients from './Sources/MultiplePatients';
import NavigationPanelReveal from './Sources/NavigationPanelReveal';
import OHIFLogo from './Sources/OHIFLogo';
import Patient from './Sources/Patient';
import Pin from './Sources/Pin';
import PinFill from './Sources/PinFill';
import Plus from './Sources/Plus';
import PowerOff from './Sources/PowerOff';
import Refresh from './Sources/Refresh';
import Rename from './Sources/Rename';
import Series from './Sources/Series';
import Settings from './Sources/Settings';
import Show from './Sources/Show';
import SidePanelCloseLeft from './Sources/SidePanelCloseLeft';
import SidePanelCloseRight from './Sources/SidePanelCloseRight';
import SortingAscending from './Sources/SortingAscending';
import SortingDescending from './Sources/SortingDescending';
import StatusError from './Sources/StatusError';
import StatusSuccess from './Sources/StatusSuccess';
import StatusTracking from './Sources/StatusTracking';
import StatusUntracked from './Sources/StatusUntracked';
import StatusWarning from './Sources/StatusWarning';
import Tab4D from './Sources/Tab4D';
import TabLinear from './Sources/TabLinear';
import TabPatientInfo from './Sources/TabPatientInfo';
import TabRoiThreshold from './Sources/TabRoiThreshold';
import TabSegmentation from './Sources/TabSegmentation';
import TabStudies from './Sources/TabStudies';
import ThumbnailView from './Sources/ThumbnailView';
import Trash from './Sources/Trash';
import ViewportViews from './Sources/ViewportViews';
import Sorting from './Sources/Sorting';
import Upload from './Sources/Upload';
import LaunchArrow from './Sources/LaunchArrow';
import LaunchInfo from './Sources/LaunchInfo';
import GroupLayers from './Sources/GroupLayers';
import Database from './Sources/Database';
import InvestigationalUse from './Sources/InvestigationalUse';
import IconTransferring from './Sources/IconTransferring';
import Alert from './Sources/Alert';
import AlertOutline from './Sources/AlertOutline';
import Clipboard from './Sources/Clipboard';
import {
Tool3DRotate,
ToolAngle,
ToolAnnotate,
ToolBidirectional,
ToolCalibrate,
ToolCapture,
ToolCine,
ToolCircle,
ToolCobbAngle,
ToolCreateThreshold,
ToolCrosshair,
ToolDicomTagBrowser,
ToolFlipHorizontal,
ToolFreehandPolygon,
ToolFreehandRoi,
ToolFreehand,
ToolFusionColor,
ToolInvert,
ToolLayoutDefault,
ToolLength,
ToolMagneticRoi,
ToolMagnify,
ToolMeasureEllipse,
ToolMoreMenu,
ToolMove,
ToolPolygon,
ToolQuickMagnify,
ToolRectangle,
ToolReferenceLines,
ToolReset,
ToolRotateRight,
ToolSegBrush,
ToolSegEraser,
ToolSegShape,
ToolSegThreshold,
ToolSplineRoi,
ToolStackImageSync,
ToolStackScroll,
ToolToggleDicomOverlay,
ToolUltrasoundBidirectional,
ToolWindowLevel,
ToolWindowRegion,
ToolZoom,
ToolLayout,
ToolProbe,
ToolEraser,
ToolBrush,
ToolThreshold,
ToolShape,
ToolLabelmapAssist,
ToolPETSegment,
ToolInterpolation,
ToolBidirectionalSegment,
ToolSegmentAnything,
ToolContract,
ToolExpand,
} from './Sources/Tools';
import ActionNewDialog from './Sources/ActionNewDialog';
import NotificationInfo from './Sources/NotificationInfo';
import StatusLocked from './Sources/StatusLocked';
import ContentPrev from './Sources/ContentPrev';
import ContentNext from './Sources/ContentNext';
import CheckBoxChecked from './Sources/CheckBoxChecked';
import CheckBoxUnchecked from './Sources/CheckBoxUnChecked';
import Close from './Sources/Close';
import Pause from './Sources/Pause';
import Play from './Sources/Play';
import ViewportWindowLevel from './Sources/ViewportWindowLevel';
import Search from './Sources/Search';
import Clear from './Sources/Clear';
import {
LayoutAdvanced3DOnly,
LayoutAdvanced3DPrimary,
LayoutAdvancedAxialPrimary,
LayoutAdvancedMPR,
LayoutCommon2x2,
LayoutCommon1x1,
LayoutCommon1x2,
LayoutCommon2x3,
LayoutAdvanced3DFourUp,
LayoutAdvanced3DMain,
} from './Sources/Layout';
import Link from './Sources/Link';
import IconColorLUT from './Sources/IconColorLUT';
import CTAAA from '../../../assets/images/CT-AAA.png';
import CTAAA2 from '../../../assets/images/CT-AAA2.png';
import CTAir from '../../../assets/images/CT-Air.png';
import CTBone from '../../../assets/images/CT-Bone.png';
import CTBones from '../../../assets/images/CT-Bones.png';
import CTCardiac from '../../../assets/images/CT-Cardiac.png';
import CTCardiac2 from '../../../assets/images/CT-Cardiac2.png';
import CTCardiac3 from '../../../assets/images/CT-Cardiac3.png';
import CTChestContrastEnhanced from '../../../assets/images/CT-Chest-Contrast-Enhanced.png';
import CTChestVessels from '../../../assets/images/CT-Chest-Vessels.png';
import CTCoronaryArteries from '../../../assets/images/CT-Coronary-Arteries.png';
import CTCoronaryArteries2 from '../../../assets/images/CT-Coronary-Arteries-2.png';
import CTCoronaryArteries3 from '../../../assets/images/CT-Coronary-Arteries-3.png';
import CTCroppedVolumeBone from '../../../assets/images/CT-Cropped-Volume-Bone.png';
import CTFat from '../../../assets/images/CT-Fat.png';
import CTLiverVasculature from '../../../assets/images/CT-Liver-Vasculature.png';
import CTLung from '../../../assets/images/CT-Lung.png';
import CTMIP from '../../../assets/images/CT-MIP.png';
import CTMuscle from '../../../assets/images/CT-Muscle.png';
import CTPulmonaryArteries from '../../../assets/images/CT-Pulmonary-Arteries.png';
import CTSoftTissue from '../../../assets/images/CT-Soft-Tissue.png';
import DTIFABrain from '../../../assets/images/DTI-FA-Brain.png';
import MRAngio from '../../../assets/images/MR-Angio.png';
import MRDefault from '../../../assets/images/MR-Default.png';
import MRMIP from '../../../assets/images/MR-MIP.png';
import MRT2Brain from '../../../assets/images/MR-T2-Brain.png';
import VolumeRendering from '../../../assets/images/VolumeRendering.png';
import ExternalLink from './Sources/ExternalLink';
import OHIFLogoColorDarkBackground from './Sources/OHIFLogoColorDarkBackground';
import Magnifier from './Sources/Magnifier';
import LoadingOHIFMark from './Sources/LoadingOHIFMark';
import ArrowLeftBold from './Sources/ArrowLeftBold';
import Pencil from './Sources/Pencil';
import NotificationWarning from './Sources/NotificationWarning';
import ArrowRight from './Sources/ArrowRight';
import ChevronLeft from './Sources/ChevronLeft';
//
//
type IconProps = React.HTMLAttributes<SVGElement>;
type ImageIconProps = React.ImgHTMLAttributes<HTMLImageElement>;
const ImageWrapper = ({ src, ...props }: { src: string } & ImageIconProps) => {
return (
<img
src={src}
{...props}
alt=""
/>
);
};
export const Icons = {
'CT-AAA': (props: ImageIconProps) => (
<ImageWrapper
src={CTAAA}
{...props}
/>
),
'CT-AAA2': (props: ImageIconProps) => (
<ImageWrapper
src={CTAAA2}
{...props}
/>
),
'CT-Air': (props: ImageIconProps) => (
<ImageWrapper
src={CTAir}
{...props}
/>
),
'CT-Bone': (props: ImageIconProps) => (
<ImageWrapper
src={CTBone}
{...props}
/>
),
'CT-Bones': (props: ImageIconProps) => (
<ImageWrapper
src={CTBones}
{...props}
/>
),
'CT-Cardiac': (props: ImageIconProps) => (
<ImageWrapper
src={CTCardiac}
{...props}
/>
),
'CT-Cardiac2': (props: ImageIconProps) => (
<ImageWrapper
src={CTCardiac2}
{...props}
/>
),
'CT-Cardiac3': (props: ImageIconProps) => (
<ImageWrapper
src={CTCardiac3}
{...props}
/>
),
'CT-Chest-Contrast-Enhanced': (props: ImageIconProps) => (
<ImageWrapper
src={CTChestContrastEnhanced}
{...props}
/>
),
'CT-Chest-Vessels': (props: ImageIconProps) => (
<ImageWrapper
src={CTChestVessels}
{...props}
/>
),
'CT-Coronary-Arteries': (props: ImageIconProps) => (
<ImageWrapper
src={CTCoronaryArteries}
{...props}
/>
),
'CT-Coronary-Arteries-2': (props: ImageIconProps) => (
<ImageWrapper
src={CTCoronaryArteries2}
{...props}
/>
),
'CT-Coronary-Arteries-3': (props: ImageIconProps) => (
<ImageWrapper
src={CTCoronaryArteries3}
{...props}
/>
),
'CT-Cropped-Volume-Bone': (props: ImageIconProps) => (
<ImageWrapper
src={CTCroppedVolumeBone}
{...props}
/>
),
'CT-Fat': (props: ImageIconProps) => (
<ImageWrapper
src={CTFat}
{...props}
/>
),
'CT-Liver-Vasculature': (props: ImageIconProps) => (
<ImageWrapper
src={CTLiverVasculature}
{...props}
/>
),
'CT-Lung': (props: ImageIconProps) => (
<ImageWrapper
src={CTLung}
{...props}
/>
),
'CT-MIP': (props: ImageIconProps) => (
<ImageWrapper
src={CTMIP}
{...props}
/>
),
'CT-Muscle': (props: ImageIconProps) => (
<ImageWrapper
src={CTMuscle}
{...props}
/>
),
'CT-Pulmonary-Arteries': (props: ImageIconProps) => (
<ImageWrapper
src={CTPulmonaryArteries}
{...props}
/>
),
'CT-Soft-Tissue': (props: ImageIconProps) => (
<ImageWrapper
src={CTSoftTissue}
{...props}
/>
),
'DTI-FA-Brain': (props: ImageIconProps) => (
<ImageWrapper
src={DTIFABrain}
{...props}
/>
),
'MR-Angio': (props: ImageIconProps) => (
<ImageWrapper
src={MRAngio}
{...props}
/>
),
'MR-Default': (props: ImageIconProps) => (
<ImageWrapper
src={MRDefault}
{...props}
/>
),
'MR-MIP': (props: ImageIconProps) => (
<ImageWrapper
src={MRMIP}
{...props}
/>
),
'MR-T2-Brain': (props: ImageIconProps) => (
<ImageWrapper
src={MRT2Brain}
{...props}
/>
),
VolumeRendering: (props: ImageIconProps) => (
<ImageWrapper
src={VolumeRendering}
{...props}
/>
),
// Icons
Clipboard,
ActionNewDialog,
GroupLayers,
Database,
InvestigationalUse,
Tool3DRotate,
ToolAngle,
ToolAnnotate,
ToolBidirectional,
ToolCalibrate,
ToolCapture,
ToolCine,
ToolCircle,
ToolCobbAngle,
ToolCreateThreshold,
ToolCrosshair,
ToolDicomTagBrowser,
ToolFlipHorizontal,
ToolFreehandPolygon,
ToolFreehandRoi,
ToolFreehand,
ToolFusionColor,
ToolInvert,
ToolLayoutDefault,
ToolLength,
ToolMagneticRoi,
ToolMagnify,
ToolMeasureEllipse,
ToolMoreMenu,
ToolMove,
ToolPolygon,
ToolQuickMagnify,
ToolRectangle,
ToolReferenceLines,
ToolReset,
ToolRotateRight,
ToolSegBrush,
ToolSegEraser,
ToolSegShape,
ToolSegThreshold,
ToolSplineRoi,
ToolStackImageSync,
ToolStackScroll,
ToolToggleDicomOverlay,
ToolUltrasoundBidirectional,
ToolWindowLevel,
ToolWindowRegion,
ToolZoom,
LaunchArrow,
LaunchInfo,
Upload,
Actions,
Add,
Cancel,
Code,
ColorChange,
Controls,
Copy,
Delete,
DicomTagBrowser,
DisplayFillAndOutline,
DisplayFillOnly,
DisplayOutlineOnly,
FillAndOutline: DisplayFillAndOutline,
FillOnly: DisplayFillOnly,
OutlineOnly: DisplayOutlineOnly,
Download,
Export,
EyeHidden,
EyeVisible,
FeedbackComplete,
GearSettings,
Hide,
IconMPR,
Info,
InfoLink,
InfoSeries,
ListView,
LoadingSpinner,
Lock,
Minus,
MissingIcon,
More,
MultiplePatients,
NavigationPanelReveal,
OHIFLogo,
Patient,
Pin,
PinFill,
Plus,
PowerOff,
Refresh,
Rename,
Series,
Settings,
Show,
SidePanelCloseLeft,
SidePanelCloseRight,
SortingAscending,
SortingDescending,
Sorting,
StatusError,
StatusSuccess,
StatusTracking,
StatusWarning,
StatusUntracked,
Tab4D,
TabLinear,
TabPatientInfo,
TabRoiThreshold,
TabSegmentation,
TabStudies,
ThumbnailView,
Trash,
ViewportViews,
ChevronClosed,
ChevronOpen,
ChevronRight: (props: IconProps) => {
return (
<ChevronLeft
{...props}
className={`${props.className || ''} rotate-180`.trim()}
/>
);
},
ChevronLeft,
ChevronDown: (props: IconProps) => {
return (
<ChevronLeft
{...props}
className={`${props.className || ''} -rotate-90`.trim()}
/>
);
},
Alert,
AlertOutline,
NotificationInfo,
StatusLocked,
ContentPrev,
ContentNext,
CheckBoxChecked,
CheckBoxUnchecked,
Close,
Pause,
Play,
Link,
LoadingOHIFMark,
ArrowLeft: ChevronClosed,
ArrowRight,
ArrowLeftBold,
ArrowRightBold: (props: IconProps) => {
return (
<ArrowLeftBold
{...props}
className={`${props.className || ''} rotate-180`.trim()}
/>
);
},
ArrowDown: (props: IconProps) => {
return (
<ChevronOpen
{...props}
className={`${props.className || ''} -rotate-90`.trim()}
/>
);
},
ViewportWindowLevel,
Search,
Clear,
LayoutCommon2x3,
LayoutCommon2x2,
LayoutCommon1x1,
LayoutCommon1x2,
LayoutAdvanced3DFourUp,
LayoutAdvanced3DMain,
LayoutAdvanced3DOnly,
LayoutAdvanced3DPrimary,
LayoutAdvancedAxialPrimary,
LayoutAdvancedMPR,
ToolLayout,
IconColorLUT,
ToolEraser,
ToolBrush,
ToolThreshold,
ToolShape,
ToolLabelmapAssist,
ToolSegmentAnything,
ToolPETSegment,
ToolInterpolation,
ToolBidirectionalSegment,
ToolContract,
ToolExpand,
ExternalLink,
OHIFLogoColorDarkBackground,
Magnifier,
Pencil,
//
//
//
//
//
//
//
//
//
// Aliases
'prev-arrow': (props: IconProps) => Icons.ArrowLeftBold(props),
'next-arrow': (props: IconProps) => Icons.ArrowRightBold(props),
'loading-ohif-mark': (props: IconProps) => LoadingOHIFMark(props),
magnifier: (props: IconProps) => Magnifier(props),
'status-alert-warning': (props: IconProps) => StatusWarning(props),
'logo-dark-background': (props: IconProps) => OHIFLogoColorDarkBackground(props),
'external-link': (props: IconProps) => ExternalLink(props),
'checkbox-checked': (props: IconProps) => CheckBoxChecked(props),
'checkbox-unchecked': (props: IconProps) => CheckBoxUnchecked(props),
'checkbox-default': (props: IconProps) => CheckBoxUnchecked(props),
'checkbox-active': (props: IconProps) => CheckBoxChecked(props),
'icon-tool-eraser': (props: IconProps) => ToolEraser(props),
'icon-tool-brush': (props: IconProps) => ToolBrush(props),
'icon-tool-labelmap-assist': (props: IconProps) => ToolLabelmapAssist(props),
'icon-tool-segment-anything': (props: IconProps) => ToolSegmentAnything(props),
'icon-tool-threshold': (props: IconProps) => ToolThreshold(props),
'icon-tool-pet-segment': (props: IconProps) => ToolPETSegment(props),
'icon-tool-interpolation': (props: IconProps) => ToolInterpolation(props),
'icon-tool-bidirectional-segment': (props: IconProps) => ToolBidirectionalSegment(props),
'icon-tool-expand': (props: IconProps) => ToolExpand(props),
'icon-tool-contract': (props: IconProps) => ToolContract(props),
'icon-tool-shape': (props: IconProps) => ToolShape(props),
link: (props: IconProps) => Link(props),
'icon-color-lut': (props: IconProps) => IconColorLUT(props),
'icon-link': (props: IconProps) => Link(props),
'icon-clear': (props: IconProps) => Clear(props),
'icon-search': (props: IconProps) => Search(props),
'viewport-window-level': (props: IconProps) => ViewportWindowLevel(props),
'action-new-dialog': (props: IconProps) => ActionNewDialog(props),
'arrow-left': (props: IconProps) => Icons.ArrowLeft(props),
'arrow-right': (props: IconProps) => Icons.ArrowRight(props),
'arrow-down': (props: IconProps) => Icons.ArrowDown(props),
'status-tracked': (props: IconProps) => StatusTracking(props),
'status-untracked': (props: IconProps) => StatusUntracked(props),
'status-locked': (props: IconProps) => StatusLocked(props),
'tab-segmentation': (props: IconProps) => TabSegmentation(props),
'tab-studies': (props: IconProps) => TabStudies(props),
'tab-linear': (props: IconProps) => TabLinear(props),
'tab-4d': (props: IconProps) => Tab4D(props),
'tab-patient-info': (props: IconProps) => TabPatientInfo(props),
'tab-roi-threshold': (props: IconProps) => TabRoiThreshold(props),
'icon-mpr': (props: IconProps) => IconMPR(props),
'power-off': (props: IconProps) => PowerOff(props),
'icon-multiple-patients': (props: IconProps) => MultiplePatients(props),
'icon-patient': (props: IconProps) => Patient(props),
'chevron-down': (props: IconProps) => ChevronOpen(props),
'tool-length': (props: IconProps) => ToolLength(props),
'tool-3d-rotate': (props: IconProps) => Tool3DRotate(props),
'tool-angle': (props: IconProps) => ToolAngle(props),
'tool-annotate': (props: IconProps) => ToolAnnotate(props),
'tool-bidirectional': (props: IconProps) => ToolBidirectional(props),
'tool-calibration': (props: IconProps) => ToolCalibrate(props),
'tool-capture': (props: IconProps) => ToolCapture(props),
'tool-cine': (props: IconProps) => ToolCine(props),
'tool-circle': (props: IconProps) => ToolCircle(props),
'tool-cobb-angle': (props: IconProps) => ToolCobbAngle(props),
'tool-create-threshold': (props: IconProps) => ToolCreateThreshold(props),
'tool-crosshair': (props: IconProps) => ToolCrosshair(props),
'dicom-tag-browser': (props: IconProps) => ToolDicomTagBrowser(props),
'tool-flip-horizontal': (props: IconProps) => ToolFlipHorizontal(props),
'tool-freehand-polygon': (props: IconProps) => ToolFreehandPolygon(props),
'tool-freehand-roi': (props: IconProps) => ToolFreehandRoi(props),
'icon-tool-freehand-roi': (props: IconProps) => ToolFreehandRoi(props),
'icon-tool-spline-roi': (props: IconProps) => ToolSplineRoi(props),
'tool-freehand': (props: IconProps) => ToolFreehand(props),
'tool-fusion-color': (props: IconProps) => ToolFusionColor(props),
'tool-invert': (props: IconProps) => ToolInvert(props),
'tool-layout-default': (props: IconProps) => ToolLayoutDefault(props),
'tool-magnetic-roi': (props: IconProps) => ToolMagneticRoi(props),
'icon-tool-livewire': (props: IconProps) => ToolMagneticRoi(props),
'tool-magnify': (props: IconProps) => ToolMagnify(props),
'tool-measure-ellipse': (props: IconProps) => ToolMeasureEllipse(props),
'tool-more-menu': (props: IconProps) => ToolMoreMenu(props),
'tool-move': (props: IconProps) => ToolMove(props),
'tool-polygon': (props: IconProps) => ToolPolygon(props),
'tool-ellipse': (props: IconProps) => ToolMeasureEllipse(props),
'tool-quick-magnify': (props: IconProps) => ToolQuickMagnify(props),
'tool-rectangle': (props: IconProps) => ToolRectangle(props),
'tool-referenceLines': (props: IconProps) => ToolReferenceLines(props),
'tool-reset': (props: IconProps) => ToolReset(props),
'tool-rotate-right': (props: IconProps) => ToolRotateRight(props),
'tool-seg-brush': (props: IconProps) => ToolSegBrush(props),
'tool-seg-eraser': (props: IconProps) => ToolSegEraser(props),
'tool-seg-shape': (props: IconProps) => ToolSegShape(props),
'tool-seg-threshold': (props: IconProps) => ToolSegThreshold(props),
'tool-spline-roi': (props: IconProps) => ToolSplineRoi(props),
'tool-stack-image-sync': (props: IconProps) => ToolStackImageSync(props),
'tool-stack-scroll': (props: IconProps) => ToolStackScroll(props),
'toggle-dicom-overlay': (props: IconProps) => ToolToggleDicomOverlay(props),
'tool-ultrasound-bidirectional': (props: IconProps) => ToolUltrasoundBidirectional(props),
'tool-window-level': (props: IconProps) => ToolWindowLevel(props),
'tool-window-region': (props: IconProps) => ToolWindowRegion(props),
'icon-tool-window-region': (props: IconProps) => ToolWindowRegion(props),
'icon-tool-ultrasound-bidirectional': (props: IconProps) => ToolUltrasoundBidirectional(props),
'icon-tool-cobb-angle': (props: IconProps) => ToolCobbAngle(props),
'icon-tool-loupe': (props: IconProps) => ToolMagnify(props),
'tool-probe': (props: IconProps) => ToolProbe(props),
'icon-tool-probe': (props: IconProps) => ToolProbe(props),
'tool-zoom': (props: IconProps) => ToolZoom(props),
'tool-layout': (props: IconProps) => ToolLayout(props),
'icon-transferring': (props: IconProps) => IconTransferring(props),
'icon-alert-small': (props: IconProps) => Alert(props),
'icon-alert-outline': (props: IconProps) => AlertOutline(props),
'status-alert': (props: IconProps) => Alert(props),
info: (props: IconProps) => Info(props),
'notifications-info': (props: IconProps) => NotificationInfo(props),
'notificationwarning-diamond': (props: IconProps) => NotificationWarning(props),
'content-prev': (props: IconProps) => ContentPrev(props),
'content-next': (props: IconProps) => ContentNext(props),
'icon-settings': (props: IconProps) => Settings(props),
close: (props: IconProps) => Close(props),
pause: (props: IconProps) => Pause(props),
'icon-pause': (props: IconProps) => Pause(props),
settings: (props: IconProps) => Settings(props),
play: (props: IconProps) => Play(props),
'icon-play': (props: IconProps) => Play(props),
'layout-advanced-3d-four-up': (props: IconProps) => LayoutAdvanced3DFourUp(props),
'layout-advanced-3d-main': (props: IconProps) => LayoutAdvanced3DMain(props),
'layout-advanced-3d-only': (props: IconProps) => LayoutAdvanced3DOnly(props),
'layout-advanced-3d-primary': (props: IconProps) => LayoutAdvanced3DPrimary(props),
'layout-advanced-axial-primary': (props: IconProps) => LayoutAdvancedAxialPrimary(props),
'layout-advanced-mpr': (props: IconProps) => LayoutAdvancedMPR(props),
'layout-common-1x1': (props: IconProps) => LayoutCommon1x1(props),
'layout-common-1x2': (props: IconProps) => LayoutCommon1x2(props),
'layout-common-2x2': (props: IconProps) => LayoutCommon2x2(props),
'layout-common-2x3': (props: IconProps) => LayoutCommon2x3(props),
pencil: (props: IconProps) => Pencil(props),
'icon-list-view': (props: IconProps) => ListView(props),
'chevron-menu': 'chevron-down',
'icon-status-alert': (props: IconProps) => Alert(props),
'info-link': (props: IconProps) => InfoLink(props),
'launch-info': (props: IconProps) => LaunchInfo(props),
'old-trash': (props: IconProps) => Trash(props),
'tool-point': (props: IconProps) => ToolCircle(props),
'tool-freehand-line': (props: IconProps) => ToolFreehand(props),
clipboard: (props: IconProps) => Clipboard(props),
/** Adds an icon to the set of icons */
addIcon: (name: string, icon) => {
if (Icons[name]) {
console.warn('Replacing icon', name);
}
Icons[name] = icon;
},
ByName: ({ name, className, ...props }: { name: string; className?: string }) => {
const IconComponent = Icons[name];
if (!IconComponent) {
console.debug(`Icon "${name}" not found.`);
return <div>Missing Icon</div>;
}
return (
<IconComponent
{...props}
className={className}
/>
);
},
};

View File

@@ -0,0 +1,64 @@
import React from 'react';
import type { IconProps } from '../types';
export const ActionNewDialog = (props: IconProps) => (
<svg
width="18px"
height="19px"
viewBox="0 0 18 19"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g
id="*****Volume-Render-Dialog"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<g
id="volume-rendering-dialog"
transform="translate(-696, -178)"
>
<g
id="action-new-dialog"
transform="translate(696, 178.5)"
>
<rect
id="Rectangle"
x="0"
y="0"
width="18"
height="18"
></rect>
<path
d="M6.73167982,5 L6.73167982,6 L3.23167982,6 C2.40325269,6 1.73167982,6.67157288 1.73167982,7.5 L1.73167982,14.5 C1.73167982,15.3284271 2.40325269,16 3.23167982,16 L10.2316798,16 C11.0601069,16 11.7316798,15.3284271 11.7316798,14.5 L11.7316798,11.5 L12.7316798,11.5 L12.7316798,14.5 C12.7316798,15.8807119 11.6123917,17 10.2316798,17 L3.23167982,17 C1.85096795,17 0.73167982,15.8807119 0.73167982,14.5 L0.73167982,7.5 C0.73167982,6.11928813 1.85096795,5 3.23167982,5 L6.73167982,5 Z"
id="Path"
fill="currentColor"
fillRule="nonzero"
></path>
<line
x1="8.23167982"
y1="9.5"
x2="16.2316798"
y2="1.5"
id="Path"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
></line>
<polyline
id="Path"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
points="11.2316798 1.5 16.2316798 1.5 16.2316798 6.5"
></polyline>
</g>
</g>
</g>
</svg>
);
export default ActionNewDialog;

View File

@@ -0,0 +1,47 @@
import React from 'react';
import type { IconProps } from '../types';
export const Actions = (props: IconProps) => (
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
{...props}
>
<g
id="more-dropdown"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<rect
id="Rectangle"
x="0"
y="0"
width="24"
height="24"
></rect>
<g
id="Group-2"
transform="translate(5, 4)"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle
id="Oval"
cx="6.74621802"
cy="7.5938001"
r="1.68710308"
></circle>
<path
d="M8.17972732,1.06493425 L8.67634848,2.69920529 C8.84616867,3.26259112 9.42371506,3.59782471 9.99714943,3.46585687 L11.6537273,3.08194406 C12.2982043,2.9366791 12.9621245,3.22831522 13.2911435,3.80120174 C13.6201625,4.37408825 13.5374848,5.09450899 13.0872366,5.57796434 L11.9284539,6.82714853 C11.528377,7.25995707 11.528377,7.92764314 11.9284539,8.36045168 L13.0872366,9.60963586 C13.5374848,10.0930912 13.6201625,10.813512 13.2911435,11.3863985 C12.9621245,11.959285 12.2982043,12.2509211 11.6537273,12.1056561 L9.99714943,11.7217433 C9.42371506,11.5897755 8.84616867,11.9250091 8.67634848,12.4883949 L8.17972732,14.122666 C7.98874475,14.7549849 7.40616232,15.1876002 6.745631,15.1876002 C6.08509968,15.1876002 5.50251725,14.7549849 5.31153468,14.122666 L4.81491352,12.4883949 C4.64509333,11.9250091 4.06754694,11.5897755 3.49411257,11.7217433 L1.83753467,12.1056561 C1.19305769,12.2509211 0.529137506,11.959285 0.200118485,11.3863985 C-0.128900535,10.813512 -0.0462227777,10.0930912 0.404025372,9.60963586 L1.56280807,8.36045168 C1.96288499,7.92764314 1.96288499,7.25995707 1.56280807,6.82714853 L0.404025372,5.57796434 C-0.0462227777,5.09450899 -0.128900535,4.37408825 0.200118485,3.80120174 C0.529137506,3.22831522 1.19305769,2.9366791 1.83753467,3.08194406 L3.49411257,3.46585687 C4.06754694,3.59782471 4.64509333,3.26259112 4.81491352,2.69920529 L5.31153468,1.06493425 C5.50251725,0.43261528 6.08509968,0 6.745631,0 C7.40616232,0 7.98874475,0.43261528 8.17972732,1.06493425 Z"
id="Path"
></path>
</g>
</g>
</svg>
);
export default Actions;

View File

@@ -0,0 +1,51 @@
import React from 'react';
import type { IconProps } from '../types';
export const Add = (props: IconProps) => (
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
{...props}
>
<g
id="Add"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<rect
id="Rectangle"
x="0"
y="0"
width="24"
height="24"
></rect>
<g
id="Group"
transform="translate(6, 6)"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<line
x1="6"
y1="0"
x2="6"
y2="12"
id="Path"
></line>
<line
x1="12"
y1="6"
x2="0"
y2="6"
id="Path"
></line>
</g>
</g>
</svg>
);
export default Add;

View File

@@ -0,0 +1,32 @@
import React from 'react';
import type { IconProps } from '../types';
export const Alert = (props: IconProps) => (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g
fill="none"
fillRule="evenodd"
>
<path
d="M24 11.794c.017 6.667-5.333 12.108-12 12.205a11.823 11.823 0 0 1-12-11.79C-.019 5.541 5.331.1 12 .001a11.824 11.824 0 0 1 12 11.793z"
fill="#B70D11"
/>
<g
stroke="#FFF"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
>
<path d="M11.494 17.158a.245.245 0 0 0-.241.255.254.254 0 0 0 .253.245h0a.246.246 0 0 0 .241-.255.253.253 0 0 0-.244-.245h-.005M11.503 13V6" />
</g>
</g>
</svg>
);
export default Alert;

View File

@@ -0,0 +1,30 @@
import React from 'react';
import type { IconProps } from '../types';
export const AlertOutline = (props: IconProps) => (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g
stroke="#5ACCE6"
fill="none"
fillRule="evenodd"
strokeLinecap="round"
strokeLinejoin="round"
>
<path
d="M24 11.794a12.176 12.176 0 0 1-12 12.204 11.823 11.823 0 0 1-12-11.79A12.176 12.176 0 0 1 12 .003a11.824 11.824 0 0 1 12 11.793z"
strokeWidth="1.5"
/>
<g strokeWidth="2">
<path d="M11.74 18.658a.47.47 0 0 0-.26.075c-.068.05-.105.114-.1.18.007.135.175.244.38.244h0c.099 0 .193-.03.26-.076.07-.048.105-.113.1-.18-.006-.132-.165-.24-.366-.243h-.008M11.754 13V6" />
</g>
</g>
</svg>
);
export default AlertOutline;

View File

@@ -0,0 +1,49 @@
import React from 'react';
import type { IconProps } from '../types';
export const ArrowLeftBold = (props: IconProps) => (
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<title>arrow-left</title>
<g
id="Icons"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<g
id="Artboard"
transform="translate(-268, -87)"
>
<g
id="arrow-left"
transform="translate(280, 99) scale(-1, 1) translate(-280, -99)translate(268, 87)"
>
<rect
id="Rectangle"
opacity="0.5"
x="0"
y="0"
width="24"
height="24"
></rect>
<polygon
id="Path"
fill="currentColor"
fillRule="nonzero"
points="10.41 6 9 7.41 13.58 12 9 16.59 10.41 18 16.41 12"
></polygon>
</g>
</g>
</g>
</svg>
);
export default ArrowLeftBold;

View File

@@ -0,0 +1,25 @@
import React from 'react';
import type { IconProps } from '../types';
export const ArrowLeftBold = (props: IconProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
{...props}
>
<g fillRule="evenodd">
<path
fill="currentcolor"
fillRule="nonzero"
d="M17.207 10.793c.36.36.388.928.083 1.32l-.083.094-5 5c-.39.39-1.024.39-1.414 0-.36-.36-.388-.928-.083-1.32l.083-.094 4.292-4.293-4.292-4.293c-.36-.36-.388-.928-.083-1.32l.083-.094c.36-.36.928-.388 1.32-.083l.094.083 5 5z"
/>
<path
fill="currentcolor"
fillRule="nonzero"
d="M17.5 11.5c0 .513-.386.936-.883.993l-.117.007H6c-.552 0-1-.448-1-1 0-.513.386-.936.883-.993L6 10.5h10.5c.552 0 1 .448 1 1z"
/>
</g>
</svg>
);
export default ArrowLeftBold;

View File

@@ -0,0 +1,37 @@
import React from 'react';
import type { IconProps } from '../types';
export const Cancel = (props: IconProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="19"
height="19"
viewBox="0 0 19 19"
{...props}
>
<g
fill="currentColor"
fillRule="evenodd"
>
<circle
cx="9.5"
cy="9.5"
r="9.5"
fill="currentColor"
/>
<g
stroke="#000"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
>
<path
d="M.188.187L8.813 8.812M8.813.187L.188 8.812"
transform="translate(5 5)"
/>
</g>
</g>
</svg>
);
export default Cancel;

View File

@@ -0,0 +1,39 @@
import React from 'react';
import type { IconProps } from '../types';
export const CheckBoxChecked = (props: IconProps) => (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<defs>
<path
id="3nvolf8jsa"
d="M4.9 8.45 2.4 5.97l.795-.785L4.9 6.875 8.605 3.2l.795.79z"
/>
</defs>
<g
fill="none"
fillRule="evenodd"
>
<rect
stroke="#348CFD"
fill="#348CFD"
x=".5"
y=".5"
width="11"
height="11"
rx="3"
/>
<use
fill="#000"
xlinkHref="#3nvolf8jsa"
/>
</g>
</svg>
);
export default CheckBoxChecked;

View File

@@ -0,0 +1,25 @@
import React from 'react';
import type { IconProps } from '../types';
export const CheckBoxUnchecked = (props: IconProps) => (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<rect
x=".5"
y=".5"
width="11"
height="11"
rx="3"
stroke="#348CFD"
fill="none"
fillRule="evenodd"
/>
</svg>
);
export default CheckBoxUnchecked;

View File

@@ -0,0 +1,40 @@
import React from 'react';
import type { IconProps } from '../types';
export const ChevronClosed = (props: IconProps) => (
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g
id="icon-chevron-closed"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<rect
id="Rectangle"
opacity="0.2"
transform="translate(12, 12) rotate(90) translate(-12, -12)"
x="0"
y="0"
width="24"
height="24"
rx="4"
></rect>
<polyline
id="Path-2"
stroke="currentColor"
transform="translate(12.0902, 12.0451) rotate(90) translate(-12.0902, -12.0451)"
points="8 10 12.090229 14.090229 16.1804581 10"
></polyline>
</g>
</svg>
);
export default ChevronClosed;

View File

@@ -0,0 +1,28 @@
import React from 'react';
import type { IconProps } from '../types';
export const ChevronLeft = (props: IconProps) => (
<svg
width="7"
height="12"
viewBox="0 0 7 12"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g
fill="none"
fillRule="evenodd"
>
<path d="M0 0h7v12H0z" />
<path
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
d="M5.757 1.757 1.515 6l4.242 4.243"
/>
</g>
</svg>
);
export default ChevronLeft;

View File

@@ -0,0 +1,38 @@
import React from 'react';
import type { IconProps } from '../types';
export const ChevronOpen = (props: IconProps) => (
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g
id="icon-chevron-open"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<rect
id="Rectangle"
opacity="0.199999988"
x="0"
y="0"
width="24"
height="24"
rx="4"
></rect>
<polyline
id="Path-2"
stroke="currentColor"
points="8 10 12.090229 14.090229 16.1804581 10"
></polyline>
</g>
</svg>
);
export default ChevronOpen;

View File

@@ -0,0 +1,34 @@
import React from 'react';
import type { IconProps } from '../types';
export const Clear = (props: IconProps) => (
<svg
width="19"
height="19"
viewBox="0 0 19 19"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g
fill="none"
fillRule="evenodd"
>
<circle
fill="#0944B3"
cx="9.5"
cy="9.5"
r="9.5"
/>
<g
stroke="#000"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
>
<path d="m5.188 5.187 8.625 8.625M13.813 5.187l-8.625 8.625" />
</g>
</g>
</svg>
);
export default Clear;

View File

@@ -0,0 +1,15 @@
import React from 'react';
import type { IconProps } from '../types';
export const Clipboard = (props: IconProps) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
{...props}
>
<path d="M6 22v-16h16v7.543c0 4.107-6 2.457-6 2.457s1.518 6-2.638 6h-7.362zm18-7.614v-10.386h-20v20h10.189c3.163 0 9.811-7.223 9.811-9.614zm-10 1.614h-5v-1h5v1zm5-4h-10v1h10v-1zm0-3h-10v1h10v-1zm2-7h-19v19h-2v-21h21v2z" />
</svg>
);
export default Clipboard;

View File

@@ -0,0 +1,20 @@
import React from 'react';
import type { IconProps } from '../types';
export const Close = (props: IconProps) => (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M12 1.05 10.95 0 6 4.95 1.05 0 0 1.05 4.95 6 0 10.95 1.05 12 6 7.05 10.95 12 12 10.95 7.05 6z"
fill="currentColor"
fillRule="evenodd"
/>
</svg>
);
export default Close;

View File

@@ -0,0 +1,7 @@
import React from 'react';
import { Code as LucideCode, LucideProps } from 'lucide-react';
import type { IconProps } from '../types';
export const Code = (props: LucideProps) => <LucideCode {...props} />;
export default Code;

View File

@@ -0,0 +1,100 @@
import React from 'react';
import type { IconProps } from '../types';
export const ColorChange = (props: IconProps) => (
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
{...props}
>
<g
id="ColorChange"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<rect
id="Rectangle"
x="0"
y="0"
width="24"
height="24"
></rect>
<g
id="Group-4"
transform="translate(4, 5)"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<path
d="M11.9830989,1.93861826 C10.5789104,0.665654409 8.7439168,-0.0269214719 6.84881419,0.000801239617 C3.06621664,0.000801239617 0,2.55531723 0,5.70792489 C0.13353775,8.17267636 1.81407658,10.2821527 4.18664171,10.963157 C5.1582084,11.3047338 5.96098033,11.0256244 6.52318651,9.75767005 C6.72995019,9.15827585 7.22615951,8.70434675 7.84154981,8.55163978 C8.45694011,8.39893281 9.10779864,8.568221 9.57079585,9.00141636 L9.70370511,9.13432562"
id="Path"
></path>
<line
x1="13.2723188"
y1="6.75657895"
x2="9.69838874"
y2="13.9017808"
id="Path"
></line>
<path
d="M12.5054323,4.46389421 C12.1415749,5.29815229 12.4941364,6.27127189 13.3079188,6.67887077 C14.1217013,7.08646964 15.1121527,6.7860243 15.5623453,5.99500889 C16.4969889,4.55848545 16.3097611,2.6657723 15.1117829,1.44020853 C15.0640204,1.39470251 14.9942555,1.38091172 14.9327711,1.40482233 C14.8712867,1.42873293 14.8291685,1.48603381 14.8246989,1.55185231 C14.8366608,3.08695427 13.4517463,2.56727906 12.5054323,4.46389421 Z"
id="Path"
></path>
<path
d="M3.52608268,6.59044238 C3.70959208,6.59044238 3.85835583,6.73920613 3.85835583,6.92271553"
id="Path"
></path>
<path
d="M3.19380953,6.92404462 C3.19345611,6.83569024 3.22830728,6.75083342 3.29065893,6.68823236 C3.35301058,6.62563131 3.4377276,6.59044167 3.52608268,6.59044238"
id="Path"
></path>
<path
d="M3.52608268,7.25498868 C3.34257329,7.25498868 3.19380953,7.10622492 3.19380953,6.92271553"
id="Path"
></path>
<path
d="M3.85835583,6.92404462 C3.85762387,7.10703476 3.70907428,7.25499014 3.52608268,7.25498868"
id="Path"
></path>
<path
d="M4.52290214,3.26771086 C4.70641153,3.26771086 4.85517529,3.41647462 4.85517529,3.59998401"
id="Path"
></path>
<path
d="M4.19062898,3.60131311 C4.19062898,3.41780371 4.33939274,3.26903996 4.52290214,3.26903996"
id="Path"
></path>
<path
d="M4.52290214,3.93225717 C4.33939274,3.93225717 4.19062898,3.78349341 4.19062898,3.59998401"
id="Path"
></path>
<path
d="M4.85517529,3.60131311 C4.85444332,3.78430325 4.70589374,3.93225863 4.52290214,3.93225717"
id="Path"
></path>
<path
d="M7.84563365,2.60316456 C8.02914304,2.60316456 8.1779068,2.75192832 8.1779068,2.93543771"
id="Path"
></path>
<path
d="M7.5133605,2.9367668 C7.51300708,2.84841243 7.54785824,2.76355561 7.61020989,2.70095455 C7.67256154,2.63835349 7.75727857,2.60316385 7.84563365,2.60316456"
id="Path"
></path>
<path
d="M7.84563365,3.26771086 C7.66212425,3.26771086 7.5133605,3.11894711 7.5133605,2.93543771"
id="Path"
></path>
<path
d="M8.1779068,2.9367668 C8.1779068,3.1202762 8.02914304,3.26903996 7.84563365,3.26903996"
id="Path"
></path>
</g>
</g>
</svg>
);
export default ColorChange;

View File

@@ -0,0 +1,35 @@
import React from 'react';
import type { IconProps } from '../types';
export const ContentNext = (props: IconProps) => (
<svg
width="6px"
height="10px"
viewBox="0 0 6 10"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g
id="Icons"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<g
id="Artboard"
transform="translate(-460, -343)"
fill="#FFFFFF"
>
<polygon
id="chevron-next"
transform="translate(463, 348) scale(-1, 1) rotate(90) translate(-463, -348)"
points="463 351 458 345.736842 458.7 345 463 349.526316 467.3 345 468 345.736842"
></polygon>
</g>
</g>
</svg>
);
export default ContentNext;

View File

@@ -0,0 +1,35 @@
import React from 'react';
import type { IconProps } from '../types';
export const ContentPrev = (props: IconProps) => (
<svg
width="6px"
height="10px"
viewBox="0 0 6 10"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g
id="Icons"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<g
id="Artboard"
transform="translate(-435, -343)"
fill="#FFFFFF"
>
<polygon
id="chevron-prev"
transform="translate(438, 348) rotate(90) translate(-438, -348)"
points="438 351 433 345.736842 433.7 345 438 349.526316 442.3 345 443 345.736842"
></polygon>
</g>
</g>
</svg>
);
export default ContentPrev;

View File

@@ -0,0 +1,99 @@
import React from 'react';
import type { IconProps } from '../types';
export const Controls = (props: IconProps) => (
<svg
width="18px"
height="18px"
viewBox="0 0 18 18"
{...props}
>
<g
id="Controls"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle
id="Oval"
stroke="currentColor"
cx="9"
cy="3.34782609"
r="1.73913043"
></circle>
<line
x1="10.7373913"
y1="3.34782609"
x2="16.4095652"
y2="3.34782609"
id="Path"
stroke="currentColor"
></line>
<line
x1="1.62695652"
y1="3.34782609"
x2="7.2573913"
y2="3.34782609"
id="Path"
stroke="currentColor"
></line>
<g
id="Group-26"
transform="translate(9.0183, 9) scale(-1, 1) translate(-9.0183, -9)translate(1.627, 7.2609)"
stroke="currentColor"
>
<circle
id="Oval"
transform="translate(3.8948, 1.7391) scale(-1, 1) translate(-3.8948, -1.7391)"
cx="3.89478261"
cy="1.73913043"
r="1.73913043"
></circle>
<line
x1="5.63043478"
y1="1.73913043"
x2="14.7826087"
y2="1.73913043"
id="Path"
transform="translate(10.2065, 1.7391) scale(-1, 1) translate(-10.2065, -1.7391)"
></line>
<line
x1="0"
y1="1.73913043"
x2="2.15217391"
y2="1.73913043"
id="Path"
transform="translate(1.0761, 1.7391) scale(-1, 1) translate(-1.0761, -1.7391)"
></line>
</g>
<circle
id="Oval"
stroke="currentColor"
cx="7.26086957"
cy="14.6521739"
r="1.73913043"
></circle>
<line
x1="8.99652174"
y1="14.6521739"
x2="16.4095652"
y2="14.6521739"
id="Path"
stroke="currentColor"
></line>
<line
x1="1.62695652"
y1="14.6521739"
x2="5.52173913"
y2="14.6521739"
id="Path"
stroke="currentColor"
></line>
</g>
</svg>
);
export default Controls;

View File

@@ -0,0 +1,44 @@
import React from 'react';
import type { IconProps } from '../types';
export const Copy = (props: IconProps) => (
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
{...props}
>
<g
id="Copy"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<rect
id="Rectangle"
x="0"
y="0"
width="24"
height="24"
></rect>
<rect
id="Rectangle"
stroke="currentColor"
x="8.95205173"
y="4.5"
width="10"
height="10"
rx="2"
></rect>
<path
d="M7.05569885,9.5 L5.5,9.5 C4.67157288,9.5 4,10.1715729 4,11 L4,17.8271183 C4,18.6555454 4.67157288,19.3271183 5.5,19.3271183 L12.4520517,19.3271183 C13.2804789,19.3271183 13.9520517,18.6555454 13.9520517,17.8271183 L13.9520517,16.3489489 L13.9520517,16.3489489"
id="Path-4"
stroke="currentColor"
strokeLinecap="round"
></path>
</g>
</svg>
);
export default Copy;

View File

@@ -0,0 +1,33 @@
import React from 'react';
import type { IconProps } from '../types';
export const Database = (props: IconProps) => (
<svg
width="19"
height="19"
viewBox="0 0 19 19"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
transform="matrix(0.84873874,0,0,0.84873874,0.13586772,0.14249413)"
>
<ellipse
cx="11"
cy="4"
rx="9"
ry="3"
/>
<path d="m 2,4 v 14 a 9,3 0 0 0 18,0 V 4" />
<path d="m 2,11 a 9,3 0 0 0 18,0" />
</g>
</svg>
);
export default Database;

View File

@@ -0,0 +1,58 @@
import React from 'react';
import type { IconProps } from '../types';
export const Delete = (props: IconProps) => (
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
{...props}
>
<g
id="Delete"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<rect
id="Rectangle"
x="0"
y="0"
width="24"
height="24"
></rect>
<circle
id="Oval"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
cx="12"
cy="12"
r="7"
></circle>
<line
x1="8.95652174"
y1="8.95652174"
x2="15.0434783"
y2="15.0434783"
id="Path"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
></line>
<line
x1="15.0434783"
y1="8.95652174"
x2="8.95652174"
y2="15.0434783"
id="Path"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
></line>
</g>
</svg>
);
export default Delete;

View File

@@ -0,0 +1,79 @@
import React from 'react';
import type { IconProps } from '../types';
export const DicomTagBrowser = (props: IconProps) => (
<svg
width="24px"
height="24px"
viewBox="0 0 28 28"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<g
id="tool-dicom-tag-browser"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<rect
id="Rectangle"
x="0"
y="0"
width="28"
height="28"
></rect>
<g
id="Group"
transform="translate(4, 5.5)"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
>
<circle
id="Oval"
cx="1.73913043"
cy="1.73913043"
r="1.73913043"
></circle>
<line
x1="6.95652174"
y1="1.73913043"
x2="20"
y2="1.73913043"
id="Path"
></line>
<circle
id="Oval"
cx="1.73913043"
cy="8.69565217"
r="1.73913043"
></circle>
<line
x1="6.95652174"
y1="8.69565217"
x2="20"
y2="8.69565217"
id="Path"
></line>
<circle
id="Oval"
cx="1.73913043"
cy="15.6521739"
r="1.73913043"
></circle>
<line
x1="6.95652174"
y1="15.6521739"
x2="20"
y2="15.6521739"
id="Path"
></line>
</g>
</g>
</svg>
);
export default DicomTagBrowser;

View File

@@ -0,0 +1,49 @@
import React from 'react';
import type { IconProps } from '../types';
export const DisplayFillAndOutline = (props: IconProps) => (
<svg
width="18px"
height="18px"
viewBox="0 0 18 18"
{...props}
>
<g
id="view-outline-fill"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<g id="Group-13">
<rect
id="Rectangle"
x="0"
y="0"
width="18"
height="18"
></rect>
<rect
id="Rectangle"
stroke="currentColor"
x="1.5"
y="1.5"
width="15"
height="15"
rx="1"
></rect>
<rect
id="Rectangle"
fill="currentColor"
x="3.5"
y="3.5"
width="11"
height="11"
rx="1"
></rect>
</g>
</g>
</svg>
);
export default DisplayFillAndOutline;

View File

@@ -0,0 +1,40 @@
import React from 'react';
import type { IconProps } from '../types';
export const DisplayFillOnly = (props: IconProps) => (
<svg
width="18px"
height="18px"
viewBox="0 0 18 18"
{...props}
>
<g
id="view-fill"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<g id="Group-13">
<rect
id="Rectangle"
x="0"
y="0"
width="18"
height="18"
></rect>
<rect
id="Rectangle"
fill="currentColor"
x="2"
y="2"
width="14"
height="14"
rx="1"
></rect>
</g>
</g>
</svg>
);
export default DisplayFillOnly;

Some files were not shown because too many files have changed in this diff Show More