create toaster,tooltip,add icons

This commit is contained in:
yzned
2025-07-18 18:29:43 +03:00
committed by yzned
parent ca6dfe6ccb
commit d249f0d291
15 changed files with 640 additions and 38 deletions

View File

@@ -10,6 +10,8 @@
"test": "vitest run" "test": "vitest run"
}, },
"dependencies": { "dependencies": {
"@radix-ui/react-toast": "^1.2.14",
"@radix-ui/react-tooltip": "^1.2.7",
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"@tanstack/react-query": "^5.83.0", "@tanstack/react-query": "^5.83.0",
"@tanstack/react-router": "^1.121.2", "@tanstack/react-router": "^1.121.2",

6
pnpm-lock.yaml generated
View File

@@ -8,6 +8,12 @@ importers:
.: .:
dependencies: dependencies:
'@radix-ui/react-toast':
specifier: ^1.2.14
version: 1.2.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-tooltip':
specifier: ^1.2.7
version: 1.2.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@tailwindcss/vite': '@tailwindcss/vite':
specifier: ^4.1.11 specifier: ^4.1.11
version: 4.1.11(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)) version: 4.1.11(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3))

View 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,
};

View 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 };

View File

@@ -1,3 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 0.25C8.07164 0.25 6.18657 0.821828 4.58319 1.89317C2.97982 2.96451 1.73013 4.48726 0.992179 6.26884C0.254225 8.05042 0.061142 10.0108 0.437348 11.9021C0.813554 13.7934 1.74215 15.5307 3.10571 16.8943C4.46928 18.2579 6.20656 19.1865 8.09787 19.5627C9.98919 19.9389 11.9496 19.7458 13.7312 19.0078C15.5127 18.2699 17.0355 17.0202 18.1068 15.4168C19.1782 13.8134 19.75 11.9284 19.75 10C19.7473 7.41498 18.7192 4.93661 16.8913 3.10872C15.0634 1.28084 12.585 0.25273 10 0.25ZM14.2806 8.28063L9.03063 13.5306C8.96097 13.6004 8.87826 13.6557 8.78721 13.6934C8.69616 13.7312 8.59857 13.7506 8.5 13.7506C8.40144 13.7506 8.30385 13.7312 8.2128 13.6934C8.12175 13.6557 8.03903 13.6004 7.96938 13.5306L5.71938 11.2806C5.57865 11.1399 5.49959 10.949 5.49959 10.75C5.49959 10.551 5.57865 10.3601 5.71938 10.2194C5.86011 10.0786 6.05098 9.99958 6.25 9.99958C6.44903 9.99958 6.6399 10.0786 6.78063 10.2194L8.5 11.9397L13.2194 7.21937C13.2891 7.14969 13.3718 7.09442 13.4628 7.0567C13.5539 7.01899 13.6515 6.99958 13.75 6.99958C13.8486 6.99958 13.9461 7.01899 14.0372 7.0567C14.1282 7.09442 14.2109 7.14969 14.2806 7.21937C14.3503 7.28906 14.4056 7.37178 14.4433 7.46283C14.481 7.55387 14.5004 7.65145 14.5004 7.75C14.5004 7.84855 14.481 7.94613 14.4433 8.03717C14.4056 8.12822 14.3503 8.21094 14.2806 8.28063Z" fill="black"/> <path d="M10 0.25C8.07164 0.25 6.18657 0.821828 4.58319 1.89317C2.97982 2.96451 1.73013 4.48726 0.992179 6.26884C0.254225 8.05042 0.061142 10.0108 0.437348 11.9021C0.813554 13.7934 1.74215 15.5307 3.10571 16.8943C4.46928 18.2579 6.20656 19.1865 8.09787 19.5627C9.98919 19.9389 11.9496 19.7458 13.7312 19.0078C15.5127 18.2699 17.0355 17.0202 18.1068 15.4168C19.1782 13.8134 19.75 11.9284 19.75 10C19.7473 7.41498 18.7192 4.93661 16.8913 3.10872C15.0634 1.28084 12.585 0.25273 10 0.25ZM14.2806 8.28063L9.03063 13.5306C8.96097 13.6004 8.87826 13.6557 8.78721 13.6934C8.69616 13.7312 8.59857 13.7506 8.5 13.7506C8.40144 13.7506 8.30385 13.7312 8.2128 13.6934C8.12175 13.6557 8.03903 13.6004 7.96938 13.5306L5.71938 11.2806C5.57865 11.1399 5.49959 10.949 5.49959 10.75C5.49959 10.551 5.57865 10.3601 5.71938 10.2194C5.86011 10.0786 6.05098 9.99958 6.25 9.99958C6.44903 9.99958 6.6399 10.0786 6.78063 10.2194L8.5 11.9397L13.2194 7.21937C13.2891 7.14969 13.3718 7.09442 13.4628 7.0567C13.5539 7.01899 13.6515 6.99958 13.75 6.99958C13.8486 6.99958 13.9461 7.01899 14.0372 7.0567C14.1282 7.09442 14.2109 7.14969 14.2806 7.21937C14.3503 7.28906 14.4056 7.37178 14.4433 7.46283C14.481 7.55387 14.5004 7.65145 14.5004 7.75C14.5004 7.84855 14.481 7.94613 14.4433 8.03717C14.4056 8.12822 14.3503 8.21094 14.2806 8.28063Z" fill="currentColor"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,3 +1,3 @@
<svg width="21" height="15" viewBox="0 0 21 15" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="21" height="15" viewBox="0 0 21 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.7959 2.54594L7.79593 14.5459C7.69141 14.6508 7.56722 14.734 7.43047 14.7908C7.29373 14.8476 7.14712 14.8768 6.99905 14.8768C6.85099 14.8768 6.70438 14.8476 6.56763 14.7908C6.43089 14.734 6.30669 14.6508 6.20218 14.5459L0.952177 9.29595C0.84753 9.1913 0.764519 9.06706 0.707885 8.93034C0.65125 8.79361 0.622101 8.64706 0.622101 8.49907C0.622101 8.35108 0.65125 8.20453 0.707885 8.0678C0.764519 7.93108 0.84753 7.80684 0.952177 7.7022C1.05682 7.59755 1.18106 7.51454 1.31779 7.4579C1.45451 7.40127 1.60106 7.37212 1.74905 7.37212C1.89705 7.37212 2.04359 7.40127 2.18032 7.4579C2.31705 7.51454 2.44128 7.59755 2.54593 7.7022L6.99999 12.1563L18.2041 0.954069C18.4154 0.742725 18.702 0.623993 19.0009 0.623993C19.2998 0.623993 19.5865 0.742725 19.7978 0.954069C20.0091 1.16541 20.1279 1.45206 20.1279 1.75094C20.1279 2.04983 20.0091 2.33648 19.7978 2.54782L19.7959 2.54594Z" fill="black"/> <path d="M19.7959 2.54594L7.79593 14.5459C7.69141 14.6508 7.56722 14.734 7.43047 14.7908C7.29373 14.8476 7.14712 14.8768 6.99905 14.8768C6.85099 14.8768 6.70438 14.8476 6.56763 14.7908C6.43089 14.734 6.30669 14.6508 6.20218 14.5459L0.952177 9.29595C0.84753 9.1913 0.764519 9.06706 0.707885 8.93034C0.65125 8.79361 0.622101 8.64706 0.622101 8.49907C0.622101 8.35108 0.65125 8.20453 0.707885 8.0678C0.764519 7.93108 0.84753 7.80684 0.952177 7.7022C1.05682 7.59755 1.18106 7.51454 1.31779 7.4579C1.45451 7.40127 1.60106 7.37212 1.74905 7.37212C1.89705 7.37212 2.04359 7.40127 2.18032 7.4579C2.31705 7.51454 2.44128 7.59755 2.54593 7.7022L6.99999 12.1563L18.2041 0.954069C18.4154 0.742725 18.702 0.623993 19.0009 0.623993C19.2998 0.623993 19.5865 0.742725 19.7978 0.954069C20.0091 1.16541 20.1279 1.45206 20.1279 1.75094C20.1279 2.04983 20.0091 2.33648 19.7978 2.54782L19.7959 2.54594Z" fill="currentColor"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1001 B

