ui(component): Added new Toast (Sonner) component (#4412)

This commit is contained in:
Dan Rukas authored and GitHub committed 2024-10-17 09:30:42 -04:00
1 parent 43f54aa49c
commit e0fca8c88c
16 files changed
+1212 -247

No files matched your search

+2
View File
@@ -176,12 +176,14 @@ module.exports = {
target: '_blank',
position: 'left',
},
/*
{
to: '/playground',
label: 'UI Playground',
position: 'left',
className: 'new-badge',
},
*/
{
to: '/help',
//activeBaseRegex: '(^/help$)|(/help)',
+8 -2
View File
@@ -10,16 +10,22 @@ export default function Patterns() {
<h1>Patterns</h1>
<button
className="bg-slate-400"
onClick={() => history.push('/patterns/segmentation-panel')}
onClick={() => history.push('/patterns/patterns-segmentation')}
>
{'Segmentation Panel'}
</button>
<button
className="bg-slate-400"
onClick={() => history.push('/patterns/measurement-panel')}
onClick={() => history.push('/patterns/patterns-measurements')}
>
{'Measurements Panel'}
</button>
<button
className="bg-slate-400"
onClick={() => history.push('/patterns/patterns-tmtv')}
>
{'tmtv'}
</button>
</Layout>
);
}
@@ -10,6 +10,18 @@ import {
SelectItem,
} from '../../../../ui-next/src/components/Select';
import { Icons } from '../../../../ui-next/src/components/Icons';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuLabel,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
DropdownMenuPortal,
} from '../../../../ui-next/src/components/DropdownMenu';
import {
Accordion,
AccordionItem,
@@ -85,6 +97,50 @@ export default function SegmentationPanel() {
<div className="mb-0">
{/* Header Controls */}
<div className="bg-muted flex h-10 w-full items-center space-x-1 rounded-t px-1.5">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
>
<Icons.More className="h-6 w-6" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>
<Icons.Add className="text-foreground" />
<span className="pl-2">Create New Segmentation</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuLabel>Manage Current Segmentation</DropdownMenuLabel>
<DropdownMenuItem>
<Icons.Series className="text-foreground" />
<span className="pl-2">Remove from Viewport</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Rename className="text-foreground" />
<span className="pl-2">Rename</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Icons.Export className="text-foreground" />
<span className="pl-2">Export & Download</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>Export DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM RTSTRUCT</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icons.Delete className="text-red-600" />
<span className="pl-2 text-red-600">Delete</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Select>
<SelectTrigger className="w-full overflow-hidden">
<SelectValue placeholder="Segmentation 1" />
@@ -0,0 +1,43 @@
import React, { useState } from 'react';
import { DataRow } from '../../../../ui-next/src/components/DataRow';
import { Button } from '../../../../ui-next/src/components/Button';
import {
Select,
SelectValue,
SelectTrigger,
SelectContent,
SelectItem,
} from '../../../../ui-next/src/components/Select';
import { Icons } from '../../../../ui-next/src/components/Icons';
import {
Accordion,
AccordionItem,
AccordionTrigger,
AccordionContent,
} from '../../../../ui-next/src/components/Accordion';
import { Slider } from '../../../../ui-next/src/components/Slider';
import { Switch } from '../../../../ui-next/src/components/Switch';
import { Label } from '../../../../ui-next/src/components/Label';
import { Input } from '../../../../ui-next/src/components/Input';
import { Tabs, TabsList, TabsTrigger } from '../../../../ui-next/src/components/Tabs';
import { actionOptionsMap, dataList } from '../../../../ui-next/assets/data';
interface DataItem {
id: number;
title: string;
description: string;
optionalField?: string;
colorHex?: string;
details?: string;
series?: string;
}
interface ListGroup {
type: string;
items: DataItem[];
}
export default function SplitPanel() {
return <div>hellosssssss</div>;
}
@@ -0,0 +1,403 @@
import React, { useState } from 'react';
import { Button } from '../../../../ui-next/src/components/Button';
import {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
} from '../../../../ui-next/src/components/Select';
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuLabel,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
DropdownMenuPortal,
} from '../../../../ui-next/src/components/DropdownMenu';
import { Icons } from '../../../../ui-next/src/components/Icons/Icons';
import { DataRow } from '../../../../ui-next/src/components/DataRow';
import { actionOptionsMap, dataList } from '../../../../ui-next/assets/data';
import {
Accordion,
AccordionItem,
AccordionTrigger,
AccordionContent,
} from '../../../../ui-next/src/components/Accordion/Accordion';
import { Slider } from '../../../../ui-next/src/components/Slider';
import { Switch } from '../../../../ui-next/src/components/Switch';
import { Label } from '../../../../ui-next/src/components/Label';
import { Input } from '../../../../ui-next/src/components/Input';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../../../ui-next/src/components/Tabs';
import { ChevronDownIcon } from '@radix-ui/react-icons';
interface DataItem {
id: number;
title: string;
description: string;
optionalField?: string;
colorHex?: string;
details?: string;
series?: string;
}
interface ListGroup {
type: string;
items: DataItem[];
}
export default function TMTVPatterns() {
const [selectedRowId, setSelectedRowId] = useState<string | null>(null);
const [selectedTab, setSelectedTab] = useState<string>('Fill & Outline');
const handleAction = (id: string, action: string) => {
console.log(`Action "${action}" triggered for item with id: ${id}`);
// Implement actual action logic here
};
// Handle row selection
const handleRowSelect = (id: string) => {
setSelectedRowId(prevSelectedId => (prevSelectedId === id ? null : id));
};
// Find the "TMTV2" group
const tmv2Group = dataList.find((listGroup: ListGroup) => listGroup.type === 'TMTV2');
// Find the "TMTV1" group
const tmvGroup = dataList.find((listGroup: ListGroup) => listGroup.type === 'TMTV1');
// Check if both groups exist
if (!tmv2Group) {
return <div className="text-red-500">TMTV2 data not found.</div>;
}
if (!tmvGroup) {
return <div className="text-red-500">TMTV1 data not found.</div>;
}
return (
<div className="px-auto my-4 flex h-full w-full justify-center bg-black py-6">
<div className="w-64 space-y-0">
<Accordion
type="multiple"
defaultValue={['segmentation-tools', 'segmentation-list', 'tmv1-group', 'tmv2-group']}
collapsible
>
{/* Segmentation Tools */}
<AccordionItem value="segmentation-tools">
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
<span>Segmentation Tools</span>
</AccordionTrigger>
<AccordionContent>
<div className="bg-muted mb-0.5 h-32 rounded-b pb-3"></div>
</AccordionContent>
</AccordionItem>
{/* Segmentation List */}
<AccordionItem value="segmentation-list">
<AccordionTrigger className="bg-popover hover:bg-accent text-muted-foreground my-0.5 flex h-7 w-full items-center justify-between rounded py-2 pr-1 pl-2 font-normal">
<span>Segmentation List</span>
</AccordionTrigger>
<AccordionContent>
{/* Appearance Settings */}
<AccordionItem value="segmentation-display">
<AccordionTrigger className="bg-muted hover:bg-accent mt-0.5 flex h-7 w-full items-center justify-between rounded-b pr-1 pl-2 font-normal text-white">
<div className="flex space-x-2">
<Icons.Controls className="text-primary" />
<span className="text-primary pr-1">Appearance Settings</span>
</div>
</AccordionTrigger>
<AccordionContent>
<div className="bg-muted mb-0.5 space-y-2 rounded-b px-px pt-0.5 pb-3">
<div className="mx-1 mb-2.5 mt-1 flex items-center justify-between space-x-4">
{/* Display Label with Selected Tab */}
<div className="text-muted-foreground text-xs">Show: {selectedTab}</div>
{/* Tabs Controls */}
<Tabs
value={selectedTab}
onValueChange={setSelectedTab}
>
<TabsList>
<TabsTrigger value="Fill & Outline">
<Icons.DisplayFillAndOutline className="text-primary" />
</TabsTrigger>
<TabsTrigger value="Outline Only">
<Icons.DisplayOutlineOnly className="text-primary" />
</TabsTrigger>
<TabsTrigger value="Fill Only">
<Icons.DisplayFillOnly className="text-primary" />
</TabsTrigger>
</TabsList>
</Tabs>
</div>
{/* Opacity Slider */}
<div className="my-2 flex items-center ">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Opacity
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[85]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="85"
/>
</div>
{/* Border Slider */}
<div className="my-2 flex items-center">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Border
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[10]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="2"
/>
</div>
{/* Sync Changes Switch */}
<div className="my-2 flex items-center pl-1 pb-1">
<Switch defaultChecked />
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
Sync changes in all viewports
</Label>
</div>
<div className="border-input w-full border"></div>
{/* Display Inactive Segmentations Switch */}
<div className="my-2 flex items-center pl-1">
<Switch defaultChecked />
<Label className="text-muted-foreground mx-2 w-14 flex-none whitespace-nowrap text-xs">
Display inactive segmentations
</Label>
</div>
{/* Additional Opacity Slider */}
<div className="my-2 flex items-center ">
<Label className="text-muted-foreground mx-1 w-14 flex-none whitespace-nowrap text-xs">
Opacity
</Label>
<Slider
className="mx-1 flex-1"
defaultValue={[65]}
max={100}
step={1}
/>
<Input
className="mx-1 w-10 flex-none"
placeholder="65"
/>
</div>
</div>
</AccordionContent>
</AccordionItem>
{/* TMTV1 Group */}
<AccordionItem value="tmv1-group">
<AccordionTrigger className="hover:bg-popover mr-0 flex h-8 w-full items-center pl-0 pr-1">
<div className="text-foreground border-input flex h-8 w-full items-center justify-between border-t-2">
{/* Left Group: DropdownMenu and TMTV1 Label */}
<div className="flex items-center space-x-1">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="ml-1"
>
<Icons.More className="h-6 w-6" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>
<Icons.Add className="text-foreground" />
<span className="pl-2">Add Segment</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icons.Series className="text-foreground" />
<span className="pl-2">Remove from Viewport</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Rename className="text-foreground" />
<span className="pl-2">Rename</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Hide className="text-foreground" />
<span className="pl-2">Hide or Show all Segments</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Icons.Export className="text-foreground" />
<span className="pl-2">Export & Download</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>Export DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM RTSTRUCT</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icons.Delete className="text-red-600" />
<span className="pl-2 text-red-600">Delete</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<div className="pl-1.5">TMTV1 Segmentation</div>
</div>
<div className="mr-1 flex items-center">
<Button
variant="ghost"
size="icon"
>
<Icons.Info className="h-6 w-6" />
</Button>
</div>
</div>
</AccordionTrigger>
<AccordionContent>
{/* Data Rows for TMTV1 */}
<div className="space-y-px">
{tmvGroup.items.map((item, index) => {
const compositeId = `${tmvGroup.type}-${item.id}-panel`; // Ensure unique composite ID
return (
<DataRow
key={`panel-${compositeId}`} // Prefix to ensure uniqueness
number={index + 1}
title={item.title}
description={item.description}
optionalField={item.optionalField}
colorHex={item.colorHex}
details={item.details}
series={item.series}
actionOptions={actionOptionsMap[tmvGroup.type] || ['Action']}
onAction={(action: string) => handleAction(compositeId, action)}
isSelected={selectedRowId === compositeId}
onSelect={() => handleRowSelect(compositeId)}
/>
);
})}
</div>
</AccordionContent>
</AccordionItem>
{/* TMTV2 Group */}
<AccordionItem value="tmv2-group">
<AccordionTrigger className="hover:bg-popover mr-0 flex h-8 w-full items-center pl-0 pr-1">
<div className="text-foreground border-input flex h-8 w-full items-center justify-between border-t-2">
<div className="flex items-center space-x-1">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="ml-1"
>
<Icons.More className="h-6 w-6" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem>
<Icons.Add className="text-foreground" />
<span className="pl-2">Add Segment</span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icons.Series className="text-foreground" />
<span className="pl-2">Remove from Viewport</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Rename className="text-foreground" />
<span className="pl-2">Rename</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Hide className="text-foreground" />
<span className="pl-2">Hide or Show all Segments</span>
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Icons.Export className="text-foreground" />
<span className="pl-2">Export & Download</span>
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
<DropdownMenuItem>Export DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM SEG</DropdownMenuItem>
<DropdownMenuItem>Download DICOM RTSTRUCT</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Icons.Delete className="text-red-600" />
<span className="pl-2 text-red-600">Delete</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<div className="pl-1.5">TMTV2 Segmentation</div>
</div>
<div className="mr-1 flex items-center ">
<Button
variant="ghost"
size="icon"
>
<Icons.Info className="h-6 w-6" />
</Button>
</div>
</div>
</AccordionTrigger>
<AccordionContent>
{/* Data Rows for TMTV2 */}
<div className="space-y-px">
{tmv2Group.items.map((item, index) => {
const compositeId = `${tmv2Group.type}-${item.id}-panel`; // Ensure unique composite ID
return (
<DataRow
key={`panel-${compositeId}`} // Prefix to ensure uniqueness
number={index + 1}
title={item.title}
description={item.description}
optionalField={item.optionalField}
colorHex={item.colorHex}
details={item.details}
series={item.series}
actionOptions={actionOptionsMap[tmv2Group.type] || ['Action']}
onAction={(action: string) => handleAction(compositeId, action)}
isSelected={selectedRowId === compositeId}
onSelect={() => handleRowSelect(compositeId)}
/>
);
})}
</div>
</AccordionContent>
</AccordionItem>
{/* Footer or Additional Information */}
<div className="bg-popover text-foreground flex h-8 items-center justify-between pl-9 pr-3 text-sm font-semibold">
<span>TMTV</span>
<span>21.555 mL</span>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</div>
);
}
+177
View File
@@ -25,6 +25,7 @@ import {
DropdownMenuItem,
} from '../../../ui-next/src/components/DropdownMenu';
import { Icons } from '../../../ui-next/src/components/Icons';
import { Toaster, toast } from '../../../ui-next/src/components/Sonner';
interface ShowcaseRowProps {
title: string;
@@ -34,6 +35,106 @@ interface ShowcaseRowProps {
}
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('Success heading', {
description: 'This is a detailed description of the success message.',
});
};
// Handler to trigger a toast with an action button
const triggerActionButtonToast = () => {
toast.info('Info heading', {
description: 'This is an info message with an action button.',
action: (
<Button
size="sm"
variant="ghost"
onClick={() => alert('Action button clicked')}
>
Undo
</Button>
),
});
};
// Handler to trigger a toast with a cancel button
const triggerCancelButtonToast = () => {
toast.error('Error!', {
description: 'This is an error message with a cancel button.',
cancel: (
<Button
size="sm"
variant="ghost"
onClick={() => toast.dismiss()}
>
Dismiss
</Button>
),
});
};
// 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...');
};
return (
<div className="bg-background text-foreground h-full">
<div className="mx-auto my-4 max-w-6xl py-6">
@@ -488,6 +589,82 @@ export default function ComponentShowcase() {
do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</ScrollArea>
</ShowcaseRow>
<ShowcaseRow
title="sonner 1"
description="sonner1"
code={`sdf`}
>
{/* Toast Examples Section */}
<div className="space-x-2">
<Button
variant="default"
onClick={triggerSuccess}
>
Show Success Toast
</Button>
<Button
variant="default"
onClick={triggerError}
>
Show Error Toast
</Button>
<Button
variant="default"
onClick={triggerInfo}
>
Show Info Toast
</Button>
<Button
variant="default"
onClick={triggerWarning}
>
Show Warning Toast
</Button>
<Button
variant="default"
onClick={triggerPromiseToast}
>
Loading & Success Toast
</Button>
</div>
</ShowcaseRow>
<ShowcaseRow
title="sonner 2"
description="sonner2"
code={`sdf`}
>
<Button
variant="default"
onClick={triggerDescriptionToast}
>
Show Toast with Description
</Button>
<Button
variant="default"
onClick={triggerActionButtonToast}
>
Show Toast with Action Button
</Button>
<Button
variant="default"
onClick={triggerCancelButtonToast}
>
Show Toast with Cancel Button
</Button>
<Button
variant="default"
onClick={triggerCombinedToast}
>
Show Toast with Action & Cancel Buttons
</Button>
{/* Render the Toaster component */}
<Toaster />
</ShowcaseRow>
</div>
</div>
);