kvm/ui/src/notifications.tsx
Marc Brooks 3ec1bdf388
chore(ui): Patch-bump packages and use tailwind upgrade (#456)
* 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.
2025-05-16 19:59:57 +02:00

89 lines
2.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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