After

Width:  |  Height:  |  Size: 1008 B

View File

@@ -1,3 +1,3 @@
<svg width="11" height="18" viewBox="0 0 11 18" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="11" height="18" viewBox="0 0 11 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.2959 9.79593L2.79593 17.2959C2.58458 17.5073 2.29794 17.626 1.99905 17.626C1.70017 17.626 1.41352 17.5073 1.20218 17.2959C0.990833 17.0846 0.872101 16.7979 0.872101 16.4991C0.872101 16.2002 0.990833 15.9135 1.20218 15.7022L7.90624 8.99999L1.20405 2.29593C1.09941 2.19128 1.01639 2.06705 0.95976 1.93032C0.903125 1.79359 0.873976 1.64705 0.873976 1.49905C0.873976 1.35106 0.903125 1.20451 0.95976 1.06779C1.01639 0.931058 1.09941 0.806825 1.20405 0.702177C1.3087 0.59753 1.43293 0.51452 1.56966 0.457885C1.70639 0.40125 1.85293 0.372101 2.00093 0.372101C2.14892 0.372101 2.29546 0.40125 2.43219 0.457885C2.56892 0.51452 2.69316 0.59753 2.7978 0.702177L10.2978 8.20218C10.4026 8.30682 10.4856 8.43111 10.5422 8.56792C10.5989 8.70473 10.6279 8.85138 10.6278 8.99944C10.6276 9.14751 10.5982 9.29408 10.5412 9.43076C10.4843 9.56744 10.4009 9.69153 10.2959 9.79593Z" fill="black"/> <path d="M10.2959 9.79593L2.79593 17.2959C2.58458 17.5073 2.29794 17.626 1.99905 17.626C1.70017 17.626 1.41352 17.5073 1.20218 17.2959C0.990833 17.0846 0.872101 16.7979 0.872101 16.4991C0.872101 16.2002 0.990833 15.9135 1.20218 15.7022L7.90624 8.99999L1.20405 2.29593C1.09941 2.19128 1.01639 2.06705 0.95976 1.93032C0.903125 1.79359 0.873976 1.64705 0.873976 1.49905C0.873976 1.35106 0.903125 1.20451 0.95976 1.06779C1.01639 0.931058 1.09941 0.806825 1.20405 0.702177C1.3087 0.59753 1.43293 0.51452 1.56966 0.457885C1.70639 0.40125 1.85293 0.372101 2.00093 0.372101C2.14892 0.372101 2.29546 0.40125 2.43219 0.457885C2.56892 0.51452 2.69316 0.59753 2.7978 0.702177L10.2978 8.20218C10.4026 8.30682 10.4856 8.43111 10.5422 8.56792C10.5989 8.70473 10.6279 8.85138 10.6278 8.99944C10.6276 9.14751 10.5982 9.29408 10.5412 9.43076C10.4843 9.56744 10.4009 9.69153 10.2959 9.79593Z" fill="currentColor"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 992 B

