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>
);
+52
View File
@@ -205,6 +205,58 @@ const dataList = [
},
],
},
{
type: 'TMTV1',
items: [
{
id: 29,
title: 'Segment 1',
colorHex: '#FF6F61',
description: 'Description for Segmentation One.',
details: 'SUV Peak: NaN\nVolume: 21.56mm³',
},
{
id: 30,
title: 'Segment 2',
colorHex: '#00CED1',
description: 'Description for Segmentation Two.',
details: 'SUV Peak: NaN\nVolume: 21.56mm³',
},
{
id: 31,
title: 'Segment 3',
colorHex: '#88B04B',
description: 'Description for Segmentation One.',
details: 'SUV Peak: NaN\nVolume: 21.56mm³',
},
],
},
{
type: 'TMTV2',
items: [
{
id: 32,
title: 'Segment A',
colorHex: '#FF6F61',
description: 'Description for Segmentation One.',
details: 'SUV Peak: NaN\nVolume: 21.56mm³',
},
{
id: 33,
title: 'Segment B',
colorHex: '#00CED1',
description: 'Description for Segmentation Two.',
details: 'SUV Peak: NaN\nVolume: 21.56mm³',
},
{
id: 34,
title: 'Segment C',
colorHex: '#88B04B',
description: 'Description for Segmentation One.',
details: 'SUV Peak: NaN\nVolume: 21.56mm³',
},
],
},
];
export { actionOptionsMap, dataList };
+1 -1
View File
@@ -54,7 +54,7 @@
"react-day-picker": "^8.10.1",
"react-shepherd": "6.1.1",
"shepherd.js": "13.0.3",
"sonner": "^1.4.41",
"sonner": "^1.5.0",
"tailwind-merge": "^2.3.0",
"tailwindcss": "3.2.4",
"tailwindcss-animate": "^1.0.7"
@@ -169,6 +169,10 @@ const DataRow: React.FC<DataRowProps> = ({
<Icons.Lock className="text-foreground" />
<span className="pl-2">Lock</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.ColorChange className="text-foreground" />
<span className="pl-2">Change Color</span>
</DropdownMenuItem>
<DropdownMenuItem>
<Icons.Delete className="text-foreground" />
<span className="pl-2">Delete</span>
@@ -122,6 +122,101 @@ export const Icons = {
</g>
</svg>
),
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>
),
Controls: (props: IconProps) => (
<svg
width="18px"
@@ -1084,6 +1179,97 @@ export const Icons = {
</g>
</svg>
),
StatusError: (props: IconProps) => (
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
>
<g
id="StatusAlert"
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="8"
></circle>
<g
id="Group"
transform="translate(11.5, 8)"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<line
x1="0.502969489"
y1="5"
x2="0.502969489"
y2="5.55111512e-16"
id="Path"
></line>
<path
d="M0.494019489,7.75 C0.427967985,7.75102315 0.365128544,7.77867594 0.319754003,7.82668634 C0.274379462,7.87469675 0.250315262,7.93899595 0.253019489,8.005 C0.257853669,8.14136674 0.369567839,8.24954844 0.506019489,8.25 L0.506019489,8.25 C0.57198073,8.2487037 0.634656968,8.22096694 0.679972815,8.17301863 C0.725288662,8.12507033 0.749445908,8.06092934 0.747019489,7.995 C0.742888429,7.86182395 0.636177529,7.75467571 0.503019489,7.75 L0.498019489,7.75"
id="Path"
></path>
</g>
</g>
</svg>
),
StatusSuccess: (props: IconProps) => (
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
{...props}
>
<g
id="StatusSuccess"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<rect
id="Rectangle"
x="0"
y="0"
width="24"
height="24"
></rect>
<polyline
id="Path"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
points="16.173913 8.52173913 11.3043478 15.1304348 7.82608696 12.3478261"
></polyline>
<circle
id="Oval"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
cx="12"
cy="12"
r="8"
></circle>
</g>
</svg>
),
StatusTracking: (props: IconProps) => (
<svg
width="24px"
@@ -1290,6 +1476,50 @@ export const Icons = {
</g>
</svg>
),
LoadingSpinner: (props: IconProps) => (
<svg
role="status"
aria-label="Loading"
className={`h-5 w-5 animate-spin ${props.className}`}
width="24px"
height="24px"
viewBox="0 0 24 24"
{...props}
>
<g
id="LoadingSpinner"
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(1, 1)"
fillRule="nonzero"
>
<path
d="M11,0 C17.0751322,0 22,4.92486775 22,11 C22,17.0751322 17.0751322,22 11,22 C4.92486775,22 0,17.0751322 0,11 C0,4.92486775 4.92486775,0 11,0 Z M11,2 C6.02943725,2 2,6.02943725 2,11 C2,15.9705627 6.02943725,20 11,20 C15.9705627,20 20,15.9705627 20,11 C20,6.02943725 15.9705627,2 11,2 Z"
id="Oval"
fill="#348CFD"
opacity="0.25"
></path>
<path
d="M19.0287175,4.94590384 C19.5005019,4.65878387 20.1157155,4.80848402 20.4028355,5.28026847 C21.4419642,6.98772474 22,8.94986784 22,10.9915479 C22,17.0666801 17.0751322,21.9915479 11,21.9915479 C10.4477153,21.9915479 10,21.5438326 10,20.9915479 C10,20.4392631 10.4477153,19.9915479 11,19.9915479 C15.9705627,19.9915479 20,15.9621106 20,10.9915479 C20,9.31924154 19.5441371,7.7163545 18.6943528,6.32002184 C18.4072329,5.84823739 18.556933,5.2330238 19.0287175,4.94590384 Z"
id="Oval"
fill="#5ACCE6"
></path>
</g>
</g>
</svg>
),
NavigationPanelReveal: (props: IconProps) => (
<svg
width="17"
@@ -1311,6 +1541,48 @@ export const Icons = {
</svg>
),
MissingIcon: (props: IconProps) => <div>Missing icon</div>,
Series: (props: IconProps) => (
<svg
width="24px"
height="24px"
viewBox="0 0 24 24"
{...props}
>
<g
id="Series"
stroke="none"
strokeWidth="1"
fill="none"
fillRule="evenodd"
>
<rect
id="Rectangle"
x="0"
y="0"
width="24"
height="24"
></rect>
<path
d="M8.17391304,15.826087 L4.69565217,15.826087 C4.31145409,15.826087 4,15.5146329 4,15.1304348 L4,4.69565217 C4,4.31145409 4.31145409,4 4.69565217,4 L15.1304348,4 C15.5146329,4 15.826087,4.31145409 15.826087,4.69565217 L15.826087,8.17391304"
id="Path"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<rect
id="Rectangle"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
x="8.17391304"
y="8.17391304"
width="11.826087"
height="11.826087"
rx="1"
></rect>
</g>
</svg>
),
Settings: (props: IconProps) => (
<svg
width="15.7826087px"
@@ -0,0 +1,38 @@
import React from 'react';
import { Toaster as Sonner } from 'sonner';
import { Icons } from '../Icons';
type ToasterProps = React.ComponentProps<typeof Sonner>;
const Toaster = ({ ...props }: ToasterProps) => {
return (
<Sonner
className="toaster group"
loadingIcon={<Icons.LoadingSpinner />}
icons={{
warning: <Icons.StatusWarning />,
info: <Icons.Info className="text-secondary-foreground" />,
success: <Icons.StatusSuccess />,
error: <Icons.StatusError />,
}}
theme="dark"
richColors="true"
toastOptions={{
classNames: {
toast:
'group toast group-[.toaster]:bg-popover group-[.toaster]:text-foreground group-[.toaster]:border-background group-[.toaster]:shadow-lg',
info: 'group-[.toast] group-[.toaster]:!bg-popover group-[.toaster]:!text-foreground group-[.toaster]:!border-background',
error: 'group-[.toaster]:!border-background',
success: 'group-[.toaster]:!border-background',
warning: 'group-[.toaster]:!border-background',
description: 'group-[.toast]:text-secondary-foreground',
actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
},
}}
{...props}
/>
);
};
export { Toaster };
@@ -0,0 +1,4 @@
import { Toaster } from './Sonner';
import { toast } from 'sonner';
export { Toaster, toast };
+2
View File
@@ -71,6 +71,7 @@ import {
import { Onboarding } from './Onboarding';
import { BackgroundColorSelect } from './BackgroundColorSelect';
import { DataRow } from './DataRow';
import { Toaster } from './Sonner';
export {
Button,
@@ -157,4 +158,5 @@ export {
SelectItem,
SelectTrigger,
SelectValue,
Toaster,
};
+2 -2
View File
@@ -146,7 +146,7 @@
--muted-foreground: 220 8.9% 46.1%;
--accent: 220 14.3% 95.9%;
--accent-foreground: 220.9 39.3% 11%;
--destructive: 0 84.2% 60.2%;
--destructive: 360 84.2% 60.2%;
--destructive-foreground: 210 20% 98%;
--border: 220 13% 91%;
--input: 220 13% 91%;
@@ -174,7 +174,7 @@
--muted-foreground: 217.9 10.6% 64.9%;
--accent: 215 27.9% 16.9%;
--accent-foreground: 210 20% 98%;
--destructive: 0 62.8% 30.6%;
--destructive: 360 84.2% 60.2%;
--destructive-foreground: 210 20% 98%;
--border: 215 27.9% 16.9%;
--input: 215 27.9% 16.9%;