ui(components): migrates to new toolbar ui components and Numeric for Input InputRange and InputDoubleRange (#4728)

Co-authored-by: sedghi <ar.sedghi@gmail.com>
This commit is contained in:
Dan Rukasandsedghi authored and GitHub committed 2025-01-29 13:36:52 -05:00
1 parent ac5ae4a29e
commit a8d84443a9
116 files changed
+2930 -1038

No files matched your search

@@ -3,12 +3,28 @@ sidebar_position: 11
sidebar_label: Playwright Testing
---
:::note
You might need to run the `yarn playwright install ` for the first time if you have not
:::
# Running the tests
```bash
# run the tests
yarn test:e2e:ui
```
# Writing PlayWright Tests
Our Playwright tests are written using the Playwright test framework. We use these tests to test our OHIF Viewer and ensure that it is working as expected.
In this guide, we will show you how to write Playwright tests for the OHIF Viewer.
## Using a specific study and mode
If you would like to use a specific study, you can use the `studyInstanceUID` property to reference the study you would like to visit. for example, if you would like to use the study with StudyInstanceUID `2.16.840.1.114362.1.11972228.22789312658.616067305.306.2` and the mode `Basic Viewer`, you can use the following code snippet:
@@ -0,0 +1,199 @@
---
title: Input Number, Range, and Double Range
---
# Migration Guide: Moving to `Numeric` Component
This guide explains how to migrate from the existing `Input`, `InputRange`, and `InputDoubleRange` components to the new `Numeric` meta component.
## Why Migrate?
The old components relied heavily on props, making them complex and difficult to maintain and apply custom styles. The new `Numeric` component provides a structured approach with a context-based API, reducing prop clutter and improving reusability.
## `Input` > `Numeric.NumberInput`
### Basic Usage
**Old Usage:**
```tsx
<Input
id="example"
label="Enter a number"
value={value}
onChange={(e) => setValue(e.target.value)}
type="number"
/>
```
**New Usage:**
```tsx
<Numeric.Container mode="number" value={value} onChange={setValue}>
<Numeric.Label>Enter a number</Numeric.Label>
<Numeric.NumberInput />
</Numeric.Container>
```
### `Input` with Custom Classes
#### **Old Usage (with containerClassName, labelClassName, and className)**
In the old implementation, we manually applied `containerClassName`, `labelClassName`, and `className` to style the `Input` component:
```tsx
<Input
id="example"
label="Enter a number"
value={value}
onChange={(e) => setValue(e.target.value)}
type="number"
containerClassName="flex flex-col space-y-2"
labelClassName="text-gray-500 text-sm"
className="border rounded p-2"
/>
```
**New Usage (Migrating to `Numeric.NumberInput`)**
With `Numeric`, you should wrap everything inside `Numeric.Container`, and you can directly apply class names to its subcomponents:
```tsx
<Numeric.Container mode="number" value={value} onChange={setValue} className="flex flex-col space-y-2">
<Numeric.Label className="text-gray-500 text-sm">Enter a number</Numeric.Label>
<Numeric.NumberInput className="border rounded p-2" />
</Numeric.Container>
```
## `InputRange` > `Numeric.SingleRange`
### Basic Usage
**Old Usage:**
```tsx
<InputRange
value={value}
onChange={setValue}
minValue={0}
maxValue={100}
step={1}
showLabel
/>
```
**New Usage:**
```tsx
<Numeric.Container mode="singleRange" value={value} onChange={setValue} min={0} max={100} step={1}>
<Numeric.Label showValue>Range</Numeric.Label>
<Numeric.SingleRange showNumberInput />
</Numeric.Container>
```
### Custom Classes
**Old Usage**
```tsx
<InputRange
value={value}
onChange={setValue}
minValue={0}
maxValue={100}
step={1}
containerClassName="flex flex-col space-y-2"
inputClassName="w-full bg-gray-700"
labelClassName="text-gray-500 text-sm"
labelVariant="body1"
showLabel={true}
labelPosition="left"
/>
```
**New Usage**
```tsx
<Numeric.Container mode="singleRange" value={value} onChange={setValue} min={0} max={100} step={1} className="flex flex-col space-y-2">
<Numeric.Label className="text-gray-500 text-sm">Range</Numeric.Label>
<Numeric.SingleRange sliderClassName="w-full bg-gray-700" />
</Numeric.Container>
```
:::note
You now have more control over the position of the label and the slider. You can use pretty much any layout you want, whether that's flex, grid, or something else. Instead of relying on `labelPosition` to position the label, you're free to use the layout that works best for you.
:::
### AllowNumberEdit
**Old Usage:**
```tsx
<InputRange
value={value}
onChange={setValue}
minValue={0}
maxValue={100}
step={1}
allowNumberEdit={true}
showAdjustmentArrows={true}
/>
```
**New Usage:**
Using `Numeric.SingleRange` and `showNumberInput`
```tsx
<Numeric.Container mode="singleRange" value={value} onChange={setValue} min={0} max={100} step={1}>
<Numeric.Label showValue>Range</Numeric.Label>
<Numeric.SingleRange showNumberInput />
</Numeric.Container>
```
## `InputDoubleRange` > `Numeric.DoubleRange`
### Basic Usage
**Old Usage:**
```tsx
<InputDoubleRange
values={rangeValues}
onChange={setRangeValues}
minValue={0}
maxValue={100}
step={5}
showLabel
/>
```
**New Usage:**
```tsx
<Numeric.Container mode="doubleRange" values={rangeValues} onChange={setRangeValues} min={0} max={100} step={5}>
<Numeric.Label showValue>Range</Numeric.Label>
<Numeric.DoubleRange showNumberInputs />
</Numeric.Container>
```
---
## Summary of Changes
| Old Component | New Component Equivalent |
|--------------------|------------------------|
| `<Input>` | `<Numeric.NumberInput>` |
| `<InputRange>` | `<Numeric.SingleRange>` |
| `<InputDoubleRange>` | `<Numeric.DoubleRange>` |
@@ -0,0 +1,68 @@
---
title: Tests
---
## 1. ToolButton `data-active` Attribute
- **Previous**: Checked for a `bg-primary-light` class to determine if a tool was active.
- **Now**: Check for the HTML attribute `data-active="true"`.
### Example
```diff
- cy.get('@wwwcButton').should('have.class', 'bg-primary-light');
+ cy.get('@wwwcButton').should('have.attr', 'data-active', 'true');
```
## 2. Additional Data Attributes
- Each tool button now includes:
- `data-tool="<toolId>"`
- `data-active="<true|false>"`
This makes it easier to identify and assert on specific tools in the DOM.
### Example
```diff
- <span data-cy={id}>
+ <span
+ data-cy={id}
+ data-tool={id}
+ data-active={isActive}
+ >
```
## 3. MPR Button Class Change
If you were targeting the `ohif-disabled` class, you need to update your tests to target the `cursor-not-allowed` class.
- **Previous**: `ohif-disabled`
- **Now**: `cursor-not-allowed`
### Example
```diff
- cy.get('[data-cy="MPR"]').should('have.class', 'ohif-disabled');
+ cy.get('[data-cy="MPR"]').should('have.class', 'cursor-not-allowed');
```
## 4. Removal of Stack Scroll Alias
- The `[data-cy="StackScroll"]` element is no longer reliably in the DOM at study load.
- If needed, reintroduce or conditionally assert its presence when appropriate.
```diff
- cy.get('[data-cy="StackScroll"]').as('stackScrollBtn');
+ // Removed due to absence in DOM at study load
```
---
## Summary
1. **Replace** all checks for `bg-primary-light` with `data-active="true"`.
2. **Use** `data-tool` and `data-active` attributes for more robust DOM selection and assertions.
3. **Update** MPR button checks to `cursor-not-allowed`.
4. **Remove** the `[data-cy="StackScroll"]` alias (or only use it when the element is present).
@@ -0,0 +1,101 @@
---
title: Toolbar
---
# Toolbar
## New Toolbar uiType
We have two new toolbar button types: `ohif.toolButtonList` and `ohif.toolButton`, which are intended to replace the `ohif.radioGroup` and `ohif.splitButton` types.
Note that these are backward compatible, so if you are not ready to pick up the new ui types (which are more flexible and powerful), you can continue using the old types.
```js
// Old type
{
uiType: 'ohif.radioGroup',
}
// New type
{
uiType: 'ohif.toolButton',
}
```
and
```js
// Old type
{
uiType: 'ohif.splitButton',
}
// New type
{
uiType: 'ohif.toolButtonList',
}
```
The `ohif.buttonGroup` and `ohif.radioGroup` types used in the Toolbox have been replaced with `ohif.toolBoxButtonGroup` and `ohif.toolBoxButton` to reflect their usage in the Toolbox, which has distinct styling.
```js
// Old type
{
uiType: 'ohif.buttonGroup',
}
// New type
{
uiType: 'ohif.toolBoxButtonGroup',
}
```
```js
// Old type
{
uiType: 'ohif.radioGroup',
}
// New type
{
uiType: 'ohif.toolBoxButton',
}
```
## getToolbarModule
The `getToolbarModule` function previously returned `disabled`, `disabledText`, and `className` as part of its evaluation process for the button state. These properties will still be returned, but common class names are now handled internally by the new UI button components, including `ToolButton`, `ToolButtonList`, `Toolbox`, and `ToolBoxGroup`. You can override the `className` if you need to.
## ToolBox
Previously, the segmentation toolbox was not using an `evaluator` property. This is now taken into account
```js
// old
{
id: 'BrushTools',
uiType: 'ohif.buttonGroup',
props: {
groupId: 'BrushTools',
items: []
}
}
// now
{
id: 'BrushTools',
uiType: 'ohif.buttonGroup',
props: {
groupId: 'BrushTools',
evaluate: 'evaluate.cornerstone.hasSegmentation',
items: []
}
}
```
+44 -638
View File
@@ -1,651 +1,57 @@
import React, { useState } from 'react';
import React from 'react';
import '../css/custom.css';
import Layout from '@theme/Layout';
import { Label } from '../../../ui-next/src/components/Label';
import { Input } from '../../../ui-next/src/components/Input';
import { Separator } from '../../../ui-next/src/components/Separator';
import { Tabs, TabsList, TabsTrigger } from '../../../ui-next/src/components/Tabs';
import {
Select,
SelectTrigger,
SelectContent,
SelectItem,
SelectValue,
} from '../../../ui-next/src/components/Select';
import { Button } from '../../../ui-next/src/components/Button';
import { Switch } from '../../../ui-next/src/components/Switch';
import { Checkbox } from '../../../ui-next/src/components/Checkbox';
import { Toggle } from '../../../ui-next/src/components/Toggle';
import { Slider } from '../../../ui-next/src/components/Slider';
import { ScrollArea } from '../../../ui-next/src/components/ScrollArea';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
} from '../../../ui-next/src/components/DropdownMenu';
import { Icons } from '../../../ui-next/src/components/Icons';
import { Toaster, toast } from '../../../ui-next/src/components/Sonner';
import { DataRow } from '../../../ui-next/src/components/DataRow';
import DataRowExample from './patterns/DataRowExample';
import {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
} from '../../../ui-next/src/components/Card';
interface ShowcaseRowProps {
title: string;
description?: string;
children: React.ReactNode;
code: string;
}
export default function ComponentShowcase() {
// Handlers to trigger different types of toasts
const triggerSuccess = () => {
toast.success('This is a success toast!');
};
const triggerError = () => {
toast.error('This is an error toast!');
};
const triggerInfo = () => {
toast.info('This is an info toast!');
};
const triggerWarning = () => {
toast.warning('This is a warning toast!');
};
// Handler to trigger a toast.promise example
const triggerPromiseToast = () => {
const promise = () =>
new Promise<{ name: string }>(resolve =>
setTimeout(() => resolve({ name: 'Segmentation 1' }), 3000)
);
toast.promise(promise(), {
loading: 'Loading Segmentation...',
success: data => `${data.name} has been added`,
error: 'Error',
});
};
// Handler to trigger a toast with description
const triggerDescriptionToast = () => {
toast.success('Completed', {
description: 'This is a detailed description of the success message.',
});
};
// Handler to trigger a toast with an action button
const triggerActionButtonToast = () => {
toast.info('No active segmentation detected', {
description: 'Create a segmentation before using the Brush',
});
};
// Handler to trigger a toast with a cancel button
const triggerCancelButtonToast = () => {
toast.error('No active segmentation detected', {
description: 'Create a segmentation before using the Brush',
});
};
// Handler to trigger a toast with both action and cancel buttons
const triggerCombinedToast = () => {
toast.warning('Warning!', {
description: 'This is a warning with both action and cancel buttons.',
action: (
<Button
size="sm"
variant="ghost"
onClick={() => alert('Retry action clicked')}
>
Retry
</Button>
),
cancel: (
<Button
size="sm"
variant="ghost"
onClick={() => toast.dismiss()}
>
Cancel
</Button>
),
});
};
// Handler to trigger a loading toast using Toaster's default loading icon
const showLoadingToast = () => {
toast.loading('Loading your data...');
};
import { TooltipProvider } from '../../../ui-next/src/components/Tooltip';
// Import all showcase components
import ButtonShowcase from './components/ButtonShowcase';
import CheckboxShowcase from './components/CheckboxShowcase';
import DataRowShowcase from './components/DataRowShowcase';
import DropdownMenuShowcase from './components/DropdownMenuShowcase';
import InputShowcase from './components/InputShowcase';
import ScrollAreaShowcase from './components/ScrollAreaShowcase';
import SelectShowcase from './components/SelectShowcase';
import SliderShowcase from './components/SliderShowcase';
import SwitchShowcase from './components/SwitchShowcase';
import TabsShowcase from './components/TabsShowcase';
import ToastShowcase from './components/ToastShowcase';
import ToolButtonShowcase from './components/ToolButtonShowcase';
import ToolButtonListShowcase from './components/ToolButtonListShowcase';
import NumericMetaShowcase from './components/NumericMetaShowcase';
/**
* Components List page that displays all available UI components
*/
export default function ComponentsList() {
return (
<Layout
title="Components"
description="OHIF Viewer Components"
>
<div className="text-foreground min-h-screen bg-black">
<div className="mx-auto my-4 max-w-5xl pt-6 pb-3">
<div className="grid grid-cols-1 gap-5 md:grid-cols-3">
<a
href="/colors-and-type"
className="focus:ring-primary block rounded-lg text-inherit no-underline hover:no-underline focus:outline-none focus:ring-2"
>
<Card className="hover:bg-primary/30 w-full transition-colors">
<CardHeader>
<CardTitle className="text-foreground text-xl">
<Icons.ColorChange className="h-12 w-12" />
Colors & Typography
</CardTitle>
<CardDescription className="text-base">
Color Palette and Typography Guidelines
</CardDescription>
</CardHeader>
</Card>
</a>
<a
href="/components-list"
className="focus:ring-primary block rounded-lg text-inherit no-underline hover:no-underline focus:outline-none focus:ring-2"
>
<Card className="hover:bg-primary/30 w-full transition-colors">
<CardHeader>
<CardTitle className="text-foreground text-xl">
<Icons.ColorChange className="h-12 w-12" />
Components
</CardTitle>
<CardDescription className="text-base">
Essential UI Components with Variants
</CardDescription>
</CardHeader>
</Card>
</a>
<a
href="/patterns"
className="focus:ring-primary block rounded-lg text-inherit no-underline hover:no-underline focus:outline-none focus:ring-2"
>
<Card className="hover:bg-primary/30 w-full transition-colors">
<CardHeader>
<CardTitle className="text-foreground text-xl">
<Icons.ColorChange className="h-12 w-12" />
Patterns
</CardTitle>
<CardDescription className="text-base">
Component-Based Layout Examples
</CardDescription>
</CardHeader>
</Card>
</a>
<TooltipProvider>
<div className="text-foreground min-h-screen bg-black">
<div className="mx-auto my-4 max-w-5xl pt-4 pb-6">
<div className="ml-6 mb-6 text-base">
<h1 className="text-foreground mb-3 text-5xl">Components</h1>
</div>
<TabsShowcase />
<ButtonShowcase />
<CheckboxShowcase />
<DataRowShowcase />
<DropdownMenuShowcase />
<InputShowcase />
<ScrollAreaShowcase />
<SelectShowcase />
<SliderShowcase />
<SwitchShowcase />
<TabsShowcase />
<ToastShowcase />
<ToolButtonShowcase />
<ToolButtonListShowcase />
<NumericMetaShowcase />
</div>
</div>
<div className="mx-auto my-4 max-w-5xl pt-4 pb-6">
<div className="ml-6 mb-6 text-base">
<h1 className="text-foreground mb-3 text-5xl">Components</h1>
</div>
{/* Alphabetically Sorted ShowcaseRows */}
<ShowcaseRow
title="Buttons"
description="Button components and size variants. Use the primary and secondary buttons in dialogs or screens where one action is required. In the Viewer application, use ghost button in panels where many different actions are available."
code={`
<Button variant="default">Primary Button</Button>
<Button variant="secondary">Secondary Button</Button>
<Button variant="ghost">Ghost Button</Button>
<Button variant="ghost" size="icon">?</Button>
<Button variant="link">Link</Button>
`}
>
<div className="flex flex-wrap gap-4">
<Button variant="default">Primary Button</Button>
<Button variant="secondary">Secondary Button</Button>
<Button variant="ghost">Ghost Button</Button>
<Button
variant="ghost"
size="icon"
>
?
</Button>
<Button variant="link">Link</Button>
</div>
<div className="mt-6 flex flex-wrap gap-4">
<Button
variant="default"
size="lg"
className="w-[107px]"
>
Large Button
</Button>
<Button
variant="default"
size="sm"
>
Small Button
</Button>
</div>
</ShowcaseRow>
<ShowcaseRow
title="Checkbox"
description="When possible use Switch in place of checkbox. If necessary, Checkbox provides a smaller component to change between two states or options."
code={`
<div className="items-top flex space-x-2">
<Checkbox id="terms1" />
<div className="grid gap-1.5 pt-0.5 leading-none">
<Label>Display inactive segmentations</Label>
</div>
</div>
`}
>
<div className="items-top flex space-x-2">
<Checkbox id="terms1" />
<div className="grid gap-1.5 pt-0.5 leading-none">
<Label>Display inactive segmentations</Label>
</div>
</div>
</ShowcaseRow>
<ShowcaseRow
title="Data Row"
description="A selectable row with action menu options and visibility toggle. Color, Secondary details, and Image Series are optional to display."
code={`
Example code coming soon.
`}
>
{/* Render the DataRowExample component */}
<DataRowExample />
</ShowcaseRow>
<ShowcaseRow
title="Dropdown Menu"
description="Dropdown menu provides a flexible list of options that can open from buttons or other elements"
code={`
<DropdownMenu>
<DropdownMenuTrigger>
<Button>Open Basic</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align Start</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align End</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align Top</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="top" align="start">
<DropdownMenuItem onSelect={() => console.debug('Item 1')}>Item 1</DropdownMenuItem>
<DropdownMenuItem onSelect={() => console.debug('Item 2')}>Item 2</DropdownMenuItem>
<DropdownMenuItem onSelect={() => console.debug('Item 3')}>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
`}
>
<div className="flex flex-wrap gap-4">
<DropdownMenu>
<DropdownMenuTrigger>
<Button>Open Basic</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align Start</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align End</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align Top</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
align="start"
>
<DropdownMenuItem onSelect={() => console.debug('Item 1')}>
Item 1
</DropdownMenuItem>
<DropdownMenuItem onSelect={() => console.debug('Item 2')}>
Item 2
</DropdownMenuItem>
<DropdownMenuItem onSelect={() => console.debug('Item 3')}>
Long name Item 3
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</ShowcaseRow>
<ShowcaseRow
title="Input"
description="Input fields can be used with or without example text"
code={`
<div className="inline-block">
<div className="mr-4 inline-block">
<Label>Patient Weight</Label>
</div>
<div className="inline-block">
<Input placeholder="(kg)" />
</div>
</div>
`}
>
<div className="inline-block">
<div className="mr-4 inline-block">
<Label>Patient Weight</Label>
</div>
<div className="inline-block">
<Input placeholder="(kg)" />
</div>
</div>
</ShowcaseRow>
<ShowcaseRow
title="Scroll Area"
description="Displays a scroll indicator when hovering within an element."
code={`
<ScrollArea className="border-input bg-background h-[150px] w-[350px] rounded-md border p-2 text-sm text-white">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua.
</ScrollArea>
`}
>
<ScrollArea className="border-input bg-background h-[150px] w-[350px] rounded-md border p-2 text-sm text-white">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</ScrollArea>
</ShowcaseRow>
<ShowcaseRow
title="Select"
description="Switch between a list of options"
code={`
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
`}
>
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
</ShowcaseRow>
<ShowcaseRow
title="Slider"
description="Used to select a value in a predefined range."
code={`
<div className="w-40 px-5">
<Slider
className="w-full"
defaultValue={[50]}
max={100}
step={1}
/>
</div>
`}
>
<div className="w-40 px-5">
<Slider
className="w-full"
defaultValue={[50]}
max={100}
step={1}
/>
</div>
</ShowcaseRow>
<ShowcaseRow
title="Switch"
description="A toggle Switch is used to change between two different states. Use descriptive labels next to Switches that are understandable before interacting."
code={`
<Switch />
`}
>
<Switch defaultChecked />
<Label className="text-foreground mx-2 w-14 flex-none whitespace-nowrap text-sm">
Sync changes in all viewports
</Label>
</ShowcaseRow>
<ShowcaseRow
title="Tabs"
description="Tabs (or segmented controls) can be used to provide navigation options or allow users to switch between multiple options (e.g., tool settings) "
code={`
<Tabs className="w-[400px]">
<TabsList>
<TabsTrigger value="circle">Circle</TabsTrigger>
<Separator orientation="vertical" />
<TabsTrigger value="sphere">Sphere</TabsTrigger>
<Separator orientation="vertical" />
<TabsTrigger value="square">Square</TabsTrigger>
</TabsList>
</Tabs>
`}
>
<Tabs className="w-[400px]">
<TabsList>
<TabsTrigger value="circle">Circle</TabsTrigger>
<Separator orientation="vertical" />
<TabsTrigger value="sphere">Sphere</TabsTrigger>
<Separator orientation="vertical" />
<TabsTrigger value="square">Square</TabsTrigger>
</TabsList>
</Tabs>
</ShowcaseRow>
<ShowcaseRow
title="Toast"
description="A toast notification displays temporary feedback messages to users above the current UI. Notifications stack into one unit after multiple cascading notifications."
code={`
Example code coming soon.
`}
>
{/* Toast Examples Section */}
Simple message:
<div className="mt-2 mb-7 space-x-2">
<Button
variant="default"
onClick={triggerPromiseToast}
>
Loading & Success Toast
</Button>
<Button
variant="default"
onClick={triggerSuccess}
>
Success Toast
</Button>
<Button
variant="default"
onClick={triggerError}
>
Error Toast
</Button>
<Button
variant="default"
onClick={triggerInfo}
>
Info Toast
</Button>
<Button
variant="default"
onClick={triggerWarning}
>
Warning Toast
</Button>
</div>
Message with details:
<div className="mt-2 space-x-2">
<Button
variant="default"
onClick={triggerDescriptionToast}
>
Success Toast
</Button>
<Button
variant="default"
onClick={triggerActionButtonToast}
>
Info Toast
</Button>
<Button
variant="default"
onClick={triggerCancelButtonToast}
>
Error Toast
</Button>
<Button
variant="default"
onClick={triggerCombinedToast}
>
Toast with Buttons
</Button>
</div>
{/* Render the Toaster component */}
<Toaster />
</ShowcaseRow>
</div>
</div>
</TooltipProvider>
</Layout>
);
}
function ShowcaseRow({ title, description, children, code }: ShowcaseRowProps) {
const [showCode, setShowCode] = useState(false);
return (
<div className="bg-background mb-8 rounded-lg p-6">
<div className="mb-4 flex items-start justify-between">
<div>
<h2 className="text-highlight text-2xl">{title}</h2>
</div>
<Button
className="text-primary"
variant="ghost"
size="sm"
onClick={() => setShowCode(!showCode)}
>
{showCode ? 'Hide Code' : 'Show Code'} <Icons.Code className="ml-2 h-4 w-4" />
</Button>
</div>
<div className="grid grid-cols-1 gap-9 md:grid-cols-3">
<div className="text-base md:col-span-1">
{description && <p className="text-secondary-foreground mt-2">{description}</p>}
</div>
<div className="flex min-h-[120px] items-center md:col-span-2">
<div className="showcase-content">{children}</div>
</div>
</div>
{showCode && (
<pre className="border-input mt-4 overflow-x-auto rounded-md border bg-black p-4 text-sm">
<code>{code}</code>
</pre>
)}
</div>
);
}
// function ShowcaseRow({ title, description, children, code }: ShowcaseRowProps) {
// const [showCode, setShowCode] = useState(false);
// return (
// <div className="bg-background mb-8 rounded-lg p-6">
// <div className="mb-4 flex items-start justify-between">
// <div>
// <h2 className="text-2xl font-bold">{title}</h2>
// {description && <p className="text-muted-foreground mt-1">{description}</p>}
// </div>
// <Button
// className="text-primary"
// variant="outline"
// size="sm"
// onClick={() => setShowCode(!showCode)}
// >
// {showCode ? 'Hide Code' : 'Show Code'} <Icons.Code className="ml-2 h-4 w-4" />
// </Button>
// </div>
// <div className="showcase-content mb-4">{children}</div>
// {showCode && (
// <pre className="mt-4 overflow-x-auto rounded-md bg-black p-4 text-sm">
// <code>{code}</code>
// </pre>
// )}
// </div>
// );
// }
@@ -0,0 +1,54 @@
import React from 'react';
import { Button } from '../../../../ui-next/src/components/Button';
import ShowcaseRow from './ShowcaseRow';
/**
* ButtonShowcase component displays button variants and examples
*/
export default function ButtonShowcase() {
return (
<ShowcaseRow
title="Buttons"
description="Button components and size variants. Use the primary and secondary buttons in dialogs or screens where one action is required. In the Viewer application, use ghost button in panels where many different actions are available."
code={`
<Button variant="default">Primary Button</Button>
<Button variant="secondary">Secondary Button</Button>
<Button variant="ghost">Ghost Button</Button>
<Button variant="ghost" size="icon">?</Button>
<Button variant="link">Link</Button>
`}
>
<div className="flex flex-wrap gap-4">
<Button variant="default">Primary Button</Button>
<Button variant="secondary">Secondary Button</Button>
<Button variant="ghost">Ghost Button</Button>
<Button
variant="ghost"
size="icon"
>
?
</Button>
<Button variant="link">Link</Button>
</div>
<div className="mt-6 flex flex-wrap gap-4">
<Button
variant="default"
size="lg"
className="w-[107px]"
>
Large Button
</Button>
<Button
variant="default"
size="sm"
>
Small Button
</Button>
</div>
</ShowcaseRow>
);
}
@@ -0,0 +1,31 @@
import React from 'react';
import { Checkbox } from '../../../../ui-next/src/components/Checkbox';
import { Label } from '../../../../ui-next/src/components/Label';
import ShowcaseRow from './ShowcaseRow';
/**
* CheckboxShowcase component displays checkbox variants and examples
*/
export default function CheckboxShowcase() {
return (
<ShowcaseRow
title="Checkbox"
description="When possible use Switch in place of checkbox. If necessary, Checkbox provides a smaller component to change between two states or options."
code={`
<div className="items-top flex space-x-2">
<Checkbox id="terms1" />
<div className="grid gap-1.5 pt-0.5 leading-none">
<Label>Display inactive segmentations</Label>
</div>
</div>
`}
>
<div className="items-top flex space-x-2">
<Checkbox id="terms1" />
<div className="grid gap-1.5 pt-0.5 leading-none">
<Label>Display inactive segmentations</Label>
</div>
</div>
</ShowcaseRow>
);
}
@@ -0,0 +1,20 @@
import React from 'react';
import DataRowExample from '../patterns/DataRowExample';
import ShowcaseRow from './ShowcaseRow';
/**
* DataRowShowcase component displays DataRow variants and examples
*/
export default function DataRowShowcase() {
return (
<ShowcaseRow
title="Data Row"
description="A selectable row with action menu options and visibility toggle. Color, Secondary details, and Image Series are optional to display."
code={`
Example code coming soon.
`}
>
<DataRowExample />
</ShowcaseRow>
);
}
@@ -0,0 +1,81 @@
import React from 'react';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
} from '../../../../ui-next/src/components/DropdownMenu';
import { Button } from '../../../../ui-next/src/components/Button';
import ShowcaseRow from './ShowcaseRow';
/**
* DropdownMenuShowcase component displays DropdownMenu variants and examples
*/
export default function DropdownMenuShowcase() {
return (
<ShowcaseRow
title="Dropdown Menu"
description="Dropdown menu provides a flexible list of options that can open from buttons or other elements"
code={`
<DropdownMenu>
<DropdownMenuTrigger>
<Button>Open Basic</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
`}
>
<div className="flex flex-wrap gap-4">
<DropdownMenu>
<DropdownMenuTrigger>
<Button>Open Basic</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align Start</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align End</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>Item 1</DropdownMenuItem>
<DropdownMenuItem>Item 2</DropdownMenuItem>
<DropdownMenuItem>Long name Item 3</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>Open Align Top</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
align="start"
>
<DropdownMenuItem onSelect={() => console.debug('Item 1')}>Item 1</DropdownMenuItem>
<DropdownMenuItem onSelect={() => console.debug('Item 2')}>Item 2</DropdownMenuItem>
<DropdownMenuItem onSelect={() => console.debug('Item 3')}>
Long name Item 3
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</ShowcaseRow>
);
}
@@ -0,0 +1,35 @@
import React from 'react';
import { Input } from '../../../../ui-next/src/components/Input';
import { Label } from '../../../../ui-next/src/components/Label';
import ShowcaseRow from './ShowcaseRow';
/**
* InputShowcase component displays Input variants and examples
*/
export default function InputShowcase() {
return (
<ShowcaseRow
title="Input"
description="Input fields can be used with or without example text"
code={`
<div className="inline-block">
<div className="mr-4 inline-block">
<Label>Patient Weight</Label>
</div>
<div className="inline-block">
<Input placeholder="(kg)" />
</div>
</div>
`}
>
<div className="inline-block">
<div className="mr-4 inline-block">
<Label>Patient Weight</Label>
</div>
<div className="inline-block">
<Input placeholder="(kg)" />
</div>
</div>
</ShowcaseRow>
);
}
@@ -0,0 +1,260 @@
import React from 'react';
import Numeric from '../../../../ui-next/src/components/Numeric';
import Icons from '../../../../ui-next/src/components/Icons';
import ShowcaseRow from './ShowcaseRow';
/**
* NumericShowcase component displays Numeric variants and examples
*/
export default function NumericShowcase() {
return (
<div className="space-y-8">
{/* Basic Number Input */}
<ShowcaseRow
title="Numeric - Number Input"
description="Basic number input with min, max and a label"
code={`
<Numeric.Container mode="number" min={0} max={10} onChange={onChange}>
<div className="flex flex-row items-center space-x-2">
<Numeric.Label>Width</Numeric.Label>
<Numeric.NumberInput />
</div>
</Numeric.Container>
<Numeric.Container mode="number" min={0} max={100} onChange={onChange}>
<Numeric.Label className="text-secondary-foreground text-sm font-bold">Bolder</Numeric.Label>
<Numeric.NumberInput className="w-12" />
</Numeric.Container>
<Numeric.Container mode="number" className="flex flex-row items-center justify-between" onChange={onChange} min={0} value={123465789} max={10000000000000}>
<Numeric.Label className="flex flex-row items-center">
<Icons.Add />
With Icon
</Numeric.Label>
<Numeric.NumberInput className="w-32 text-center" />
</Numeric.Container>`}
>
<div className="bg-popover flex w-[300px] flex-col space-y-4 rounded p-4">
<Numeric.Container
mode="number"
min={0}
max={10}
onChange={val => console.debug('Value changed:', val)}
>
<div className="flex flex-row items-center space-x-2">
<Numeric.Label>Width</Numeric.Label>
<Numeric.NumberInput />
</div>
</Numeric.Container>
<Numeric.Container
mode="number"
className="space-y-1"
min={0}
max={100}
onChange={val => console.debug('Value changed:', val)}
>
<Numeric.Label className="text-muted-foreground text-sm font-bold">
Bolder
</Numeric.Label>
<Numeric.NumberInput className="w-12" />
</Numeric.Container>
<Numeric.Container
mode="number"
className="flex flex-row items-center justify-between"
onChange={val => console.debug('Value changed:', val)}
min={0}
value={123465789}
max={10000000000000}
>
<Numeric.Label className="flex flex-row items-center">
<Icons.Add />
With Icon
</Numeric.Label>
<Numeric.NumberInput className="w-32 text-center" />
</Numeric.Container>
</div>
</ShowcaseRow>
{/* Single Range Slider */}
<ShowcaseRow
title="Numeric - Single Range"
description="Single range slider with optional number input"
code={`
<Numeric.Container mode="singleRange" min={0} max={100} step={1} value={50} onChange={onChange}>
<Numeric.Label>Brightness</Numeric.Label>
<Numeric.SingleRange showNumberInput />
</Numeric.Container>
<Numeric.Container mode="singleRange" min={-50} max={50} step={1} value={0} className="flex flex-row items-center" onChange={onChange}>
<Numeric.Label showValue>Contrast</Numeric.Label>
<Numeric.SingleRange />
</Numeric.Container>
<Numeric.Container mode="singleRange" min={0} max={100} step={1} value={50} className="flex flex-row items-center space-x-2" onChange={onChange}>
<Numeric.Label>Something Else </Numeric.Label>
<Numeric.SingleRange showNumberInput />
</Numeric.Container>`}
>
<div className="bg-popover flex w-[300px] flex-col space-y-4 rounded p-4">
<Numeric.Container
mode="singleRange"
min={0}
max={100}
step={1}
value={50}
onChange={val => console.debug('Value changed:', val)}
>
<Numeric.Label>Brightness</Numeric.Label>
<Numeric.SingleRange showNumberInput />
</Numeric.Container>
<Numeric.Container
mode="singleRange"
min={-50}
max={50}
step={1}
value={0}
className="flex flex-row items-center"
onChange={val => console.debug('Value changed:', val)}
>
<Numeric.Label showValue>Contrast</Numeric.Label>
<Numeric.SingleRange />
</Numeric.Container>
<Numeric.Container
mode="singleRange"
min={0}
max={100}
step={1}
value={50}
className="flex flex-row items-center space-x-2"
onChange={val => console.debug('Value changed:', val)}
>
<Numeric.Label>Something Else </Numeric.Label>
<Numeric.SingleRange showNumberInput />
</Numeric.Container>
</div>
</ShowcaseRow>
{/* Double Range Slider */}
<ShowcaseRow
title="Numeric - Double Range"
description="Double range slider for selecting a range of values"
code={`
<Numeric.Container mode="doubleRange" min={0} max={100} step={1} values={[30, 70]} onChange={onChange}>
<Numeric.Label showValue>Window Width/Level</Numeric.Label>
<Numeric.DoubleRange />
</Numeric.Container>
<Numeric.Container mode="doubleRange" min={0} max={100} step={1} values={[30, 70]}>
<Numeric.Label>Window Width/Level</Numeric.Label>
<Numeric.DoubleRange showNumberInputs />
</Numeric.Container>
<Numeric.Container mode="doubleRange" min={0} max={100} step={1} values={[30, 70]} className="flex flex-row items-center space-x-2" onChange={onChange}>
<Numeric.Label>Inline double slider</Numeric.Label>
<Numeric.DoubleRange />
</Numeric.Container>`}
>
<div className="bg-popover flex w-[300px] flex-col space-y-4 rounded p-4">
<Numeric.Container
mode="doubleRange"
min={0}
max={100}
step={1}
values={[30, 70]}
className="space-y-1"
onChange={vals => console.debug('Values changed:', vals)}
>
<Numeric.Label showValue>Window Width/Level</Numeric.Label>
<Numeric.DoubleRange />
</Numeric.Container>
<Numeric.Container
mode="doubleRange"
min={0}
max={100}
step={1}
values={[30, 70]}
className="space-y-1"
>
<Numeric.Label>Window Width/Level</Numeric.Label>
<Numeric.DoubleRange showNumberInputs />
</Numeric.Container>
<Numeric.Container
mode="doubleRange"
min={0}
max={100}
step={1}
values={[30, 70]}
className="flex flex-row items-center space-x-2"
onChange={vals => console.debug('Values changed:', vals)}
>
<Numeric.Label>Inline double slider</Numeric.Label>
<Numeric.DoubleRange />
</Numeric.Container>
</div>
</ShowcaseRow>
{/* Combined Examples */}
<ShowcaseRow
title="Numeric - Combined Examples"
description="Different modes and configurations working together"
code={`
<Numeric.Container mode="number" min={0} max={10} step={0.1}>
<Numeric.Label>Zoom Factor</Numeric.Label>
<Numeric.NumberInput />
</Numeric.Container>
<Numeric.Container mode="singleRange" min={0} max={360} step={1}>
<Numeric.Label showValue>Rotation</Numeric.Label>
<Numeric.SingleRange showNumberInput />
</Numeric.Container>
<Numeric.Container mode="doubleRange" min={-1000} max={3000} values={[1000, 2000]} step={10}>
<Numeric.Label showValue>CT Window</Numeric.Label>
<Numeric.DoubleRange showNumberInputs />
</Numeric.Container>`}
>
<div className="bg-popover flex w-[300px] flex-col space-y-4 rounded p-4">
<Numeric.Container
mode="number"
min={0}
max={10}
step={0.1}
className="space-y-1"
>
<Numeric.Label>Zoom Factor</Numeric.Label>
<Numeric.NumberInput />
</Numeric.Container>
<Numeric.Container
mode="singleRange"
min={0}
max={360}
step={1}
className="space-y-1"
>
<Numeric.Label showValue>Rotation</Numeric.Label>
<Numeric.SingleRange showNumberInput />
</Numeric.Container>
<Numeric.Container
mode="doubleRange"
min={-1000}
max={3000}
values={[1000, 2000]}
step={10}
className="space-y-1"
>
<Numeric.Label showValue>CT Window</Numeric.Label>
<Numeric.DoubleRange showNumberInputs />
</Numeric.Container>
</div>
</ShowcaseRow>
</div>
);
}
@@ -0,0 +1,33 @@
import React from 'react';
import { ScrollArea } from '../../../../ui-next/src/components/ScrollArea';
import ShowcaseRow from './ShowcaseRow';
/**
* ScrollAreaShowcase component displays ScrollArea variants and examples
*/
export default function ScrollAreaShowcase() {
return (
<ShowcaseRow
title="Scroll Area"
description="Displays a scroll indicator when hovering within an element."
code={`
<ScrollArea className="border-input bg-background h-[150px] w-[350px] rounded-md border p-2 text-sm text-white">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua.
</ScrollArea>
`}
>
<ScrollArea className="border-input bg-background h-[150px] w-[350px] rounded-md border p-2 text-sm text-white">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem
ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua.
</ScrollArea>
</ShowcaseRow>
);
}
@@ -0,0 +1,44 @@
import React from 'react';
import {
Select,
SelectTrigger,
SelectContent,
SelectItem,
SelectValue,
} from '../../../../ui-next/src/components/Select';
import ShowcaseRow from './ShowcaseRow';
/**
* SelectShowcase component displays Select variants and examples
*/
export default function SelectShowcase() {
return (
<ShowcaseRow
title="Select"
description="Switch between a list of options"
code={`
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
`}
>
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
</ShowcaseRow>
);
}
@@ -0,0 +1,49 @@
import React, { useState } from 'react';
import { Button } from '../../../../ui-next/src/components/Button';
import { Icons } from '../../../../ui-next/src/components/Icons';
interface ShowcaseRowProps {
title: string;
description?: string;
children: React.ReactNode;
code: string;
}
/**
* ShowcaseRow component displays a UI component example with title, description,
* and optional code snippet that can be toggled.
*/
export default function ShowcaseRow({ title, description, children, code }: ShowcaseRowProps) {
const [showCode, setShowCode] = useState(false);
return (
<div className="bg-background mb-8 rounded-lg p-6">
<div className="mb-4 flex items-start justify-between">
<div>
<h2 className="text-highlight text-2xl">{title}</h2>
</div>
<Button
className="text-primary"
variant="ghost"
size="sm"
onClick={() => setShowCode(!showCode)}
>
{showCode ? 'Hide Code' : 'Show Code'} <Icons.Code className="ml-2 h-4 w-4" />
</Button>
</div>
<div className="grid grid-cols-1 gap-9 md:grid-cols-3">
<div className="text-base md:col-span-1">
{description && <p className="text-secondary-foreground mt-2">{description}</p>}
</div>
<div className="flex min-h-[120px] items-center md:col-span-2">
<div className="showcase-content">{children}</div>
</div>
</div>
{showCode && (
<pre className="border-input mt-4 overflow-x-auto rounded-md border bg-black p-4 text-sm">
<code>{code}</code>
</pre>
)}
</div>
);
}
@@ -0,0 +1,34 @@
import React from 'react';
import { Slider } from '../../../../ui-next/src/components/Slider';
import ShowcaseRow from './ShowcaseRow';
/**
* SliderShowcase component displays Slider variants and examples
*/
export default function SliderShowcase() {
return (
<ShowcaseRow
title="Slider"
description="Used to select a value in a predefined range."
code={`
<div className="w-40 px-5">
<Slider
className="w-full"
defaultValue={[50]}
max={100}
step={1}
/>
</div>
`}
>
<div className="w-40 px-5">
<Slider
className="w-full"
defaultValue={[50]}
max={100}
step={1}
/>
</div>
</ShowcaseRow>
);
}
@@ -0,0 +1,24 @@
import React from 'react';
import { Switch } from '../../../../ui-next/src/components/Switch';
import { Label } from '../../../../ui-next/src/components/Label';
import ShowcaseRow from './ShowcaseRow';
/**
* SwitchShowcase component displays Switch variants and examples
*/
export default function SwitchShowcase() {
return (
<ShowcaseRow
title="Switch"
description="A toggle Switch is used to change between two different states. Use descriptive labels next to Switches that are understandable before interacting."
code={`
<Switch />
`}
>
<Switch defaultChecked />
<Label className="text-foreground mx-2 w-14 flex-none whitespace-nowrap text-sm">
Sync changes in all viewports
</Label>
</ShowcaseRow>
);
}
@@ -0,0 +1,40 @@
import React from 'react';
import { Tabs, TabsList, TabsTrigger } from '../../../../ui-next/src/components/Tabs';
import { Separator } from '../../../../ui-next/src/components/Separator';
import ShowcaseRow from './ShowcaseRow';
/**
* TabsShowcase component displays Tabs variants and examples
*/
export default function TabsShowcase() {
return (
<ShowcaseRow
title="Tabs"
description="Tabs (or segmented controls) can be used to provide navigation options or allow users to switch between multiple options (e.g., tool settings) "
code={`
<Tabs className="w-[400px]" onValueChange={newValue => console.log(newValue)}>
<TabsList>
<TabsTrigger value="circle">Circle</TabsTrigger>
<Separator orientation="vertical" />
<TabsTrigger value="sphere">Sphere</TabsTrigger>
<Separator orientation="vertical" />
<TabsTrigger value="square">Square</TabsTrigger>
</TabsList>
</Tabs>
`}
>
<Tabs
className="w-[400px]"
onValueChange={newValue => console.log(newValue)}
>
<TabsList>
<TabsTrigger value="circle">Circle</TabsTrigger>
<Separator orientation="vertical" />
<TabsTrigger value="sphere">Sphere</TabsTrigger>
<Separator orientation="vertical" />
<TabsTrigger value="square">Square</TabsTrigger>
</TabsList>
</Tabs>
</ShowcaseRow>
);
}
@@ -0,0 +1,158 @@
import React from 'react';
import { Button } from '../../../../ui-next/src/components/Button';
import { Toaster, toast } from '../../../../ui-next/src/components/Sonner';
import ShowcaseRow from './ShowcaseRow';
/**
* ToastShowcase component displays Toast variants and examples
*/
export default function ToastShowcase() {
// Handlers to trigger different types of toasts
const triggerSuccess = () => {
toast.success('This is a success toast!');
};
const triggerError = () => {
toast.error('This is an error toast!');
};
const triggerInfo = () => {
toast.info('This is an info toast!');
};
const triggerWarning = () => {
toast.warning('This is a warning toast!');
};
// Handler to trigger a toast.promise example
const triggerPromiseToast = () => {
const promise = () =>
new Promise<{ name: string }>(resolve =>
setTimeout(() => resolve({ name: 'Segmentation 1' }), 3000)
);
toast.promise(promise(), {
loading: 'Loading Segmentation...',
success: data => `${data.name} has been added`,
error: 'Error',
});
};
// Handler to trigger a toast with description
const triggerDescriptionToast = () => {
toast.success('Completed', {
description: 'This is a detailed description of the success message.',
});
};
// Handler to trigger a toast with an action button
const triggerActionButtonToast = () => {
toast.info('No active segmentation detected', {
description: 'Create a segmentation before using the Brush',
});
};
// Handler to trigger a toast with a cancel button
const triggerCancelButtonToast = () => {
toast.error('No active segmentation detected', {
description: 'Create a segmentation before using the Brush',
});
};
// Handler to trigger a toast with both action and cancel buttons
const triggerCombinedToast = () => {
toast.warning('Warning!', {
description: 'This is a warning with both action and cancel buttons.',
action: (
<Button
size="sm"
variant="ghost"
onClick={() => console.debug('Retry action clicked')}
>
Retry
</Button>
),
cancel: (
<Button
size="sm"
variant="ghost"
onClick={() => toast.dismiss()}
>
Cancel
</Button>
),
});
};
return (
<ShowcaseRow
title="Toast"
description="A toast notification displays temporary feedback messages to users above the current UI. Notifications stack into one unit after multiple cascading notifications."
code={`
Example code coming soon.
`}
>
Simple message:
<div className="mt-2 mb-7 space-x-2">
<Button
variant="default"
onClick={triggerPromiseToast}
>
Loading & Success Toast
</Button>
<Button
variant="default"
onClick={triggerSuccess}
>
Success Toast
</Button>
<Button
variant="default"
onClick={triggerError}
>
Error Toast
</Button>
<Button
variant="default"
onClick={triggerInfo}
>
Info Toast
</Button>
<Button
variant="default"
onClick={triggerWarning}
>
Warning Toast
</Button>
</div>
Message with details:
<div className="mt-2 space-x-2">
<Button
variant="default"
onClick={triggerDescriptionToast}
>
Success Toast
</Button>
<Button
variant="default"
onClick={triggerActionButtonToast}
>
Info Toast
</Button>
<Button
variant="default"
onClick={triggerCancelButtonToast}
>
Error Toast
</Button>
<Button
variant="default"
onClick={triggerCombinedToast}
>
Toast with Buttons
</Button>
</div>
<Toaster />
</ShowcaseRow>
);
}
@@ -0,0 +1,90 @@
import React from 'react';
import {
ToolButtonList,
ToolButton,
ToolButtonListDefault,
ToolButtonListDropDown,
ToolButtonListItem,
ToolButtonListDivider,
} from '../../../../ui-next/src/components/ToolButton';
import { TooltipProvider } from '../../../../ui-next/src/components/Tooltip';
import ShowcaseRow from './ShowcaseRow';
/**
* ToolButtonListShowcase component displays ToolButtonList variants and examples
*/
export default function ToolButtonListShowcase() {
return (
<ShowcaseRow
title="ToolButtonList"
description="A compound component that combines a primary tool button with a dropdown menu of additional related tools"
code={`
// Example usage:
<ToolButtonList>
<ToolButtonListDefault>
<ToolButton
id="Length"
icon="tool-length"
label="Length"
tooltip="Length Tool"
onInteraction={({ itemId }) => console.debug(\`Clicked \${itemId}\`)}
/>
</ToolButtonListDefault>
<ToolButtonListDivider />
<ToolButtonListDropDown>
<ToolButtonListItem
icon="tool-length"
onSelect={() => console.debug('Selected Length')}
>
<span className="pl-1">Length</span>
</ToolButtonListItem>
<ToolButtonListItem
icon="tool-bidirectional"
onSelect={() => console.debug('Selected Bidirectional')}
>
<span className="pl-1">Bidirectional</span>
</ToolButtonListItem>
</ToolButtonListDropDown>
</ToolButtonList>
`}
>
<div className="bg-popover flex h-11 w-[450px] items-center justify-start rounded p-2">
<TooltipProvider>
<ToolButtonList>
<ToolButtonListDefault>
<ToolButton
id="Length"
icon="ToolLength"
label="Length"
tooltip="Length Tool"
onInteraction={({ itemId }) => console.debug(`Clicked ${itemId}`)}
/>
</ToolButtonListDefault>
<ToolButtonListDivider />
<ToolButtonListDropDown>
<ToolButtonListItem
icon="ToolLength"
onSelect={() => console.debug('Selected Length')}
>
<span className="pl-1">Length</span>
</ToolButtonListItem>
<ToolButtonListItem
icon="ToolBidirectional"
onSelect={() => console.debug('Selected Bidirectional')}
>
<span className="pl-1">Bidirectional</span>
</ToolButtonListItem>
<ToolButtonListItem
icon="ToolAnnotate"
onSelect={() => console.debug('Selected Annotation')}
>
<span className="pl-1">Annotation</span>
</ToolButtonListItem>
</ToolButtonListDropDown>
</ToolButtonList>
</TooltipProvider>
</div>
</ShowcaseRow>
);
}
@@ -0,0 +1,54 @@
import React from 'react';
import { TooltipProvider } from '../../../../ui-next/src/components/Tooltip';
import ToolButton from '../../../../ui-next/src/components/ToolButton/ToolButton';
import ShowcaseRow from './ShowcaseRow';
/**
* ToolButtonShowcase component displays ToolButton variants and examples
*/
export default function ToolButtonShowcase() {
return (
<ShowcaseRow
title="ToolButton"
description="Used to activate tools or perform single actions"
code={`
// Example usage:
<ToolButton
id="Zoom"
icon="zoom" // must exist in your Icons or fallback to 'MissingIcon'
label="Zoom"
tooltip="Zoom Tool"
isActive={false}
onInteraction={({ itemId }) => console.debug(\`Clicked \${itemId}\`)}
/>
`}
>
<div className="bg-popover flex h-11 w-[450px] items-center justify-center rounded">
<TooltipProvider>
<ToolButton
id="Zoom"
icon="ToolZoom"
isActive={true}
label="Zoom"
tooltip="Zoom"
onInteraction={({ itemId }) => console.debug(`Clicked ${itemId}`)}
/>
<ToolButton
id="Zoom"
icon="ToolMove"
label="Pan"
tooltip="Pan"
onInteraction={({ itemId }) => console.debug(`Clicked ${itemId}`)}
/>
<ToolButton
id="Zoom"
icon="ToolWindowLevel"
label="Window Level"
tooltip="Window Level"
onInteraction={({ itemId }) => console.debug(`Clicked ${itemId}`)}
/>
</TooltipProvider>
</div>
</ShowcaseRow>
);
}