After

Width:  |  Height:  |  Size: 999 B

View File

@@ -1,3 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.25 0H5.25C5.05109 0 4.86032 0.0790178 4.71967 0.21967C4.57902 0.360322 4.5 0.551088 4.5 0.75V4.5H0.75C0.551088 4.5 0.360322 4.57902 0.21967 4.71967C0.0790178 4.86032 0 5.05109 0 5.25V17.25C0 17.4489 0.0790178 17.6397 0.21967 17.7803C0.360322 17.921 0.551088 18 0.75 18H12.75C12.9489 18 13.1397 17.921 13.2803 17.7803C13.421 17.6397 13.5 17.4489 13.5 17.25V13.5H17.25C17.4489 13.5 17.6397 13.421 17.7803 13.2803C17.921 13.1397 18 12.9489 18 12.75V0.75C18 0.551088 17.921 0.360322 17.7803 0.21967C17.6397 0.0790178 17.4489 0 17.25 0ZM16.5 12H13.5V5.25C13.5 5.05109 13.421 4.86032 13.2803 4.71967C13.1397 4.57902 12.9489 4.5 12.75 4.5H6V1.5H16.5V12Z" fill="black"/> <path d="M17.25 0H5.25C5.05109 0 4.86032 0.0790178 4.71967 0.21967C4.57902 0.360322 4.5 0.551088 4.5 0.75V4.5H0.75C0.551088 4.5 0.360322 4.57902 0.21967 4.71967C0.0790178 4.86032 0 5.05109 0 5.25V17.25C0 17.4489 0.0790178 17.6397 0.21967 17.7803C0.360322 17.921 0.551088 18 0.75 18H12.75C12.9489 18 13.1397 17.921 13.2803 17.7803C13.421 17.6397 13.5 17.4489 13.5 17.25V13.5H17.25C17.4489 13.5 17.6397 13.421 17.7803 13.2803C17.921 13.1397 18 12.9489 18 12.75V0.75C18 0.551088 17.921 0.360322 17.7803 0.21967C17.6397 0.0790178 17.4489 0 17.25 0ZM16.5 12H13.5V5.25C13.5 5.05109 13.421 4.86032 13.2803 4.71967C13.1397 4.57902 12.9489 4.5 12.75 4.5H6V1.5H16.5V12Z" fill="currentColor"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 779 B

