mirror of
https://github.com/jetkvm/kvm.git
synced 2025-09-16 08:38:14 +00:00
* chore(ui): Patch bump in tailwind related packages and framer-motion tailwind: [4.1.6 -> 4.1.7](https://github.com/tailwindlabs/tailwindcss/compare/v4.1.6...v4.1.7) @tailwindcss/postcss: 4.1.6 -> 4.1.7 @tailwindcss/vite: 4.1.6 -> 4.1.7 Also patch-bump of: framer-motion: [12.11.0 -> 12.11.4](https://github.com/motiondivision/motion/compare/v12.11.0...v12.11.4) No source changes seemingly needed, have not rerun the migrate. * chore(ui): Run tailwind upgrade and review changes Ran the `npx @tailwindcss/upgrade` and accepted the changes that seemed safe. They're things like: - `data-[closed]:translate-y-9` -> `data-closed:translate-y-8` ()swaps the square bracket syntax to a `-` modifier) - `bg-gradient-to-*` -> `bg-linear-to-*` - `/[*%]` -> `/*` (swap square bracket syntax for inline) - `theme(*.*)` -> `var(--*-*)` (theme styles are exposed as variables with hyphens for dots now) - `[background-size:*]` -> `bg-size[*]` (move the square brackets inside tag) - `[.active_&]:` -> `in[.active]:` (new syntax for parent query) - `!class` -> `class!` (e.g. _!overflow-visible_ to _overflow-visible!_, for [important flag](https://tailwindcss.com/docs/styling-with-utility-classes#using-the-important-flag style) - `w-[1px]` -> `w-px` (that's a new syntax for a 1px width) - `h-[1px]` -> `h-px` (that's a new syntax for a 1px height) - moved `html` and `html, body` global settings in the _index.css_ Also killed off an unused `import` and blank css class. Also picked up the two `flex-grow` -> `grow` that I missed last pass, oops.
89 lines
2.1 KiB
TypeScript
89 lines
2.1 KiB
TypeScript
import toast, { Toast, Toaster, useToasterStore } from "react-hot-toast";
|
||
import React, { useEffect } from "react";
|
||
import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/20/solid";
|
||
|
||
import Card from "@/components/Card";
|
||
|
||
|
||
interface NotificationOptions {
|
||
duration?: number;
|
||
// Add other options as needed
|
||
}
|
||
|
||
const ToastContent = ({
|
||
icon,
|
||
message,
|
||
t,
|
||
}: {
|
||
icon: React.ReactNode;
|
||
message: string;
|
||
t: Toast;
|
||
}) => (
|
||
<Card
|
||
className={`${
|
||
t.visible ? "animate-enter" : "animate-leave"
|
||
} pointer-events-auto z-30 w-full max-w-sm shadow-xl!`}
|
||
>
|
||
<div className="flex items-center gap-x-2 p-2.5 px-2">
|
||
{icon}
|
||
<p className="text-[14px] font-medium text-gray-900 dark:text-gray-100">{message}</p>
|
||
</div>
|
||
</Card>
|
||
);
|
||
|
||
const notifications = {
|
||
success: (message: string, options?: NotificationOptions) => {
|
||
return toast.custom(
|
||
t => (
|
||
<ToastContent
|
||
icon={<CheckCircleIcon className="w-5 h-5 text-green-500 dark:text-green-400" />}
|
||
message={message}
|
||
t={t}
|
||
/>
|
||
),
|
||
{ duration: 2000, ...options },
|
||
);
|
||
},
|
||
|
||
error: (message: string, options?: NotificationOptions) => {
|
||
return toast.custom(
|
||
t => (
|
||
<ToastContent
|
||
icon={<XCircleIcon className="w-5 h-5 text-red-500 dark:text-red-400" />}
|
||
message={message}
|
||
t={t}
|
||
/>
|
||
),
|
||
{ duration: 2000, ...options },
|
||
);
|
||
},
|
||
};
|
||
|
||
function useMaxToasts(max: number) {
|
||
const { toasts } = useToasterStore();
|
||
|
||
useEffect(() => {
|
||
toasts
|
||
.filter(t => t.visible) // Only consider visible toasts
|
||
.filter((_, i) => i >= max) // Is toast index over limit?
|
||
.forEach(t => toast.dismiss(t.id)); // Dismiss – Use toast.remove(t.id) for no exit animation
|
||
}, [toasts, max]);
|
||
}
|
||
|
||
export function Notifications({
|
||
max = 10,
|
||
...props
|
||
}: React.ComponentProps<typeof Toaster> & {
|
||
max?: number;
|
||
}) {
|
||
useMaxToasts(max);
|
||
|
||
return <Toaster {...props} />;
|
||
}
|
||
|
||
// eslint-disable-next-line react-refresh/only-export-components
|
||
export default Object.assign(Notifications, {
|
||
success: notifications.success,
|
||
error: notifications.error,
|
||
});
|