create toaster,tooltip,add icons
This commit is contained in:
320
src/components/ui/toaster.tsx
Normal file
320
src/components/ui/toaster.tsx
Normal file
@@ -0,0 +1,320 @@
|
||||
import CopyIcon from "@/icons/copy.svg?react";
|
||||
|
||||
import SuccessIcon from "@/icons/check-circle.svg?react";
|
||||
import ErrorIcon from "@/icons/warning.svg?react";
|
||||
import InfoIcon from "@/icons/info.svg?react";
|
||||
|
||||
import * as ToastPrimitives from "@radix-ui/react-toast";
|
||||
import { type VariantProps, cva } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
import Close from "@/icons/x-mark.svg?react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import clsx from "clsx";
|
||||
import { useToast } from "@/lib/hooks/use-toast";
|
||||
|
||||
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 min-h-[88px] min-w-[300px] pointer-events-auto relative flex w-full items-center justify-between overflow-hidden rounded-2xl p-4 pr-4 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 dark:border-neutral-800",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
success: "bg-feedback-positive-100",
|
||||
warning: "bg-feedback-attention-100",
|
||||
error: "bg-feedback-negative-100",
|
||||
info: "bg-feedback-info-100",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "success",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const ToastIcon = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
||||
VariantProps<typeof toastVariants>
|
||||
>(({ className, variant }) => {
|
||||
if (variant === "success") {
|
||||
return (
|
||||
<SuccessIcon className={clsx(className, "text-feedback-positive-900")} />
|
||||
);
|
||||
}
|
||||
|
||||
if (variant === "warning") {
|
||||
return (
|
||||
<ErrorIcon className={clsx(className, "text-feedback-attention-900")} />
|
||||
);
|
||||
}
|
||||
|
||||
if (variant === "error") {
|
||||
return (
|
||||
<ErrorIcon className={clsx(className, "text-feedback-negative-900")} />
|
||||
);
|
||||
}
|
||||
|
||||
if (variant === "info") {
|
||||
return <InfoIcon className={clsx(className, "text-feedback-info-900")} />;
|
||||
}
|
||||
});
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
});
|
||||
Toast.displayName = ToastPrimitives.Root.displayName;
|
||||
|
||||
const ToastCopy = ({
|
||||
text,
|
||||
href,
|
||||
toastAction = () => {
|
||||
if (href) {
|
||||
navigator.clipboard.writeText(href);
|
||||
}
|
||||
},
|
||||
}: {
|
||||
text: string;
|
||||
href?: string;
|
||||
toastAction?: () => void;
|
||||
}) => (
|
||||
<button
|
||||
onClick={toastAction}
|
||||
type="button"
|
||||
className="flex flex-row gap-2 group mb-2"
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center size-6 bg-[#888787] rounded-full">
|
||||
<CopyIcon className="size-[10px]" />
|
||||
</div>
|
||||
<span className="text-[#888787] hover:text-white ease-in-out duration-100 text-base font-medium font-['Articulat CF'] leading-normal">
|
||||
{text}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
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 border-neutral-200 bg-transparent px-3 text-sm font-medium transition-colors hover:bg-neutral-100 focus:outline-none focus:ring-1 focus:ring-neutral-950 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-neutral-100/40 group-[.destructive]:hover:border-red-500/30 group-[.destructive]:hover:bg-red-500 group-[.destructive]:hover:text-neutral-50 group-[.destructive]:focus:ring-red-500 dark:border-neutral-800 dark:hover:bg-neutral-800 dark:focus:ring-neutral-300 dark:group-[.destructive]:border-neutral-800/40 dark:group-[.destructive]:hover:border-red-900/30 dark:group-[.destructive]:hover:bg-red-900 dark:group-[.destructive]:hover:text-neutral-50 dark:group-[.destructive]:focus:ring-red-900",
|
||||
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("my-auto mx-0 rounded-md cursor-pointer", className)}
|
||||
toast-close=""
|
||||
{...props}
|
||||
>
|
||||
<Close className="size-5 text-[#C0C0C0]" />
|
||||
</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-text-light-900 text-[18px] leading-[120%] sm:text-lg font-medium font-articulat leading-normal [&+div]:text-xs",
|
||||
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-text-light-500 text-[14px] font-medium font-articulat leading-normal mb-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
const ToastTimeline = ({
|
||||
duration,
|
||||
isPaused,
|
||||
variant,
|
||||
}: {
|
||||
duration: number;
|
||||
isPaused: boolean;
|
||||
variant?: "error" | "success" | "warning" | "info" | null;
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={cn(
|
||||
"absolute bottom-2 left-4 h-[3px] w-[90%] rounded bg-fill-100",
|
||||
)}
|
||||
/>
|
||||
|
||||
<div
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"absolute bottom-2 left-4 h-[3px] w-[90%] data-[variant=error]:bg-feedback-negative-900 rounded data-[variant=success]:bg-feedback-positive-900 data-[variant=warning]:bg-feedback-attention-900 data-[variant=info]:bg-feedback-info-900 toast-progress-animation",
|
||||
)}
|
||||
style={{
|
||||
animationDuration: `${duration}ms`,
|
||||
animationPlayState: isPaused ? "paused" : "running",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ToastDescription.displayName = ToastPrimitives.Description.displayName;
|
||||
|
||||
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
|
||||
|
||||
type ToastActionElement = React.ReactElement<typeof ToastAction>;
|
||||
|
||||
export function Toaster() {
|
||||
const { toasts, isPaused, setIsPaused } = useToast();
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleFocus = () => {
|
||||
setIsPaused(false);
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
setIsPaused(true);
|
||||
};
|
||||
|
||||
window.addEventListener("focus", handleFocus);
|
||||
window.addEventListener("blur", handleBlur);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("focus", handleFocus);
|
||||
window.removeEventListener("blur", handleBlur);
|
||||
};
|
||||
}, [setIsPaused]);
|
||||
|
||||
return (
|
||||
<ToastProvider>
|
||||
{toasts.map(
|
||||
({
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
action,
|
||||
copy,
|
||||
href,
|
||||
toastAction,
|
||||
duration = 5000,
|
||||
withTimeline,
|
||||
...props
|
||||
}) => (
|
||||
<Toast
|
||||
key={id}
|
||||
duration={duration}
|
||||
{...props}
|
||||
onMouseEnter={() => setIsPaused(true)}
|
||||
onMouseLeave={() => setIsPaused(false)}
|
||||
>
|
||||
<ToastIcon className="size-12 mr-4" variant={props.variant} />
|
||||
<div className="grid gap-2 w-full">
|
||||
{title && <ToastTitle>{title}</ToastTitle>}
|
||||
|
||||
{description && (
|
||||
<ToastDescription>
|
||||
{/* {props.variant === "errorWithCopy" && (
|
||||
<p className="text-[16px] mb-2">
|
||||
To solve problems, contact us <br />
|
||||
via{" "}
|
||||
<a
|
||||
href={"https://discord.gg/cytonic"}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-brand relative"
|
||||
>
|
||||
Discord
|
||||
<div className="h-[1px] w-[56px] bg-brand absolute left-0" />
|
||||
</a>{" "}
|
||||
and send the error text
|
||||
</p>
|
||||
)} */}
|
||||
{description}
|
||||
</ToastDescription>
|
||||
)}
|
||||
|
||||
{copy && (
|
||||
<ToastCopy text={copy} href={href} toastAction={toastAction} />
|
||||
)}
|
||||
</div>
|
||||
{action}
|
||||
<ToastClose />
|
||||
|
||||
{withTimeline && (
|
||||
<ToastTimeline
|
||||
duration={duration}
|
||||
isPaused={isPaused}
|
||||
variant={props.variant}
|
||||
/>
|
||||
)}
|
||||
</Toast>
|
||||
),
|
||||
)}
|
||||
<ToastViewport />
|
||||
</ToastProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
type ToastProps,
|
||||
type ToastActionElement,
|
||||
ToastProvider,
|
||||
ToastViewport,
|
||||
Toast,
|
||||
ToastTitle,
|
||||
ToastDescription,
|
||||
ToastClose,
|
||||
ToastAction,
|
||||
ToastIcon,
|
||||
ToastCopy,
|
||||
ToastTimeline,
|
||||
};
|
||||
59
src/components/ui/tooltip.tsx
Normal file
59
src/components/ui/tooltip.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import * as React from "react";
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function TooltipProvider({
|
||||
delayDuration = 0,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
||||
return (
|
||||
<TooltipPrimitive.Provider
|
||||
data-slot="tooltip-provider"
|
||||
delayDuration={delayDuration}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function Tooltip({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function TooltipTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
||||
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
||||
}
|
||||
|
||||
function TooltipContent({
|
||||
className,
|
||||
sideOffset = 0,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
||||
return (
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
data-slot="tooltip-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-fill-900 text-text-light-200 font-[600] text-[18px] text animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-balance",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%)] rounded-[2px]" />
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
);
|
||||
}
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
||||
Reference in New Issue
Block a user