After

Width:  |  Height:  |  Size: 786 B

View File

@@ -1,3 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 0.25C8.07164 0.25 6.18657 0.821828 4.58319 1.89317C2.97982 2.96451 1.73013 4.48726 0.992179 6.26884C0.254225 8.05042 0.061142 10.0108 0.437348 11.9021C0.813554 13.7934 1.74215 15.5307 3.10571 16.8943C4.46928 18.2579 6.20656 19.1865 8.09787 19.5627C9.98919 19.9389 11.9496 19.7458 13.7312 19.0078C15.5127 18.2699 17.0355 17.0202 18.1068 15.4168C19.1782 13.8134 19.75 11.9284 19.75 10C19.7473 7.41498 18.7192 4.93661 16.8913 3.10872C15.0634 1.28084 12.585 0.25273 10 0.25ZM9.625 4.75C9.84751 4.75 10.065 4.81598 10.25 4.9396C10.435 5.06321 10.5792 5.23891 10.6644 5.44448C10.7495 5.65005 10.7718 5.87625 10.7284 6.09448C10.685 6.31271 10.5778 6.51316 10.4205 6.6705C10.2632 6.82783 10.0627 6.93498 9.84448 6.97838C9.62625 7.02179 9.40005 6.99951 9.19449 6.91436C8.98892 6.82922 8.81322 6.68502 8.6896 6.50002C8.56598 6.31501 8.5 6.0975 8.5 5.875C8.5 5.57663 8.61853 5.29048 8.82951 5.0795C9.04049 4.86853 9.32664 4.75 9.625 4.75ZM10.75 15.25C10.3522 15.25 9.97065 15.092 9.68934 14.8107C9.40804 14.5294 9.25 14.1478 9.25 13.75V10C9.05109 10 8.86033 9.92098 8.71967 9.78033C8.57902 9.63968 8.5 9.44891 8.5 9.25C8.5 9.05109 8.57902 8.86032 8.71967 8.71967C8.86033 8.57902 9.05109 8.5 9.25 8.5C9.64783 8.5 10.0294 8.65804 10.3107 8.93934C10.592 9.22064 10.75 9.60218 10.75 10V13.75C10.9489 13.75 11.1397 13.829 11.2803 13.9697C11.421 14.1103 11.5 14.3011 11.5 14.5C11.5 14.6989 11.421 14.8897 11.2803 15.0303C11.1397 15.171 10.9489 15.25 10.75 15.25Z" fill="black"/> <path d="M10 0.25C8.07164 0.25 6.18657 0.821828 4.58319 1.89317C2.97982 2.96451 1.73013 4.48726 0.992179 6.26884C0.254225 8.05042 0.061142 10.0108 0.437348 11.9021C0.813554 13.7934 1.74215 15.5307 3.10571 16.8943C4.46928 18.2579 6.20656 19.1865 8.09787 19.5627C9.98919 19.9389 11.9496 19.7458 13.7312 19.0078C15.5127 18.2699 17.0355 17.0202 18.1068 15.4168C19.1782 13.8134 19.75 11.9284 19.75 10C19.7473 7.41498 18.7192 4.93661 16.8913 3.10872C15.0634 1.28084 12.585 0.25273 10 0.25ZM9.625 4.75C9.84751 4.75 10.065 4.81598 10.25 4.9396C10.435 5.06321 10.5792 5.23891 10.6644 5.44448C10.7495 5.65005 10.7718 5.87625 10.7284 6.09448C10.685 6.31271 10.5778 6.51316 10.4205 6.6705C10.2632 6.82783 10.0627 6.93498 9.84448 6.97838C9.62625 7.02179 9.40005 6.99951 9.19449 6.91436C8.98892 6.82922 8.81322 6.68502 8.6896 6.50002C8.56598 6.31501 8.5 6.0975 8.5 5.875C8.5 5.57663 8.61853 5.29048 8.82951 5.0795C9.04049 4.86853 9.32664 4.75 9.625 4.75ZM10.75 15.25C10.3522 15.25 9.97065 15.092 9.68934 14.8107C9.40804 14.5294 9.25 14.1478 9.25 13.75V10C9.05109 10 8.86033 9.92098 8.71967 9.78033C8.57902 9.63968 8.5 9.44891 8.5 9.25C8.5 9.05109 8.57902 8.86032 8.71967 8.71967C8.86033 8.57902 9.05109 8.5 9.25 8.5C9.64783 8.5 10.0294 8.65804 10.3107 8.93934C10.592 9.22064 10.75 9.60218 10.75 10V13.75C10.9489 13.75 11.1397 13.829 11.2803 13.9697C11.421 14.1103 11.5 14.3011 11.5 14.5C11.5 14.6989 11.421 14.8897 11.2803 15.0303C11.1397 15.171 10.9489 15.25 10.75 15.25Z" fill="currentColor"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,3 +1,3 @@
<svg width="22" height="19" viewBox="0 0 22 19" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="22" height="19" viewBox="0 0 22 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.2 15.6334L13.0016 1.39563C12.7967 1.04681 12.5042 0.757581 12.1531 0.55662C11.802 0.355659 11.4045 0.249939 11 0.249939C10.5955 0.249939 10.198 0.355659 9.84687 0.55662C9.49578 0.757581 9.20331 1.04681 8.99844 1.39563L0.799999 15.6334C0.602876 15.9708 0.498993 16.3546 0.498993 16.7453C0.498993 17.1361 0.602876 17.5198 0.799999 17.8572C1.00225 18.2081 1.29422 18.4989 1.64596 18.6997C1.9977 18.9006 2.39655 19.0042 2.80156 19H19.1984C19.6031 19.0039 20.0016 18.9001 20.353 18.6993C20.7044 18.4985 20.996 18.2078 21.1981 17.8572C21.3955 17.52 21.4997 17.1363 21.5001 16.7456C21.5004 16.3548 21.3968 15.971 21.2 15.6334ZM10.25 7.75C10.25 7.55109 10.329 7.36032 10.4697 7.21967C10.6103 7.07902 10.8011 7 11 7C11.1989 7 11.3897 7.07902 11.5303 7.21967C11.671 7.36032 11.75 7.55109 11.75 7.75V11.5C11.75 11.6989 11.671 11.8897 11.5303 12.0303C11.3897 12.171 11.1989 12.25 11 12.25C10.8011 12.25 10.6103 12.171 10.4697 12.0303C10.329 11.8897 10.25 11.6989 10.25 11.5V7.75ZM11 16C10.7775 16 10.56 15.934 10.375 15.8104C10.19 15.6868 10.0458 15.5111 9.96063 15.3055C9.87549 15.1 9.85321 14.8738 9.89662 14.6555C9.94002 14.4373 10.0472 14.2368 10.2045 14.0795C10.3618 13.9222 10.5623 13.815 10.7805 13.7716C10.9988 13.7282 11.2249 13.7505 11.4305 13.8356C11.6361 13.9208 11.8118 14.065 11.9354 14.25C12.059 14.435 12.125 14.6525 12.125 14.875C12.125 15.1734 12.0065 15.4595 11.7955 15.6705C11.5845 15.8815 11.2984 16 11 16Z" fill="black"/> <path d="M21.2 15.6334L13.0016 1.39563C12.7967 1.04681 12.5042 0.757581 12.1531 0.55662C11.802 0.355659 11.4045 0.249939 11 0.249939C10.5955 0.249939 10.198 0.355659 9.84687 0.55662C9.49578 0.757581 9.20331 1.04681 8.99844 1.39563L0.799999 15.6334C0.602876 15.9708 0.498993 16.3546 0.498993 16.7453C0.498993 17.1361 0.602876 17.5198 0.799999 17.8572C1.00225 18.2081 1.29422 18.4989 1.64596 18.6997C1.9977 18.9006 2.39655 19.0042 2.80156 19H19.1984C19.6031 19.0039 20.0016 18.9001 20.353 18.6993C20.7044 18.4985 20.996 18.2078 21.1981 17.8572C21.3955 17.52 21.4997 17.1363 21.5001 16.7456C21.5004 16.3548 21.3968 15.971 21.2 15.6334ZM10.25 7.75C10.25 7.55109 10.329 7.36032 10.4697 7.21967C10.6103 7.07902 10.8011 7 11 7C11.1989 7 11.3897 7.07902 11.5303 7.21967C11.671 7.36032 11.75 7.55109 11.75 7.75V11.5C11.75 11.6989 11.671 11.8897 11.5303 12.0303C11.3897 12.171 11.1989 12.25 11 12.25C10.8011 12.25 10.6103 12.171 10.4697 12.0303C10.329 11.8897 10.25 11.6989 10.25 11.5V7.75ZM11 16C10.7775 16 10.56 15.934 10.375 15.8104C10.19 15.6868 10.0458 15.5111 9.96063 15.3055C9.87549 15.1 9.85321 14.8738 9.89662 14.6555C9.94002 14.4373 10.0472 14.2368 10.2045 14.0795C10.3618 13.9222 10.5623 13.815 10.7805 13.7716C10.9988 13.7282 11.2249 13.7505 11.4305 13.8356C11.6361 13.9208 11.8118 14.065 11.9354 14.25C12.059 14.435 12.125 14.6525 12.125 14.875C12.125 15.1734 12.0065 15.4595 11.7955 15.6705C11.5845 15.8815 11.2984 16 11 16Z" fill="currentColor"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.546 13.9541C15.7573 14.1654 15.876 14.4521 15.876 14.7509C15.876 15.0498 15.7573 15.3365 15.546 15.5478C15.3346 15.7592 15.048 15.8779 14.7491 15.8779C14.4502 15.8779 14.1636 15.7592 13.9522 15.5478L8.00002 9.59376L2.04596 15.5459C1.83461 15.7573 1.54797 15.876 1.24908 15.876C0.950197 15.876 0.663552 15.7573 0.452208 15.5459C0.240864 15.3346 0.122131 15.048 0.122131 14.7491C0.122131 14.4502 0.240864 14.1635 0.452208 13.9522L6.40627 8.00001L0.454083 2.04595C0.242738 1.8346 0.124006 1.54796 0.124006 1.24907C0.124006 0.950188 0.242738 0.663544 0.454083 0.452199C0.665427 0.240855 0.952071 0.122123 1.25096 0.122123C1.54984 0.122123 1.83649 0.240855 2.04783 0.452199L8.00002 6.40626L13.9541 0.451262C14.1654 0.239917 14.4521 0.121185 14.751 0.121185C15.0498 0.121185 15.3365 0.239917 15.5478 0.451262C15.7592 0.662606 15.8779 0.94925 15.8779 1.24814C15.8779 1.54702 15.7592 1.83367 15.5478 2.04501L9.59377 8.00001L15.546 13.9541Z" fill="black"/> <path d="M15.546 13.9541C15.7573 14.1654 15.876 14.4521 15.876 14.7509C15.876 15.0498 15.7573 15.3365 15.546 15.5478C15.3346 15.7592 15.048 15.8779 14.7491 15.8779C14.4502 15.8779 14.1636 15.7592 13.9522 15.5478L8.00002 9.59376L2.04596 15.5459C1.83461 15.7573 1.54797 15.876 1.24908 15.876C0.950197 15.876 0.663552 15.7573 0.452208 15.5459C0.240864 15.3346 0.122131 15.048 0.122131 14.7491C0.122131 14.4502 0.240864 14.1635 0.452208 13.9522L6.40627 8.00001L0.454083 2.04595C0.242738 1.8346 0.124006 1.54796 0.124006 1.24907C0.124006 0.950188 0.242738 0.663544 0.454083 0.452199C0.665427 0.240855 0.952071 0.122123 1.25096 0.122123C1.54984 0.122123 1.83649 0.240855 2.04783 0.452199L8.00002 6.40626L13.9541 0.451262C14.1654 0.239917 14.4521 0.121185 14.751 0.121185C15.0498 0.121185 15.3365 0.239917 15.5478 0.451262C15.7592 0.662606 15.8779 0.94925 15.8779 1.24814C15.8779 1.54702 15.7592 1.83367 15.5478 2.04501L9.59377 8.00001L15.546 13.9541Z" fill="currentColor"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

