DEV Community

pjdev2d
pjdev2d

Posted on

notify

constants

import type { NotifyPosition, NotifyVariant } from "./types";

export const notifyPositionClasses: Record<NotifyPosition, string> = {
  "top-left": "top-4 left-4",
  "top-center": "top-4 left-1/2 -translate-x-1/2",
  "top-right": "top-4 right-4",
  "bottom-left": "bottom-4 left-4",
  "bottom-center": "bottom-4 left-1/2 -translate-x-1/2",
  "bottom-right": "bottom-4 right-4",
};

export const notifyVariantClasses: Record<NotifyVariant, string> = {
  default: "border-neutral-200 bg-white text-black",

  success: "border-green-200 bg-green-50 text-green-950",

  error: "border-red-200 bg-red-50 text-red-950",

  warning: "border-yellow-200 bg-yellow-50 text-yellow-950",

  info: "border-blue-200 bg-blue-50 text-blue-950",
};

Enter fullscreen mode Exit fullscreen mode

INDEX

export { NotifyContainer } from "./notify-container";
export { notify, dismiss } from "./store";

Enter fullscreen mode Exit fullscreen mode

card

import { dismiss } from "./store";
import type { NotifyItem } from "./types";
import { notifyVariantClasses } from "./constants";
import { cn } from "@/src/utils";

interface NotifyCardProps {
  item: NotifyItem;
}

export function NotifyCard({ item }: NotifyCardProps) {
  return (
    <div
      className={cn(
        "shadow-xl p-4 border rounded-xl min-w-[320px]",
        notifyVariantClasses[item?.variant || "default"],
      )}
    >
      <div className="flex justify-between items-start gap-4">
        <div>
          {item.title && <h3 className="font-semibold">{item.title}</h3>}

          {item.description && (
            <p className="text-neutral-500 text-sm">{item.description}</p>
          )}
        </div>

        <button onClick={() => dismiss()}></button>
      </div>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

container

import { useEffect, useState } from "react";
import type { NotifyItem } from "./types";
import { subscribe } from "./store";
import { notifyPositionClasses } from "./constants";
import { cn } from "@/src/utils";
import { NotifyCard } from "./notify-card";
import { Potral } from "../overlay";

export function NotifyContainer() {
  const [item, setItem] = useState<NotifyItem | null>(null);
  const [visible, setVisible] = useState(false);
  const [renderItem, setRenderItem] = useState<NotifyItem | null>(null);

  useEffect(() => {
    return subscribe(setItem);
  }, []);

  useEffect(() => {
    if (item) {
      setRenderItem(item);

      requestAnimationFrame(() => {
        setVisible(true);
      });
    } else {
      setVisible(false);

      const timeout = setTimeout(() => {
        setRenderItem(null);
      }, 300);

      return () => clearTimeout(timeout);
    }
  }, [item]);

  if (!renderItem) return null;

  return (
    <Potral>
      <div
        key={renderItem.id}
        className={cn(
          "z-[9999] fixed transition-all duration-300",
          visible ? "translate-y-0 opacity-100" : "-translate-y-2 opacity-0",
          notifyPositionClasses[renderItem.position || "top-center"],
        )}
      >
        <NotifyCard item={renderItem} />
      </div>
    </Potral>
  );
}

Enter fullscreen mode Exit fullscreen mode

Store

import type { NotifyItem } from "./types";

type Listener = (item: NotifyItem | null) => void;
let memoryState: NotifyItem | null = null;
let listener: Listener | null = null;

function dispatch() {
  listener?.(memoryState);
}

export function subscribe(l: Listener) {
  listener = l;

  listener(memoryState);

  return () => {
    listener = null;
  };
}

export function notify(item: Omit<NotifyItem, "id">) {
  const newItem: NotifyItem = {
    id: crypto.randomUUID(),
    ...item,
  };

  memoryState = newItem;

  dispatch();

  return newItem.id;
}

export function dismiss() {
  memoryState = null;

  dispatch();
}

Enter fullscreen mode Exit fullscreen mode

types

export type NotifyPosition =
  | "top-left"
  | "top-center"
  | "top-right"
  | "bottom-left"
  | "bottom-center"
  | "bottom-right";

export type NotifyVariant =
  | "default"
  | "success"
  | "error"
  | "info"
  | "warning";

export interface NotifyItem {
  id: string;
  title?: string;
  description?: string;
  position?: NotifyPosition;
  variant?: NotifyVariant;
}

Enter fullscreen mode Exit fullscreen mode

calling

import { RouterProvider } from "react-router-dom";
import { router } from "./router";
import { NotifyContainer, ToastContainer } from "./components/base";

function App() {
  return (
    <>
      <RouterProvider router={router} />
      <ToastContainer />
      <NotifyContainer />
    </>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Top comments (0)