@mnee-ui/ui 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,185 +0,0 @@
1
- import { ChevronLeft, ChevronRight, Loader2 } from "lucide-react";
2
- import { cn } from "@/lib/utils";
3
-
4
- // ─── Compound table primitives ────────────────────────────────────────────────
5
-
6
- export function Table({ className, children, ...props }: React.HTMLAttributes<HTMLTableElement>) {
7
- return (
8
- <div className="w-full overflow-x-auto rounded-lg border border-gray-200 shadow-sm">
9
- <table className={cn("w-full border-collapse text-sm", className)} {...props}>
10
- {children}
11
- </table>
12
- </div>
13
- );
14
- }
15
-
16
- export function TableHead({ className, children, ...props }: React.HTMLAttributes<HTMLTableSectionElement>) {
17
- return (
18
- <thead className={cn("bg-gray-50 border-b border-gray-200", className)} {...props}>
19
- {children}
20
- </thead>
21
- );
22
- }
23
-
24
- export function TableBody({ className, children, ...props }: React.HTMLAttributes<HTMLTableSectionElement>) {
25
- return (
26
- <tbody className={cn("divide-y divide-gray-100", className)} {...props}>
27
- {children}
28
- </tbody>
29
- );
30
- }
31
-
32
- export function TableRow({ className, children, onClick, ...props }: React.HTMLAttributes<HTMLTableRowElement>) {
33
- return (
34
- <tr
35
- className={cn(
36
- "transition-colors",
37
- onClick ? "cursor-pointer hover:bg-gray-50" : "hover:bg-gray-50/50",
38
- className
39
- )}
40
- onClick={onClick}
41
- {...props}
42
- >
43
- {children}
44
- </tr>
45
- );
46
- }
47
-
48
- export interface TableHeaderProps extends React.ThHTMLAttributes<HTMLTableCellElement> {
49
- sortable?: boolean;
50
- sortDirection?: "asc" | "desc" | null;
51
- onSort?: () => void;
52
- }
53
-
54
- export function TableHeader({
55
- className,
56
- children,
57
- sortable,
58
- sortDirection,
59
- onSort,
60
- ...props
61
- }: TableHeaderProps) {
62
- return (
63
- <th
64
- className={cn(
65
- "px-4 py-3 text-left text-xs font-semibold text-gray-600 whitespace-nowrap",
66
- sortable && "select-none",
67
- className
68
- )}
69
- {...props}
70
- >
71
- {sortable ? (
72
- <button
73
- type="button"
74
- onClick={onSort}
75
- className="inline-flex items-center gap-1 hover:text-gray-900 transition-colors"
76
- >
77
- {children}
78
- <span className="text-gray-400">
79
- {sortDirection === "asc" ? "↑" : sortDirection === "desc" ? "↓" : "↕"}
80
- </span>
81
- </button>
82
- ) : (
83
- children
84
- )}
85
- </th>
86
- );
87
- }
88
-
89
- export function TableCell({ className, children, ...props }: React.TdHTMLAttributes<HTMLTableCellElement>) {
90
- return (
91
- <td className={cn("px-4 py-3 text-sm text-gray-700", className)} {...props}>
92
- {children}
93
- </td>
94
- );
95
- }
96
-
97
- // ─── Empty state ──────────────────────────────────────────────────────────────
98
-
99
- export interface TableEmptyProps {
100
- message?: string;
101
- description?: string;
102
- }
103
-
104
- export function TableEmpty({
105
- message = "No data",
106
- description,
107
- }: TableEmptyProps) {
108
- return (
109
- <tr>
110
- <td colSpan={999}>
111
- <div className="flex flex-col items-center justify-center gap-2 py-16 text-center">
112
- <p className="text-sm font-medium text-gray-700">{message}</p>
113
- {description && <p className="text-xs text-gray-400 max-w-xs">{description}</p>}
114
- </div>
115
- </td>
116
- </tr>
117
- );
118
- }
119
-
120
- // ─── Loading overlay ──────────────────────────────────────────────────────────
121
-
122
- export function TableLoading({ cols = 4 }: { cols?: number }) {
123
- return (
124
- <>
125
- {Array.from({ length: 4 }).map((_, r) => (
126
- <TableRow key={r}>
127
- {Array.from({ length: cols }).map((_, c) => (
128
- <TableCell key={c}>
129
- <div className="h-3.5 bg-gray-100 rounded animate-pulse" style={{ width: `${60 + ((r + c) % 3) * 15}%` }} />
130
- </TableCell>
131
- ))}
132
- </TableRow>
133
- ))}
134
- </>
135
- );
136
- }
137
-
138
- // ─── Pagination ───────────────────────────────────────────────────────────────
139
-
140
- export interface PaginationProps {
141
- page: number;
142
- totalPages: number;
143
- totalItems?: number;
144
- onPageChange: (page: number) => void;
145
- className?: string;
146
- }
147
-
148
- export function Pagination({ page, totalPages, totalItems, onPageChange, className }: PaginationProps) {
149
- return (
150
- <div className={cn("flex items-center justify-between px-4 py-3 border-t border-gray-200 text-sm text-gray-600", className)}>
151
- <span>
152
- {totalItems !== undefined
153
- ? `Page ${page} of ${totalPages} (${totalItems} items)`
154
- : `Page ${page} of ${totalPages}`}
155
- </span>
156
- <div className="flex items-center gap-1">
157
- <button
158
- type="button"
159
- onClick={() => onPageChange(page - 1)}
160
- disabled={page <= 1}
161
- className="flex items-center gap-1 px-2.5 py-1.5 rounded hover:bg-gray-100 disabled:opacity-40 disabled:pointer-events-none transition-colors"
162
- >
163
- <ChevronLeft size={14} />
164
- Previous
165
- </button>
166
- <span className="px-3 py-1.5 border border-gray-200 rounded text-xs font-medium min-w-[2rem] text-center">
167
- {page}
168
- </span>
169
- <button
170
- type="button"
171
- onClick={() => onPageChange(page + 1)}
172
- disabled={page >= totalPages}
173
- className="flex items-center gap-1 px-2.5 py-1.5 rounded hover:bg-gray-100 disabled:opacity-40 disabled:pointer-events-none transition-colors"
174
- >
175
- Next
176
- <ChevronRight size={14} />
177
- </button>
178
- </div>
179
- </div>
180
- );
181
- }
182
-
183
- // ─── Convenience loading spinner for table toolbar ────────────────────────────
184
-
185
- export { Loader2 };
@@ -1,136 +0,0 @@
1
- "use client";
2
-
3
- import { createContext, useContext, useRef, useState } from "react";
4
- import { X, CheckCircle, AlertTriangle, Info } from "lucide-react";
5
- import { cn } from "@/lib/utils";
6
-
7
- // ─── Types ────────────────────────────────────────────────────────────────────
8
-
9
- export type ToastType = "success" | "error" | "warning" | "info" | "default";
10
-
11
- export interface ToastContextType {
12
- showToast: (message: string, type?: ToastType) => void;
13
- }
14
-
15
- // ─── Context ──────────────────────────────────────────────────────────────────
16
-
17
- const ToastContext = createContext<ToastContextType | null>(null);
18
-
19
- // ─── useToast hook ────────────────────────────────────────────────────────────
20
-
21
- export function useToast(): ToastContextType {
22
- const ctx = useContext(ToastContext);
23
- if (!ctx) {
24
- throw new Error("useToast must be used inside <ToastProvider>");
25
- }
26
- return ctx;
27
- }
28
-
29
- // ─── Toast visual component ───────────────────────────────────────────────────
30
- // Renders the notification box only — no fixed positioning.
31
- // ToastProvider wraps it in the fixed top-right container.
32
-
33
- const toastStyles: Record<ToastType, { bg: string; text: string; icon: React.ReactNode }> = {
34
- success: {
35
- bg: "bg-success-bg",
36
- text: "text-success",
37
- icon: <CheckCircle size={18} className="text-success shrink-0" />,
38
- },
39
- error: {
40
- bg: "bg-error-bg",
41
- text: "text-error",
42
- icon: <AlertTriangle size={18} className="text-error shrink-0" />,
43
- },
44
- warning: {
45
- bg: "bg-warning-bg",
46
- text: "text-warning",
47
- icon: <AlertTriangle size={18} className="text-warning shrink-0" />,
48
- },
49
- info: {
50
- bg: "bg-info-bg",
51
- text: "text-info",
52
- icon: <Info size={18} className="text-info shrink-0" />,
53
- },
54
- default: {
55
- bg: "bg-gray-100",
56
- text: "text-gray-700",
57
- icon: <CheckCircle size={18} className="text-gray-500 shrink-0" />,
58
- },
59
- };
60
-
61
- export interface ToastProps {
62
- message: string;
63
- type: ToastType;
64
- onClose?: () => void;
65
- className?: string;
66
- }
67
-
68
- export function Toast({ message, type, onClose = () => {}, className }: ToastProps) {
69
- const { bg, text, icon } = toastStyles[type];
70
-
71
- return (
72
- <div
73
- role="alert"
74
- aria-live="polite"
75
- className={cn(
76
- "flex items-center gap-3 px-4 py-3 rounded-xl shadow-lg w-[380px]",
77
- bg,
78
- text,
79
- className
80
- )}
81
- >
82
- {icon}
83
- <span className="flex-1 text-sm font-medium">{message}</span>
84
- <button
85
- onClick={onClose}
86
- aria-label="Dismiss notification"
87
- className="opacity-60 hover:opacity-100 transition-opacity"
88
- >
89
- <X size={16} />
90
- </button>
91
- </div>
92
- );
93
- }
94
-
95
- // ─── ToastProvider ────────────────────────────────────────────────────────────
96
-
97
- export function ToastProvider({ children }: { children: React.ReactNode }) {
98
- const [toast, setToast] = useState<{ message: string; type: ToastType } | null>(null);
99
- const [isExiting, setIsExiting] = useState(false);
100
- const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
101
-
102
- const dismiss = () => {
103
- setIsExiting(true);
104
- if (timerRef.current) clearTimeout(timerRef.current);
105
- timerRef.current = setTimeout(() => {
106
- setToast(null);
107
- setIsExiting(false);
108
- }, 300);
109
- };
110
-
111
- const showToast = (message: string, type: ToastType = "default") => {
112
- if (timerRef.current) clearTimeout(timerRef.current);
113
- setIsExiting(false);
114
- setToast({ message, type });
115
- timerRef.current = setTimeout(dismiss, 4000);
116
- };
117
-
118
- return (
119
- <ToastContext.Provider value={{ showToast }}>
120
- {children}
121
- {toast && (
122
- <div
123
- className="fixed top-6 right-6"
124
- style={{
125
- zIndex: 9999,
126
- animation: isExiting
127
- ? "slideOutToast 0.3s ease-in forwards"
128
- : "slideInToast 0.3s ease-out",
129
- }}
130
- >
131
- <Toast message={toast.message} type={toast.type} onClose={dismiss} />
132
- </div>
133
- )}
134
- </ToastContext.Provider>
135
- );
136
- }