200
src/lib/hooks/use-toast.ts Normal file
View File

@@ -0,0 +1,200 @@
import type { ToastActionElement, ToastProps } from "@/components/ui/toaster";
import { type ReactNode, useEffect, useState } from "react";
type ToasterToast = ToastProps & {
id: string;
title?: ReactNode;
description?: ReactNode;
action?: ToastActionElement;
toastAction?: () => void;
copy?: string;
withTimeline?: boolean;
href?: string;
};
type ActionType = typeof actionTypes;
type Action =
| {
type: ActionType["ADD_TOAST"];
toast: ToasterToast;
}
| {
type: ActionType["UPDATE_TOAST"];
toast: Partial<ToasterToast>;
}
| {
type: ActionType["DISMISS_TOAST"];
toastId?: ToasterToast["id"];
}
| {
type: ActionType["REMOVE_TOAST"];
toastId?: ToasterToast["id"];
};
type State = {
toasts: ToasterToast[];
};
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const;
export type ToastFunction = (props: Toast) => {
id: string;
dismiss: () => void;
update: (props: ToasterToast) => void;
};
export type Toast = Omit<ToasterToast, "id">;
const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000;
let count = 0;
let memoryState: State = { toasts: [] };
const listeners: Array<(state: State) => void> = [];
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
const genId = () => {
count = (count + 1) % Number.MAX_SAFE_INTEGER;
return count.toString();
};
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return;
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId);
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
});
}, TOAST_REMOVE_DELAY);
toastTimeouts.set(toastId, timeout);
};
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
};
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t,
),
};
case "DISMISS_TOAST": {
const { toastId } = action;
if (toastId) {
addToRemoveQueue(toastId);
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id);
});
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t,
),
};
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
};
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
};
}
};
function dispatch(action: Action) {
memoryState = reducer(memoryState, action);
listeners.forEach((listener) => {
listener(memoryState);
});
}
function toast({ ...props }: Toast) {
const id = genId();
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
});
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss();
},
},
});
return {
id: id,
dismiss,
update,
};
}
function useToast() {
const [state, setState] = useState<State>(memoryState);
const [isPaused, setIsPaused] = useState(false);
// biome-ignore lint/correctness/useExhaustiveDependencies: shadcn component
useEffect(() => {
setIsPaused(false);
listeners.push(setState);
return () => {
const index = listeners.indexOf(setState);
if (index > -1) {
listeners.splice(index, 1);
}
};
}, [state]);
return {
...state,
toast,
isPaused,
setIsPaused,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
};
}
export { useToast, toast };

