Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-toggle": "^1.1.0",
"@vitejs/plugin-react": "^4.3.1",
"class-variance-authority": "^0.7.0",
Expand Down
46 changes: 46 additions & 0 deletions src/components/ui/toast/toast.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Meta, StoryObj } from "@storybook/react";
import { Toast, ToastProvider, ToastViewport } from "./toast";
import React from "react";

// Meta configuration for Toast
const meta = {
title: "Example/Toast",
component: Toast,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
} satisfies Meta<typeof Toast>;

export default meta;

const Template: StoryObj<typeof Toast> = {
render: (args) => (
<ToastProvider>
<Toast {...args}>
<div className="grid gap-1">
{args.title && <div>{args.title}</div>}
</div>
</Toast>
<ToastViewport />
</ToastProvider>
),
};

// Default Toast
export const Default = {
...Template,
args: {
title: "Default Toast",
variant: "default",
},
};

// Destructive Toast
export const Destructive = {
...Template,
args: {
title: "Destructive Toast",
variant: "destructive",
},
};
135 changes: 135 additions & 0 deletions src/components/ui/toast/toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import * as React from "react";
import * as ToastPrimitives from "@radix-ui/react-toast";
import { cva, type VariantProps } from "class-variance-authority";
import { CheckCircle2, X, XCircle } from "lucide-react";

import { cn } from "@/lib/utils";

const ToastProvider = ToastPrimitives.Provider;

const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className
)}
{...props}
/>
));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;

const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
{
variants: {
variant: {
default: "border bg-background text-foreground",
destructive:
"destructive group border-destructive bg-destructive text-destructive-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
);

const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
>
<div className="flex flex-row gap-6">
{variant === "destructive" ? (
<XCircle className="h-6 w-6" />
) : (
<CheckCircle2 className="h-6 w-6" />
)}

{props.children}
</div>
</ToastPrimitives.Root>
);
});
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className
)}
{...props}
/>
));
ToastAction.displayName = ToastPrimitives.Action.displayName;

const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
className
)}
toast-close=""
{...props}
>
<X className="h-4 w-4" />
</ToastPrimitives.Close>
));
ToastClose.displayName = ToastPrimitives.Close.displayName;

const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn("text-sm font-semibold", className)}
{...props}
/>
));
ToastTitle.displayName = ToastPrimitives.Title.displayName;

const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn("text-sm opacity-90", className)}
{...props}
/>
));
ToastDescription.displayName = ToastPrimitives.Description.displayName;

type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;

type ToastActionElement = React.ReactElement<typeof ToastAction>;

export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
};
55 changes: 55 additions & 0 deletions src/components/ui/toaster/toaster.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Meta, StoryObj } from "@storybook/react";
import { Toaster } from "./toaster";
import React from "react";
import { toast } from "../../../hooks/use-toast";

// Meta configuration for Toaster
const meta = {
title: "Example/Toaster",
component: Toaster,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
} satisfies Meta<typeof Toaster>;

export default meta;

const Template: StoryObj<typeof Toaster> = {
render: (args) => (
<div>
<button
onClick={() =>
toast({
title: args.title,
description: args.description,
variant: args.variant,
})
}
>
Show Toast
</button>
<Toaster />
</div>
),
};

// Default Toaster
export const Default = {
...Template,
args: {
title: "Default Toast",
description: "This is a default toast",
variant: "default",
},
};

// Destructive Toaster
export const Destructive = {
...Template,
args: {
title: "Destructive Toast",
description: "This is a destructive toast",
variant: "destructive",
},
};
Loading