Add Oudelaa dashboard API integration
فشلت بعض الفحوصات
Deploy To Ghaymah / deploy (push) Has been cancelled
فشلت بعض الفحوصات
Deploy To Ghaymah / deploy (push) Has been cancelled
هذا الالتزام موجود في:
30
oudelaa_dashboard/components/ui/badge.tsx
Normal file
30
oudelaa_dashboard/components/ui/badge.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-full px-2.5 py-1 text-xs font-medium",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary/20 text-primary",
|
||||
success: "bg-emerald-500/15 text-emerald-300",
|
||||
warning: "bg-amber-500/15 text-amber-200",
|
||||
danger: "bg-rose-500/15 text-rose-200",
|
||||
muted: "bg-secondary text-muted-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants };
|
||||
49
oudelaa_dashboard/components/ui/button.tsx
Normal file
49
oudelaa_dashboard/components/ui/button.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 rounded-lg font-semibold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "bg-transparent text-foreground hover:bg-secondary/60",
|
||||
outline: "border border-border bg-transparent text-foreground hover:bg-secondary/60",
|
||||
danger: "bg-red-900/70 text-red-100 hover:bg-red-800/80",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2 text-sm",
|
||||
sm: "h-8 rounded-md px-3 text-xs",
|
||||
lg: "h-11 px-5 text-sm",
|
||||
icon: "h-9 w-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, type, ...props }, ref) => {
|
||||
return (
|
||||
<button
|
||||
type={type ?? "button"}
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button, buttonVariants };
|
||||
30
oudelaa_dashboard/components/ui/card.tsx
Normal file
30
oudelaa_dashboard/components/ui/card.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("frame-panel gold-edge", className)} {...props} />
|
||||
));
|
||||
Card.displayName = "Card";
|
||||
|
||||
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("flex flex-col gap-1.5 p-5", className)} {...props} />
|
||||
));
|
||||
CardHeader.displayName = "CardHeader";
|
||||
|
||||
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(({ className, ...props }, ref) => (
|
||||
<h3 ref={ref} className={cn("font-heading text-lg font-semibold tracking-tight", className)} {...props} />
|
||||
));
|
||||
CardTitle.displayName = "CardTitle";
|
||||
|
||||
const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
|
||||
({ className, ...props }, ref) => <p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />,
|
||||
);
|
||||
CardDescription.displayName = "CardDescription";
|
||||
|
||||
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-5 pt-0", className)} {...props} />
|
||||
));
|
||||
CardContent.displayName = "CardContent";
|
||||
|
||||
export { Card, CardContent, CardDescription, CardHeader, CardTitle };
|
||||
62
oudelaa_dashboard/components/ui/dialog.tsx
Normal file
62
oudelaa_dashboard/components/ui/dialog.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Dialog = DialogPrimitive.Root;
|
||||
const DialogTrigger = DialogPrimitive.Trigger;
|
||||
const DialogPortal = DialogPrimitive.Portal;
|
||||
const DialogClose = DialogPrimitive.Close;
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn("fixed inset-0 z-50 bg-black/70 backdrop-blur-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
DialogOverlay.displayName = "DialogOverlay";
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-1/2 top-1/2 z-50 grid w-[95vw] max-w-xl -translate-x-1/2 -translate-y-1/2 gap-4 rounded-2xl border border-border bg-card p-6 shadow-glow",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogClose className="absolute left-4 top-4 rounded-md p-1 text-muted-foreground hover:text-foreground">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
));
|
||||
DialogContent.displayName = "DialogContent";
|
||||
|
||||
function DialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return <div className={cn("space-y-2 text-right", className)} {...props} />;
|
||||
}
|
||||
|
||||
function DialogTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
|
||||
return <h2 className={cn("font-heading text-xl font-bold", className)} {...props} />;
|
||||
}
|
||||
|
||||
function DialogDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
|
||||
return <p className={cn("text-sm text-muted-foreground", className)} {...props} />;
|
||||
}
|
||||
|
||||
export { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger };
|
||||
58
oudelaa_dashboard/components/ui/drawer.tsx
Normal file
58
oudelaa_dashboard/components/ui/drawer.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
type DrawerProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
title: string;
|
||||
description?: string;
|
||||
children: ReactNode;
|
||||
side?: "left" | "right";
|
||||
widthClassName?: string;
|
||||
};
|
||||
|
||||
export function Drawer({
|
||||
open,
|
||||
onClose,
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
side = "left",
|
||||
widthClassName,
|
||||
}: DrawerProps) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cn(
|
||||
"fixed inset-0 z-40 bg-black/50 backdrop-blur-sm transition",
|
||||
open ? "opacity-100" : "pointer-events-none opacity-0",
|
||||
)}
|
||||
onClick={onClose}
|
||||
/>
|
||||
<aside
|
||||
className={cn(
|
||||
"fixed bottom-0 top-0 z-50 w-[96vw] max-w-md border border-border bg-card p-5 shadow-glow transition",
|
||||
widthClassName,
|
||||
side === "left" ? "left-0" : "right-0",
|
||||
open ? "translate-x-0" : side === "left" ? "-translate-x-full" : "translate-x-full",
|
||||
)}
|
||||
>
|
||||
<div className="mb-5 flex items-start justify-between">
|
||||
<div className="space-y-1">
|
||||
<h3 className="font-heading text-lg font-bold">{title}</h3>
|
||||
{description ? <p className="text-sm text-muted-foreground">{description}</p> : null}
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" onClick={onClose}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="h-[calc(100%-72px)] overflow-y-auto">{children}</div>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
21
oudelaa_dashboard/components/ui/empty-state.tsx
Normal file
21
oudelaa_dashboard/components/ui/empty-state.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Music2 } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type EmptyStateProps = {
|
||||
title: string;
|
||||
description: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function EmptyState({ title, description, className }: EmptyStateProps) {
|
||||
return (
|
||||
<div className={cn("ornament-grid shimmer-empty rounded-xl border border-dashed border-border p-8 text-center", className)}>
|
||||
<div className="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-secondary text-primary">
|
||||
<Music2 className="h-5 w-5" />
|
||||
</div>
|
||||
<h3 className="font-heading text-lg font-semibold">{title}</h3>
|
||||
<p className="mx-auto mt-1 max-w-md text-sm text-muted-foreground">{description}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
20
oudelaa_dashboard/components/ui/input.tsx
Normal file
20
oudelaa_dashboard/components/ui/input.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-lg border border-input bg-background/60 px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Input.displayName = "Input";
|
||||
|
||||
export { Input };
|
||||
71
oudelaa_dashboard/components/ui/select.tsx
Normal file
71
oudelaa_dashboard/components/ui/select.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||
import { Check, ChevronDown } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Select = SelectPrimitive.Root;
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-lg border border-input bg-background/60 px-3 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
));
|
||||
SelectTrigger.displayName = "SelectTrigger";
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn("z-50 min-w-[8rem] overflow-hidden rounded-lg border border-border bg-popover text-popover-foreground", className)}
|
||||
{...props}
|
||||
>
|
||||
<SelectPrimitive.Viewport className="p-1">{children}</SelectPrimitive.Viewport>
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
));
|
||||
SelectContent.displayName = "SelectContent";
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-md py-2 pl-8 pr-3 text-sm outline-none hover:bg-secondary",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
));
|
||||
SelectItem.displayName = "SelectItem";
|
||||
|
||||
const SelectValue = SelectPrimitive.Value;
|
||||
|
||||
export { Select, SelectContent, SelectItem, SelectTrigger, SelectValue };
|
||||
9
oudelaa_dashboard/components/ui/separator.tsx
Normal file
9
oudelaa_dashboard/components/ui/separator.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Separator({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return <div role="separator" className={cn("h-px w-full bg-border", className)} {...props} />;
|
||||
}
|
||||
|
||||
export { Separator };
|
||||
32
oudelaa_dashboard/components/ui/switch.tsx
Normal file
32
oudelaa_dashboard/components/ui/switch.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type SwitchProps = {
|
||||
checked: boolean;
|
||||
onCheckedChange: (value: boolean) => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function Switch({ checked, onCheckedChange, className }: SwitchProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={checked}
|
||||
onClick={() => onCheckedChange(!checked)}
|
||||
className={cn(
|
||||
"relative h-6 w-11 rounded-full border border-border transition",
|
||||
checked ? "bg-primary" : "bg-secondary",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
"absolute top-0.5 h-[18px] w-[18px] rounded-full bg-background transition",
|
||||
checked ? "right-0.5" : "right-[22px]",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
37
oudelaa_dashboard/components/ui/table.tsx
Normal file
37
oudelaa_dashboard/components/ui/table.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(({ className, ...props }, ref) => (
|
||||
<div className="w-full overflow-auto">
|
||||
<table ref={ref} className={cn("w-full caption-bottom text-sm", className)} {...props} />
|
||||
</div>
|
||||
));
|
||||
Table.displayName = "Table";
|
||||
|
||||
const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b [&_tr]:border-border", className)} {...props} />
|
||||
));
|
||||
TableHeader.displayName = "TableHeader";
|
||||
|
||||
const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(({ className, ...props }, ref) => (
|
||||
<tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />
|
||||
));
|
||||
TableBody.displayName = "TableBody";
|
||||
|
||||
const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(({ className, ...props }, ref) => (
|
||||
<tr ref={ref} className={cn("border-b border-border/70 transition hover:bg-secondary/40", className)} {...props} />
|
||||
));
|
||||
TableRow.displayName = "TableRow";
|
||||
|
||||
const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(({ className, ...props }, ref) => (
|
||||
<th ref={ref} className={cn("h-11 px-4 text-right align-middle font-medium text-muted-foreground", className)} {...props} />
|
||||
));
|
||||
TableHead.displayName = "TableHead";
|
||||
|
||||
const TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(({ className, ...props }, ref) => (
|
||||
<td ref={ref} className={cn("p-4 align-middle", className)} {...props} />
|
||||
));
|
||||
TableCell.displayName = "TableCell";
|
||||
|
||||
export { Table, TableBody, TableCell, TableHead, TableHeader, TableRow };
|
||||
22
oudelaa_dashboard/components/ui/textarea.tsx
Normal file
22
oudelaa_dashboard/components/ui/textarea.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, React.TextareaHTMLAttributes<HTMLTextAreaElement>>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"min-h-[90px] w-full rounded-lg border border-input bg-background/60 px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Textarea.displayName = "Textarea";
|
||||
|
||||
export { Textarea };
|
||||
80
oudelaa_dashboard/components/ui/toast.tsx
Normal file
80
oudelaa_dashboard/components/ui/toast.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { X } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type ToastVariant = "default" | "success" | "warning" | "danger";
|
||||
|
||||
type ToastItem = {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
variant?: ToastVariant;
|
||||
};
|
||||
|
||||
type ToastContextValue = {
|
||||
toast: (item: Omit<ToastItem, "id">) => void;
|
||||
};
|
||||
|
||||
const ToastContext = React.createContext<ToastContextValue | null>(null);
|
||||
|
||||
function getToastId() {
|
||||
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
return `toast_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
||||
}
|
||||
|
||||
const variantStyles: Record<ToastVariant, string> = {
|
||||
default: "border-border bg-card text-foreground",
|
||||
success: "border-emerald-500/40 bg-emerald-500/10 text-emerald-100",
|
||||
warning: "border-amber-500/40 bg-amber-500/10 text-amber-100",
|
||||
danger: "border-rose-500/40 bg-rose-500/10 text-rose-100",
|
||||
};
|
||||
|
||||
export function ToastProvider({ children }: { children: React.ReactNode }) {
|
||||
const [toasts, setToasts] = React.useState<ToastItem[]>([]);
|
||||
|
||||
const toast = React.useCallback((item: Omit<ToastItem, "id">) => {
|
||||
const id = getToastId();
|
||||
setToasts((prev) => [...prev, { id, ...item }]);
|
||||
window.setTimeout(() => {
|
||||
setToasts((prev) => prev.filter((toastItem) => toastItem.id !== id));
|
||||
}, 3200);
|
||||
}, []);
|
||||
|
||||
const remove = React.useCallback((id: string) => {
|
||||
setToasts((prev) => prev.filter((toastItem) => toastItem.id !== id));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={{ toast }}>
|
||||
{children}
|
||||
<div className="fixed bottom-6 left-6 z-50 flex w-[90vw] max-w-sm flex-col gap-3">
|
||||
{toasts.map((item) => (
|
||||
<div key={item.id} className={cn("frame-panel border px-4 py-3 shadow-glow", variantStyles[item.variant ?? "default"])}>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-sm font-semibold">{item.title}</p>
|
||||
{item.description ? <p className="mt-1 text-xs text-muted-foreground">{item.description}</p> : null}
|
||||
</div>
|
||||
<button className="rounded-md p-1 text-muted-foreground hover:text-foreground" onClick={() => remove(item.id)}>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useToast() {
|
||||
const context = React.useContext(ToastContext);
|
||||
if (!context) {
|
||||
throw new Error("useToast must be used within ToastProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
المرجع في مشكلة جديدة
حظر مستخدم