mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2026-10-15 11:55:43 -04:00
chore: replace sonner with base-ui toast primitive
This commit is contained in:
11 files changed
+274
-144
No files matched your search
@@ -114,7 +114,7 @@ export const submitForm = async (state: ActionState, payload: FormData): Promise
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
**Client-side:** Use `toast` from sonner for feedback, `useTransition` for pending states.
|
**Client-side:** Use `toast` from `@/components/ui/toast` for feedback, `useTransition` for pending states.
|
||||||
|
|
||||||
### Database (Drizzle ORM)
|
### Database (Drizzle ORM)
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { Analytics as VercelAnalytics } from "@vercel/analytics/next";
|
|
||||||
import { SpeedInsights as VercelSpeedInsights } from "@vercel/speed-insights/next";
|
|
||||||
|
|
||||||
const Analytics = () => (
|
|
||||||
<>
|
|
||||||
<VercelAnalytics />
|
|
||||||
<VercelSpeedInsights />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
export { Analytics };
|
|
||||||
+5
-5
@@ -1,19 +1,19 @@
|
|||||||
|
import { Analytics } from "@vercel/analytics/next";
|
||||||
import { JsonLd } from "react-schemaorg";
|
import { JsonLd } from "react-schemaorg";
|
||||||
import type { Person, WebSite } from "schema-dts";
|
import type { Person, WebSite } from "schema-dts";
|
||||||
|
|
||||||
import { Analytics } from "@/app/analytics";
|
|
||||||
import { Footer } from "@/components/layout/footer";
|
import { Footer } from "@/components/layout/footer";
|
||||||
import { Header } from "@/components/layout/header";
|
import { Header } from "@/components/layout/header";
|
||||||
import { Providers } from "@/components/providers";
|
import { Providers } from "@/components/providers";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/toast";
|
||||||
import authorConfig from "@/lib/config/author";
|
import authorConfig from "@/lib/config/author";
|
||||||
import siteConfig from "@/lib/config/site";
|
import siteConfig from "@/lib/config/site";
|
||||||
import { Inter, JetBrainsMono } from "@/lib/fonts";
|
import { Inter, JetBrainsMono } from "@/lib/fonts";
|
||||||
|
|
||||||
import "./globals.css";
|
|
||||||
import { defaultMetadata } from "@/lib/metadata";
|
import { defaultMetadata } from "@/lib/metadata";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
export const metadata = defaultMetadata;
|
export const metadata = defaultMetadata;
|
||||||
|
|
||||||
const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => (
|
const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => (
|
||||||
@@ -67,7 +67,7 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => (
|
|||||||
<main className="mt-4 w-full">{children}</main>
|
<main className="mt-4 w-full">{children}</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
<Toaster position="bottom-center" hotkey={[]} />
|
<Toaster />
|
||||||
</Providers>
|
</Providers>
|
||||||
<Analytics />
|
<Analytics />
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { IconDots, IconEdit, IconMessageReply, IconTrash } from "@tabler/icons-react";
|
import { IconDots, IconEdit, IconMessageReply, IconTrash } from "@tabler/icons-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
@@ -22,6 +21,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
|
import { toast } from "@/components/ui/toast";
|
||||||
import { useSession } from "@/lib/auth-client";
|
import { useSession } from "@/lib/auth-client";
|
||||||
import { type CommentWithUser, deleteComment } from "@/lib/server/comments";
|
import { type CommentWithUser, deleteComment } from "@/lib/server/comments";
|
||||||
|
|
||||||
@@ -46,11 +46,17 @@ const CommentActions = ({ comment }: { comment: CommentWithUser }) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await deleteComment(comment.id);
|
await deleteComment(comment.id);
|
||||||
toast.success("Your comment has been deleted successfully.");
|
toast.add({
|
||||||
|
title: "Your comment has been deleted successfully.",
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
setMode({ type: "idle" });
|
setMode({ type: "idle" });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error deleting comment:", error);
|
console.error("Error deleting comment:", error);
|
||||||
toast.error("Failed to delete comment. Please try again.");
|
toast.add({
|
||||||
|
title: "Failed to delete comment. Please try again.",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
setMode({ type: "idle" });
|
setMode({ type: "idle" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
import { IconInfoCircle, IconMarkdown } from "@tabler/icons-react";
|
import { IconInfoCircle, IconMarkdown } from "@tabler/icons-react";
|
||||||
import { createContext, useContext, useMemo, useState, useTransition } from "react";
|
import { createContext, useContext, useMemo, useState, useTransition } from "react";
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { toast } from "@/components/ui/toast";
|
||||||
import { useSession } from "@/lib/auth-client";
|
import { useSession } from "@/lib/auth-client";
|
||||||
import { createComment, updateComment } from "@/lib/server/comments";
|
import { createComment, updateComment } from "@/lib/server/comments";
|
||||||
|
|
||||||
@@ -203,18 +203,21 @@ const NewCommentForm = ({ slug }: { slug: string }) => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!content.trim()) {
|
if (!content.trim()) {
|
||||||
toast.error("Comment cannot be empty.");
|
toast.add({ title: "Comment cannot be empty.", type: "error" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
try {
|
try {
|
||||||
await createComment({ content, pageSlug: slug });
|
await createComment({ content, pageSlug: slug });
|
||||||
toast.success("Comment posted!");
|
toast.add({ title: "Comment posted!", type: "success" });
|
||||||
setContent("");
|
setContent("");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error submitting comment:", error);
|
console.error("Error submitting comment:", error);
|
||||||
toast.error("Failed to submit comment. Please try again.");
|
toast.add({
|
||||||
|
title: "Failed to submit comment. Please try again.",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -268,19 +271,22 @@ const ReplyForm = ({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!content.trim()) {
|
if (!content.trim()) {
|
||||||
toast.error("Comment cannot be empty.");
|
toast.add({ title: "Comment cannot be empty.", type: "error" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
try {
|
try {
|
||||||
await createComment({ content, parentId, pageSlug: slug });
|
await createComment({ content, parentId, pageSlug: slug });
|
||||||
toast.success("Comment posted!");
|
toast.add({ title: "Comment posted!", type: "success" });
|
||||||
setContent("");
|
setContent("");
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error submitting comment:", error);
|
console.error("Error submitting comment:", error);
|
||||||
toast.error("Failed to submit comment. Please try again.");
|
toast.add({
|
||||||
|
title: "Failed to submit comment. Please try again.",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -338,18 +344,21 @@ const EditCommentForm = ({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!content.trim()) {
|
if (!content.trim()) {
|
||||||
toast.error("Comment cannot be empty.");
|
toast.add({ title: "Comment cannot be empty.", type: "error" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
try {
|
try {
|
||||||
await updateComment(commentId, content);
|
await updateComment(commentId, content);
|
||||||
toast.success("Comment updated!");
|
toast.add({ title: "Comment updated!", type: "success" });
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating comment:", error);
|
console.error("Error updating comment:", error);
|
||||||
toast.error("Failed to update comment. Please try again.");
|
toast.add({
|
||||||
|
title: "Failed to update comment. Please try again.",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
import { GitHubIcon } from "@/components/icons";
|
import { GitHubIcon } from "@/components/icons";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
|
import { toast } from "@/components/ui/toast";
|
||||||
import { signIn } from "@/lib/auth-client";
|
import { signIn } from "@/lib/auth-client";
|
||||||
|
|
||||||
const SignIn = ({ callbackPath }: { callbackPath?: string }) => {
|
const SignIn = ({ callbackPath }: { callbackPath?: string }) => {
|
||||||
@@ -21,7 +21,7 @@ const SignIn = ({ callbackPath }: { callbackPath?: string }) => {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error signing in:", error);
|
console.error("Error signing in:", error);
|
||||||
toast.error("There was a problem signing in.");
|
toast.add({ title: "There was a problem signing in.", type: "error" });
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { IconCheck, IconClipboardCheck, IconCopy } from "@tabler/icons-react";
|
import { IconCheck, IconCopy } from "@tabler/icons-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { toast } from "@/components/ui/toast";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
function CopyButton({
|
function CopyButton({
|
||||||
@@ -34,13 +34,20 @@ function CopyButton({
|
|||||||
await navigator.clipboard.writeText(value);
|
await navigator.clipboard.writeText(value);
|
||||||
setHasCopied(true);
|
setHasCopied(true);
|
||||||
|
|
||||||
toast.success("Copied!", {
|
toast.add({
|
||||||
icon: <IconClipboardCheck className="size-4 text-foreground/85" aria-hidden="true" />,
|
title: "Copied!",
|
||||||
duration: 2000,
|
type: "success",
|
||||||
|
timeout: 2000,
|
||||||
id: "copy-button-toast-success",
|
id: "copy-button-toast-success",
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("failed to copy:", error);
|
console.error("Failed to copy:", error);
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
title: "Failed to copy; see console for details.",
|
||||||
|
type: "error",
|
||||||
|
id: "copy-button-toast-error",
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
if (timeoutRef.current) {
|
if (timeoutRef.current) {
|
||||||
clearTimeout(timeoutRef.current);
|
clearTimeout(timeoutRef.current);
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import {
|
|
||||||
IconAlertTriangle,
|
|
||||||
IconCircleCheck,
|
|
||||||
IconCircleX,
|
|
||||||
IconInfoCircle,
|
|
||||||
} from "@tabler/icons-react";
|
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
|
||||||
|
|
||||||
import { Spinner } from "@/components/ui/spinner";
|
|
||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
|
||||||
const { theme = "system" } = useTheme();
|
|
||||||
const toasterTheme: ToasterProps["theme"] =
|
|
||||||
theme === "dark" || theme === "light" || theme === "system" ? theme : "system";
|
|
||||||
|
|
||||||
const style: React.CSSProperties & {
|
|
||||||
[key: `--${string}`]: string;
|
|
||||||
} = {
|
|
||||||
"--normal-bg": "var(--popover)",
|
|
||||||
"--normal-text": "var(--popover-foreground)",
|
|
||||||
"--normal-border": "var(--border)",
|
|
||||||
"--border-radius": "var(--radius)",
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Sonner
|
|
||||||
theme={toasterTheme}
|
|
||||||
className="toaster group"
|
|
||||||
icons={{
|
|
||||||
success: <IconCircleCheck className="size-4" />,
|
|
||||||
info: <IconInfoCircle className="size-4" />,
|
|
||||||
warning: <IconAlertTriangle className="size-4" />,
|
|
||||||
error: <IconCircleX className="size-4" />,
|
|
||||||
loading: <Spinner className="size-4" />,
|
|
||||||
}}
|
|
||||||
style={style}
|
|
||||||
toastOptions={{
|
|
||||||
classNames: {
|
|
||||||
toast: "cn-toast",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { Toaster };
|
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Toast as ToastPrimitive } from "@base-ui/react/toast";
|
||||||
|
import {
|
||||||
|
IconX,
|
||||||
|
IconCircleCheck,
|
||||||
|
IconInfoCircle,
|
||||||
|
IconAlertTriangle,
|
||||||
|
IconAlertOctagon,
|
||||||
|
IconLoader,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const toast = ToastPrimitive.createToastManager();
|
||||||
|
|
||||||
|
function ToastProvider({ ...props }: ToastPrimitive.Provider.Props) {
|
||||||
|
return <ToastPrimitive.Provider {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToastPortal({ ...props }: ToastPrimitive.Portal.Props) {
|
||||||
|
return <ToastPrimitive.Portal data-slot="toast-portal" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToastViewport({ className, ...props }: ToastPrimitive.Viewport.Props) {
|
||||||
|
return (
|
||||||
|
<ToastPrimitive.Viewport
|
||||||
|
data-slot="toast-viewport"
|
||||||
|
className={cn(
|
||||||
|
"pointer-events-none fixed inset-x-4 bottom-4 z-50 mx-auto w-auto max-w-sm outline-none sm:right-4 sm:left-auto sm:mx-0 sm:w-full",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Toast({ className, ...props }: ToastPrimitive.Root.Props) {
|
||||||
|
return (
|
||||||
|
<ToastPrimitive.Root
|
||||||
|
data-slot="toast"
|
||||||
|
className={cn(
|
||||||
|
"group/toast pointer-events-auto absolute right-0 bottom-0 z-[calc(1000-var(--toast-index))] w-full origin-bottom rounded-2xl border bg-popover text-popover-foreground shadow-lg will-change-transform outline-none select-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
||||||
|
"[--gap:0.75rem] [--height:var(--toast-frontmost-height,var(--toast-height))] [--offset-y:calc(var(--toast-offset-y)*-1+calc(var(--toast-index)*var(--gap)*-1)+var(--toast-swipe-movement-y))] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))] [--shrink:calc(1-var(--scale))]",
|
||||||
|
"h-(--height) [transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--peek))-(var(--shrink)*var(--height))))_scale(var(--scale))] [transition:transform_500ms_cubic-bezier(0.22,1,0.36,1),opacity_500ms,height_150ms]",
|
||||||
|
"after:absolute after:top-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-['']",
|
||||||
|
"data-expanded:h-(--toast-height) data-expanded:[transform:translateX(var(--toast-swipe-movement-x))_translateY(var(--offset-y))]",
|
||||||
|
"data-limited:opacity-0 data-starting-style:[transform:translateY(150%)]",
|
||||||
|
"[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(150%)]",
|
||||||
|
"data-ending-style:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))]",
|
||||||
|
"data-ending-style:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))]",
|
||||||
|
"data-ending-style:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))]",
|
||||||
|
"data-ending-style:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]",
|
||||||
|
"data-expanded:data-ending-style:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))]",
|
||||||
|
"data-expanded:data-ending-style:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))]",
|
||||||
|
"data-expanded:data-ending-style:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))]",
|
||||||
|
"data-expanded:data-ending-style:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToastContent({ className, ...props }: ToastPrimitive.Content.Props) {
|
||||||
|
return (
|
||||||
|
<ToastPrimitive.Content
|
||||||
|
data-slot="toast-content"
|
||||||
|
className={cn(
|
||||||
|
"flex h-full items-center gap-3 overflow-hidden p-4 transition-opacity duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] data-behind:opacity-0 data-expanded:opacity-100",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToastTitle({ className, ...props }: ToastPrimitive.Title.Props) {
|
||||||
|
return (
|
||||||
|
<ToastPrimitive.Title
|
||||||
|
data-slot="toast-title"
|
||||||
|
className={cn("text-sm font-medium", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToastDescription({ className, ...props }: ToastPrimitive.Description.Props) {
|
||||||
|
return (
|
||||||
|
<ToastPrimitive.Description
|
||||||
|
data-slot="toast-description"
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const toastActionButton = <Button variant="outline" size="sm" />;
|
||||||
|
const toastCloseButton = <Button variant="ghost" size="icon-sm" />;
|
||||||
|
|
||||||
|
function ToastAction({
|
||||||
|
className,
|
||||||
|
render = toastActionButton,
|
||||||
|
...props
|
||||||
|
}: ToastPrimitive.Action.Props) {
|
||||||
|
return (
|
||||||
|
<ToastPrimitive.Action
|
||||||
|
data-slot="toast-action"
|
||||||
|
render={render}
|
||||||
|
className={cn("shrink-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToastClose({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
render = toastCloseButton,
|
||||||
|
...props
|
||||||
|
}: ToastPrimitive.Close.Props) {
|
||||||
|
return (
|
||||||
|
<ToastPrimitive.Close
|
||||||
|
data-slot="toast-close"
|
||||||
|
aria-label="Close toast"
|
||||||
|
render={render}
|
||||||
|
className={cn(
|
||||||
|
"relative shrink-0 text-muted-foreground after:absolute after:-inset-2 after:content-[''] hover:text-foreground",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children ?? <IconX aria-hidden="true" />}
|
||||||
|
</ToastPrimitive.Close>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToastIcon({ type }: { type: string | undefined }) {
|
||||||
|
let icon: React.ReactNode = null;
|
||||||
|
|
||||||
|
if (type === "success") {
|
||||||
|
icon = <IconCircleCheck aria-hidden="true" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "info") {
|
||||||
|
icon = <IconInfoCircle aria-hidden="true" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "warning") {
|
||||||
|
icon = <IconAlertTriangle aria-hidden="true" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "error") {
|
||||||
|
icon = <IconAlertOctagon className="text-destructive" aria-hidden="true" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "loading") {
|
||||||
|
icon = <IconLoader className="animate-spin" aria-hidden="true" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!icon) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
data-slot="toast-icon"
|
||||||
|
className="shrink-0 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4"
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToastList() {
|
||||||
|
const { toasts } = ToastPrimitive.useToastManager();
|
||||||
|
|
||||||
|
return toasts.map((toastItem) => (
|
||||||
|
<Toast key={toastItem.id} toast={toastItem}>
|
||||||
|
<ToastContent>
|
||||||
|
<ToastIcon type={toastItem.type} />
|
||||||
|
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
||||||
|
<ToastTitle />
|
||||||
|
<ToastDescription />
|
||||||
|
</div>
|
||||||
|
<ToastAction />
|
||||||
|
<ToastClose />
|
||||||
|
</ToastContent>
|
||||||
|
</Toast>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
function Toaster({ children, toastManager = toast, ...props }: ToastPrimitive.Provider.Props) {
|
||||||
|
return (
|
||||||
|
<ToastProvider toastManager={toastManager} {...props}>
|
||||||
|
{children}
|
||||||
|
<ToastPortal>
|
||||||
|
<ToastViewport>
|
||||||
|
<ToastList />
|
||||||
|
</ToastViewport>
|
||||||
|
</ToastPortal>
|
||||||
|
</ToastProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createToastManager = ToastPrimitive.createToastManager;
|
||||||
|
const useToastManager = ToastPrimitive.useToastManager;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Toaster,
|
||||||
|
Toast,
|
||||||
|
ToastAction,
|
||||||
|
ToastClose,
|
||||||
|
ToastContent,
|
||||||
|
ToastDescription,
|
||||||
|
ToastPortal,
|
||||||
|
ToastProvider,
|
||||||
|
ToastTitle,
|
||||||
|
ToastViewport,
|
||||||
|
createToastManager,
|
||||||
|
toast,
|
||||||
|
useToastManager,
|
||||||
|
};
|
||||||
@@ -35,7 +35,6 @@
|
|||||||
"@tabler/icons-react": "^3.46.0",
|
"@tabler/icons-react": "^3.46.0",
|
||||||
"@vercel/analytics": "^2.0.1",
|
"@vercel/analytics": "^2.0.1",
|
||||||
"@vercel/functions": "^3.9.3",
|
"@vercel/functions": "^3.9.3",
|
||||||
"@vercel/speed-insights": "^2.0.0",
|
|
||||||
"better-auth": "1.7.0-rc.6",
|
"better-auth": "1.7.0-rc.6",
|
||||||
"cheerio": "^1.2.0",
|
"cheerio": "^1.2.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
@@ -74,7 +73,6 @@
|
|||||||
"server-only": "0.0.1",
|
"server-only": "0.0.1",
|
||||||
"shadcn": "^4.18.0",
|
"shadcn": "^4.18.0",
|
||||||
"shiki": "^4.4.3",
|
"shiki": "^4.4.3",
|
||||||
"sonner": "^2.0.8",
|
|
||||||
"tailwind-merge": "^3.6.0",
|
"tailwind-merge": "^3.6.0",
|
||||||
"tinyglobby": "^0.2.17",
|
"tinyglobby": "^0.2.17",
|
||||||
"unified": "^11.0.5",
|
"unified": "^11.0.5",
|
||||||
|
|||||||
Generated
-54
@@ -44,9 +44,6 @@ importers:
|
|||||||
'@vercel/functions':
|
'@vercel/functions':
|
||||||
specifier: ^3.9.3
|
specifier: ^3.9.3
|
||||||
version: 3.9.3
|
version: 3.9.3
|
||||||
'@vercel/speed-insights':
|
|
||||||
specifier: ^2.0.0
|
|
||||||
version: 2.0.0(next@16.3.1(@babel/core@7.29.7)(@types/node@26.2.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)
|
|
||||||
better-auth:
|
better-auth:
|
||||||
specifier: 1.7.0-rc.6
|
specifier: 1.7.0-rc.6
|
||||||
version: 1.7.0-rc.6(drizzle-kit@1.0.0-rc.5-ab785fc)(drizzle-orm@1.0.0-rc.5-ab785fc(@types/pg@8.21.0)(pg@8.23.0)(zod@4.4.3))(next@16.3.1(@babel/core@7.29.7)(@types/node@26.2.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.23.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
version: 1.7.0-rc.6(drizzle-kit@1.0.0-rc.5-ab785fc)(drizzle-orm@1.0.0-rc.5-ab785fc(@types/pg@8.21.0)(pg@8.23.0)(zod@4.4.3))(next@16.3.1(@babel/core@7.29.7)(@types/node@26.2.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(pg@8.23.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
@@ -161,9 +158,6 @@ importers:
|
|||||||
shiki:
|
shiki:
|
||||||
specifier: ^4.4.3
|
specifier: ^4.4.3
|
||||||
version: 4.4.3
|
version: 4.4.3
|
||||||
sonner:
|
|
||||||
specifier: ^2.0.8
|
|
||||||
version: 2.0.8(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
|
||||||
tailwind-merge:
|
tailwind-merge:
|
||||||
specifier: ^3.6.0
|
specifier: ^3.6.0
|
||||||
version: 3.6.0
|
version: 3.6.0
|
||||||
@@ -1715,32 +1709,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-FGNvVZ5pgX9FaBqkPt6VkYFZ6bWAMDzYi7nxW+1Xt+Z4fn5PuTULVwsxjKc+0uKhysyWBQmvsmM50Oh6C2/oMA==}
|
resolution: {integrity: sha512-FGNvVZ5pgX9FaBqkPt6VkYFZ6bWAMDzYi7nxW+1Xt+Z4fn5PuTULVwsxjKc+0uKhysyWBQmvsmM50Oh6C2/oMA==}
|
||||||
engines: {node: '>= 20'}
|
engines: {node: '>= 20'}
|
||||||
|
|
||||||
'@vercel/speed-insights@2.0.0':
|
|
||||||
resolution: {integrity: sha512-jwkNcrTeafWxjmWq4AHBaptSqZiJkYU5adLC9QBSqeim0GcqDMgN5Ievh8OG1rJ6W3A4l1oiP7qr9CWxGuzu3w==}
|
|
||||||
peerDependencies:
|
|
||||||
'@sveltejs/kit': ^1 || ^2
|
|
||||||
next: '>= 13'
|
|
||||||
nuxt: '>= 3'
|
|
||||||
react: ^18 || ^19 || ^19.0.0-rc
|
|
||||||
svelte: '>= 4'
|
|
||||||
vue: ^3
|
|
||||||
vue-router: ^4
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@sveltejs/kit':
|
|
||||||
optional: true
|
|
||||||
next:
|
|
||||||
optional: true
|
|
||||||
nuxt:
|
|
||||||
optional: true
|
|
||||||
react:
|
|
||||||
optional: true
|
|
||||||
svelte:
|
|
||||||
optional: true
|
|
||||||
vue:
|
|
||||||
optional: true
|
|
||||||
vue-router:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
accepts@2.0.0:
|
accepts@2.0.0:
|
||||||
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@@ -3813,16 +3781,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==}
|
resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==}
|
||||||
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
|
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
|
||||||
|
|
||||||
sonner@2.0.8:
|
|
||||||
resolution: {integrity: sha512-UM/ByIoFra8yzV75n1o0Puu0bw5U/9UNnDacrJNspekBewIfsQ3D6ez1nvlWpt7aTsO6rujQtifBpycwIivqlg==}
|
|
||||||
peerDependencies:
|
|
||||||
'@types/react': ^18.0.0 || ^19.0.0
|
|
||||||
react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
|
||||||
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@types/react':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
source-map-js@1.2.1:
|
source-map-js@1.2.1:
|
||||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -5311,11 +5269,6 @@ snapshots:
|
|||||||
'@vercel/cli-exec': 1.0.1
|
'@vercel/cli-exec': 1.0.1
|
||||||
jose: 5.10.0
|
jose: 5.10.0
|
||||||
|
|
||||||
'@vercel/speed-insights@2.0.0(next@16.3.1(@babel/core@7.29.7)(@types/node@26.2.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)':
|
|
||||||
optionalDependencies:
|
|
||||||
next: 16.3.1(@babel/core@7.29.7)(@types/node@26.2.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
|
||||||
react: 19.2.8
|
|
||||||
|
|
||||||
accepts@2.0.0:
|
accepts@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
mime-types: 3.0.2
|
mime-types: 3.0.2
|
||||||
@@ -7706,13 +7659,6 @@ snapshots:
|
|||||||
ip-address: 10.5.0
|
ip-address: 10.5.0
|
||||||
smart-buffer: 4.2.0
|
smart-buffer: 4.2.0
|
||||||
|
|
||||||
sonner@2.0.8(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
|
|
||||||
dependencies:
|
|
||||||
react: 19.2.8
|
|
||||||
react-dom: 19.2.8(react@19.2.8)
|
|
||||||
optionalDependencies:
|
|
||||||
'@types/react': 19.2.18
|
|
||||||
|
|
||||||
source-map-js@1.2.1: {}
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
source-map@0.6.1: {}
|
source-map@0.6.1: {}
|
||||||
|
|||||||
Reference in new issue
Block a user