View File

@@ -1,42 +1,47 @@
import { StrictMode } from 'react' import { StrictMode } from "react";
import ReactDOM from 'react-dom/client' import ReactDOM from "react-dom/client";
import { RouterProvider, createRouter } from '@tanstack/react-router' import { RouterProvider, createRouter } from "@tanstack/react-router";
// Import the generated route tree // Import the generated route tree
import { routeTree } from './routeTree.gen' import { routeTree } from "./routeTree.gen";
import './styles.css' import "./styles.css";
import reportWebVitals from './reportWebVitals.ts' import reportWebVitals from "./reportWebVitals.ts";
import { Toaster } from "./components/ui/toaster.tsx";
import { TooltipProvider } from "./components/ui/tooltip.tsx";
// Create a new router instance // Create a new router instance
const router = createRouter({ const router = createRouter({
routeTree, routeTree,
context: {}, context: {},
defaultPreload: 'intent', defaultPreload: "intent",
scrollRestoration: true, scrollRestoration: true,
defaultStructuralSharing: true, defaultStructuralSharing: true,
defaultPreloadStaleTime: 0, defaultPreloadStaleTime: 0,
}) });
// Register the router instance for type safety // Register the router instance for type safety
declare module '@tanstack/react-router' { declare module "@tanstack/react-router" {
interface Register { interface Register {
router: typeof router router: typeof router;
} }
} }
// Render the app // Render the app
const rootElement = document.getElementById('app') const rootElement = document.getElementById("app");
if (rootElement && !rootElement.innerHTML) { if (rootElement && !rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement) const root = ReactDOM.createRoot(rootElement);
root.render( root.render(
<StrictMode> <StrictMode>
<RouterProvider router={router} /> <TooltipProvider delayDuration={50}>
</StrictMode>, <RouterProvider router={router} />
) <Toaster />
</TooltipProvider>
</StrictMode>,
);
} }
// If you want to start measuring performance in your app, pass a function // If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log)) // to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals() reportWebVitals();

View File

@@ -1,4 +1,3 @@
import { TextLink } from "@/components/ui/text-link";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
@@ -6,9 +5,5 @@ export const Route = createFileRoute("/")({
}); });
function App() { function App() {
return ( return <div className="p-4">hi</div>;
<div className="p-4">
<TextLink>Lable</TextLink>
</div>
);
} }

View File

@@ -136,3 +136,18 @@
font-display: swap; font-display: swap;
} }
} }
@keyframes toast-progress {
from {
width: 90%;
}
to {
width: 0%;
}
}
.toast-progress-animation {
animation: toast-progress 5s linear forwards;
}