@djangocfg/ui-core 2.1.90 → 2.1.91

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.
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Log Store
3
+ *
4
+ * Zustand store for log accumulation and filtering.
5
+ * Keeps logs in memory for Console panel display.
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import { create } from 'zustand';
11
+ import type { LogStore, LogEntry, LogFilter } from './types';
12
+
13
+ const DEFAULT_FILTER: LogFilter = {
14
+ levels: ['debug', 'info', 'warn', 'error', 'success'],
15
+ component: undefined,
16
+ search: undefined,
17
+ since: undefined,
18
+ };
19
+
20
+ const MAX_LOGS = 1000;
21
+
22
+ function generateId(): string {
23
+ return `log-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
24
+ }
25
+
26
+ function matchesFilter(entry: LogEntry, filter: LogFilter): boolean {
27
+ // Level filter
28
+ if (!filter.levels.includes(entry.level)) {
29
+ return false;
30
+ }
31
+
32
+ // Component filter (case-insensitive partial match)
33
+ if (filter.component) {
34
+ const comp = filter.component.toLowerCase();
35
+ if (!entry.component.toLowerCase().includes(comp)) {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ // Search filter (case-insensitive, searches message and data)
41
+ if (filter.search) {
42
+ const search = filter.search.toLowerCase();
43
+ const inMessage = entry.message.toLowerCase().includes(search);
44
+ const inData = entry.data
45
+ ? JSON.stringify(entry.data).toLowerCase().includes(search)
46
+ : false;
47
+ if (!inMessage && !inData) {
48
+ return false;
49
+ }
50
+ }
51
+
52
+ // Time filter
53
+ if (filter.since && entry.timestamp < filter.since) {
54
+ return false;
55
+ }
56
+
57
+ return true;
58
+ }
59
+
60
+ export const useLogStore = create<LogStore>((set, get) => ({
61
+ logs: [],
62
+ filter: DEFAULT_FILTER,
63
+ maxLogs: MAX_LOGS,
64
+
65
+ addLog: (entry) => {
66
+ const newEntry: LogEntry = {
67
+ ...entry,
68
+ id: generateId(),
69
+ timestamp: new Date(),
70
+ };
71
+
72
+ set((state) => {
73
+ const newLogs = [...state.logs, newEntry];
74
+ // Trim to max size
75
+ if (newLogs.length > state.maxLogs) {
76
+ return { logs: newLogs.slice(-state.maxLogs) };
77
+ }
78
+ return { logs: newLogs };
79
+ });
80
+ },
81
+
82
+ clearLogs: () => {
83
+ set({ logs: [] });
84
+ },
85
+
86
+ setFilter: (filter) => {
87
+ set((state) => ({
88
+ filter: { ...state.filter, ...filter },
89
+ }));
90
+ },
91
+
92
+ resetFilter: () => {
93
+ set({ filter: DEFAULT_FILTER });
94
+ },
95
+
96
+ getFilteredLogs: () => {
97
+ const { logs, filter } = get();
98
+ return logs.filter((entry) => matchesFilter(entry, filter));
99
+ },
100
+
101
+ exportLogs: () => {
102
+ const { logs } = get();
103
+ return JSON.stringify(logs, null, 2);
104
+ },
105
+ }));
106
+
107
+ // Selector hooks for performance
108
+ export const useFilteredLogs = () => {
109
+ const logs = useLogStore((state) => state.logs);
110
+ const filter = useLogStore((state) => state.filter);
111
+ return logs.filter((entry) => matchesFilter(entry, filter));
112
+ };
113
+
114
+ export const useLogCount = () => {
115
+ return useLogStore((state) => state.logs.length);
116
+ };
117
+
118
+ export const useErrorCount = () => {
119
+ return useLogStore((state) =>
120
+ state.logs.filter((log) => log.level === 'error').length
121
+ );
122
+ };
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Logger
3
+ *
4
+ * Universal logger with consola + zustand store integration.
5
+ * Logs are accumulated for Console panel display.
6
+ * In production, only logs to store (no console output).
7
+ */
8
+
9
+ 'use client';
10
+
11
+ import { consola, type ConsolaInstance } from 'consola';
12
+ import { useLogStore } from './logStore';
13
+ import type { Logger, LogLevel, MediaLogger } from './types';
14
+
15
+ // Check environment
16
+ const isDev = process.env.NODE_ENV !== 'production';
17
+ const isBrowser = typeof window !== 'undefined';
18
+
19
+ // Create base consola instance
20
+ const baseConsola = consola.create({
21
+ level: isDev ? 4 : 2, // 4 = debug, 2 = warn
22
+ });
23
+
24
+ /**
25
+ * Extract error details from unknown error type
26
+ */
27
+ function extractErrorData(data?: Record<string, unknown>): {
28
+ cleanData: Record<string, unknown> | undefined;
29
+ stack: string | undefined;
30
+ } {
31
+ if (!data) return { cleanData: undefined, stack: undefined };
32
+
33
+ const cleanData = { ...data };
34
+ let stack: string | undefined;
35
+
36
+ // Extract stack from error object
37
+ if (data.error instanceof Error) {
38
+ stack = data.error.stack;
39
+ cleanData.error = {
40
+ name: data.error.name,
41
+ message: data.error.message,
42
+ };
43
+ } else if (typeof data.error === 'object' && data.error !== null) {
44
+ const errObj = data.error as Record<string, unknown>;
45
+ if (typeof errObj.stack === 'string') {
46
+ stack = errObj.stack;
47
+ }
48
+ if (typeof errObj.message === 'string') {
49
+ cleanData.error = errObj.message;
50
+ }
51
+ }
52
+
53
+ return { cleanData, stack };
54
+ }
55
+
56
+ /**
57
+ * Format buffer ranges for logging
58
+ */
59
+ function formatBufferRanges(buffered: TimeRanges, duration: number): string {
60
+ if (buffered.length === 0) return 'empty';
61
+
62
+ const ranges: string[] = [];
63
+ for (let i = 0; i < buffered.length; i++) {
64
+ const start = buffered.start(i);
65
+ const end = buffered.end(i);
66
+ const percent = ((end - start) / duration * 100).toFixed(1);
67
+ ranges.push(`${start.toFixed(1)}-${end.toFixed(1)}s (${percent}%)`);
68
+ }
69
+ return ranges.join(', ');
70
+ }
71
+
72
+ /**
73
+ * Create a logger for a specific component/module
74
+ */
75
+ export function createLogger(component: string): Logger {
76
+ const consolaTagged = baseConsola.withTag(component);
77
+
78
+ const log = (level: LogLevel, message: string, data?: Record<string, unknown>) => {
79
+ const { cleanData, stack } = extractErrorData(data);
80
+
81
+ // Add to store (for Console panel)
82
+ if (isBrowser) {
83
+ useLogStore.getState().addLog({
84
+ level,
85
+ component,
86
+ message,
87
+ data: cleanData,
88
+ stack,
89
+ });
90
+ }
91
+
92
+ // Log to console via consola (in dev mode)
93
+ if (isDev) {
94
+ const consolaMethod = level === 'success' ? 'success' : level;
95
+ if (cleanData) {
96
+ consolaTagged[consolaMethod](message, cleanData);
97
+ } else {
98
+ consolaTagged[consolaMethod](message);
99
+ }
100
+ }
101
+ };
102
+
103
+ return {
104
+ debug: (message, data) => log('debug', message, data),
105
+ info: (message, data) => log('info', message, data),
106
+ warn: (message, data) => log('warn', message, data),
107
+ error: (message, data) => log('error', message, data),
108
+ success: (message, data) => log('success', message, data),
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Create a media-specific logger with helper methods
114
+ * for AudioPlayer, VideoPlayer, ImageViewer
115
+ */
116
+ export function createMediaLogger(component: string): MediaLogger {
117
+ const baseLogger = createLogger(component);
118
+
119
+ return {
120
+ ...baseLogger,
121
+
122
+ load: (src: string, type?: string) => {
123
+ const typeStr = type ? ` (${type})` : '';
124
+ baseLogger.info(`LOAD: ${src}${typeStr}`);
125
+ },
126
+
127
+ state: (state: string, details?: Record<string, unknown>) => {
128
+ baseLogger.debug(`STATE: ${state}`, details);
129
+ },
130
+
131
+ seek: (from: number, to: number, duration: number) => {
132
+ baseLogger.debug(`SEEK: ${from.toFixed(2)}s -> ${to.toFixed(2)}s`, {
133
+ from,
134
+ to,
135
+ duration,
136
+ progress: `${((to / duration) * 100).toFixed(1)}%`,
137
+ });
138
+ },
139
+
140
+ buffer: (buffered: TimeRanges, duration: number) => {
141
+ if (buffered.length > 0) {
142
+ baseLogger.debug(`BUFFER: ${formatBufferRanges(buffered, duration)}`);
143
+ }
144
+ },
145
+
146
+ event: (name: string, data?: unknown) => {
147
+ if (data !== undefined) {
148
+ baseLogger.debug(`EVENT: ${name}`, { data });
149
+ } else {
150
+ baseLogger.debug(`EVENT: ${name}`);
151
+ }
152
+ },
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Global logger for non-component code
158
+ */
159
+ export const logger = createLogger('App');
160
+
161
+ /**
162
+ * Quick access for one-off logs
163
+ */
164
+ export const log = {
165
+ debug: (component: string, message: string, data?: Record<string, unknown>) =>
166
+ createLogger(component).debug(message, data),
167
+ info: (component: string, message: string, data?: Record<string, unknown>) =>
168
+ createLogger(component).info(message, data),
169
+ warn: (component: string, message: string, data?: Record<string, unknown>) =>
170
+ createLogger(component).warn(message, data),
171
+ error: (component: string, message: string, data?: Record<string, unknown>) =>
172
+ createLogger(component).error(message, data),
173
+ success: (component: string, message: string, data?: Record<string, unknown>) =>
174
+ createLogger(component).success(message, data),
175
+ };
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Logger Types
3
+ *
4
+ * Type definitions for the universal logging system.
5
+ * Compatible with Console panel in FileWorkspace IDE layout.
6
+ */
7
+
8
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'success';
9
+
10
+ export interface LogEntry {
11
+ /** Unique log ID */
12
+ id: string;
13
+ /** Timestamp when log was created */
14
+ timestamp: Date;
15
+ /** Log level */
16
+ level: LogLevel;
17
+ /** Component/module name that created the log */
18
+ component: string;
19
+ /** Log message */
20
+ message: string;
21
+ /** Additional data (objects, errors, etc.) */
22
+ data?: Record<string, unknown>;
23
+ /** Error stack trace (for error level) */
24
+ stack?: string;
25
+ }
26
+
27
+ export interface LogFilter {
28
+ /** Filter by log levels */
29
+ levels: LogLevel[];
30
+ /** Filter by component name (partial match) */
31
+ component?: string;
32
+ /** Filter by message text (partial match) */
33
+ search?: string;
34
+ /** Filter by time range */
35
+ since?: Date;
36
+ }
37
+
38
+ export interface LogStore {
39
+ /** All accumulated logs */
40
+ logs: LogEntry[];
41
+ /** Current filter settings */
42
+ filter: LogFilter;
43
+ /** Maximum logs to keep */
44
+ maxLogs: number;
45
+
46
+ /** Add new log entry */
47
+ addLog: (entry: Omit<LogEntry, 'id' | 'timestamp'>) => void;
48
+ /** Clear all logs */
49
+ clearLogs: () => void;
50
+ /** Update filter settings */
51
+ setFilter: (filter: Partial<LogFilter>) => void;
52
+ /** Reset filter to defaults */
53
+ resetFilter: () => void;
54
+ /** Get filtered logs */
55
+ getFilteredLogs: () => LogEntry[];
56
+ /** Export logs as JSON string */
57
+ exportLogs: () => string;
58
+ }
59
+
60
+ export interface Logger {
61
+ debug: (message: string, data?: Record<string, unknown>) => void;
62
+ info: (message: string, data?: Record<string, unknown>) => void;
63
+ warn: (message: string, data?: Record<string, unknown>) => void;
64
+ error: (message: string, data?: Record<string, unknown>) => void;
65
+ success: (message: string, data?: Record<string, unknown>) => void;
66
+ }
67
+
68
+ /**
69
+ * Media-specific log helper methods
70
+ */
71
+ export interface MediaLogger extends Logger {
72
+ /** Log source load event */
73
+ load: (src: string, type?: string) => void;
74
+ /** Log state change */
75
+ state: (state: string, details?: Record<string, unknown>) => void;
76
+ /** Log seek event */
77
+ seek: (from: number, to: number, duration: number) => void;
78
+ /** Log buffer ranges */
79
+ buffer: (buffered: TimeRanges, duration: number) => void;
80
+ /** Log generic event */
81
+ event: (name: string, data?: unknown) => void;
82
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * LazyComponent - Universal lazy loading wrapper
3
+ *
4
+ * Provides consistent Suspense handling for lazy-loaded components
5
+ * Works in any React environment: Next.js, Vite, Wails, CRA
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import React, { Suspense, ReactNode, ComponentType, lazy } from 'react';
11
+
12
+ // Default loading spinner
13
+ export const DefaultLoader = () => (
14
+ <div className="flex items-center justify-center p-8">
15
+ <div className="flex flex-col items-center gap-2">
16
+ <div className="h-8 w-8 animate-spin rounded-full border-4 border-muted border-t-primary" />
17
+ <span className="text-sm text-muted-foreground">Loading...</span>
18
+ </div>
19
+ </div>
20
+ );
21
+
22
+ // Minimal loader for inline components
23
+ export const InlineLoader = () => (
24
+ <span className="inline-flex items-center gap-1">
25
+ <span className="h-3 w-3 animate-spin rounded-full border-2 border-muted border-t-primary" />
26
+ <span className="text-xs text-muted-foreground">Loading...</span>
27
+ </span>
28
+ );
29
+
30
+ // Full-screen loader
31
+ export const FullScreenLoader = () => (
32
+ <div className="flex items-center justify-center min-h-screen">
33
+ <div className="text-center">
34
+ <div className="inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent" />
35
+ <p className="mt-4 text-sm text-muted-foreground">Loading...</p>
36
+ </div>
37
+ </div>
38
+ );
39
+
40
+ export interface LazyWrapperProps {
41
+ children: ReactNode;
42
+ fallback?: ReactNode;
43
+ }
44
+
45
+ /**
46
+ * Suspense wrapper with default fallback
47
+ */
48
+ export function LazyWrapper({ children, fallback }: LazyWrapperProps) {
49
+ return (
50
+ <Suspense fallback={fallback ?? <DefaultLoader />}>
51
+ {children}
52
+ </Suspense>
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Create a lazy-loaded component with Suspense wrapper built-in
58
+ *
59
+ * @example
60
+ * ```tsx
61
+ * const HeavyComponent = createLazyComponent(
62
+ * () => import('./HeavyComponent'),
63
+ * <CustomLoader />
64
+ * );
65
+ *
66
+ * // Usage - no need to wrap in Suspense
67
+ * <HeavyComponent someProps={value} />
68
+ * ```
69
+ */
70
+ export function createLazyComponent<T extends ComponentType<any>>(
71
+ importFn: () => Promise<{ default: T }>,
72
+ fallback?: ReactNode
73
+ ) {
74
+ const LazyComponent = lazy(importFn);
75
+
76
+ return function WrappedLazyComponent(props: React.ComponentProps<T>) {
77
+ return (
78
+ <Suspense fallback={fallback ?? <DefaultLoader />}>
79
+ <LazyComponent {...props} />
80
+ </Suspense>
81
+ );
82
+ };
83
+ }
84
+
85
+ /**
86
+ * Create a lazy-loaded component from a named export
87
+ *
88
+ * @example
89
+ * ```tsx
90
+ * const NamedComponent = createLazyNamedComponent(
91
+ * () => import('./module'),
92
+ * 'NamedExport',
93
+ * <CustomLoader />
94
+ * );
95
+ * ```
96
+ */
97
+ export function createLazyNamedComponent<
98
+ T extends Record<string, ComponentType<any>>,
99
+ K extends keyof T
100
+ >(
101
+ importFn: () => Promise<T>,
102
+ exportName: K,
103
+ fallback?: ReactNode
104
+ ) {
105
+ const LazyComponent = lazy(() =>
106
+ importFn().then((mod) => ({ default: mod[exportName] }))
107
+ );
108
+
109
+ return function WrappedLazyComponent(props: React.ComponentProps<T[K]>) {
110
+ return (
111
+ <Suspense fallback={fallback ?? <DefaultLoader />}>
112
+ <LazyComponent {...props} />
113
+ </Suspense>
114
+ );
115
+ };
116
+ }
@@ -0,0 +1,9 @@
1
+ export {
2
+ LazyWrapper,
3
+ DefaultLoader,
4
+ InlineLoader,
5
+ FullScreenLoader,
6
+ createLazyComponent,
7
+ createLazyNamedComponent,
8
+ } from './LazyComponent';
9
+ export type { LazyWrapperProps } from './LazyComponent';
@@ -1,144 +0,0 @@
1
- "use client"
2
-
3
- import { cva, type VariantProps } from 'class-variance-authority';
4
- import { X } from 'lucide-react';
5
- import * as React from 'react';
6
-
7
- import * as ToastPrimitives from '@radix-ui/react-toast';
8
-
9
- import { cn } from '../lib/utils';
10
-
11
- const ToastProvider = ToastPrimitives.Provider
12
-
13
- const ToastViewport = React.forwardRef<
14
- React.ElementRef<typeof ToastPrimitives.Viewport>,
15
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
16
- >(({ className, ...props }, ref) => (
17
- <ToastPrimitives.Viewport
18
- ref={ref}
19
- className={cn(
20
- "fixed bottom-0 right-0 z-9999 flex max-h-screen w-full flex-col p-4 md:max-w-md",
21
- className
22
- )}
23
- {...props}
24
- />
25
- ))
26
- ToastViewport.displayName = ToastPrimitives.Viewport.displayName
27
-
28
- const toastVariants = cva(
29
- "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-bottom-full",
30
- {
31
- variants: {
32
- variant: {
33
- default: "border bg-background text-foreground",
34
- destructive:
35
- "destructive group border-destructive bg-destructive text-destructive-foreground",
36
- success:
37
- "success group border-green-600 bg-green-500 text-white",
38
- warning:
39
- "warning group border-yellow-600 bg-yellow-500 text-black",
40
- info:
41
- "info group border-blue-600 bg-blue-500 text-white",
42
- },
43
- },
44
- defaultVariants: {
45
- variant: "default",
46
- },
47
- }
48
- )
49
-
50
- const Toast = React.forwardRef<
51
- React.ElementRef<typeof ToastPrimitives.Root>,
52
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
53
- VariantProps<typeof toastVariants>
54
- >(({ className, variant, ...props }, ref) => {
55
- return (
56
- <ToastPrimitives.Root
57
- ref={ref}
58
- className={cn(toastVariants({ variant }), className)}
59
- {...props}
60
- />
61
- )
62
- })
63
- Toast.displayName = ToastPrimitives.Root.displayName
64
-
65
- const ToastAction = React.forwardRef<
66
- React.ElementRef<typeof ToastPrimitives.Action>,
67
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
68
- >(({ className, ...props }, ref) => (
69
- <ToastPrimitives.Action
70
- ref={ref}
71
- className={cn(
72
- "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
73
- "group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
74
- "group-[.success]:border-green-300/40 group-[.success]:hover:bg-green-600 group-[.success]:hover:text-white group-[.success]:focus:ring-green-400",
75
- "group-[.warning]:border-yellow-600/40 group-[.warning]:hover:bg-yellow-600 group-[.warning]:hover:text-black group-[.warning]:focus:ring-yellow-400",
76
- "group-[.info]:border-blue-300/40 group-[.info]:hover:bg-blue-600 group-[.info]:hover:text-white group-[.info]:focus:ring-blue-400",
77
- className
78
- )}
79
- {...props}
80
- />
81
- ))
82
- ToastAction.displayName = ToastPrimitives.Action.displayName
83
-
84
- const ToastClose = React.forwardRef<
85
- React.ElementRef<typeof ToastPrimitives.Close>,
86
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
87
- >(({ className, ...props }, ref) => (
88
- <ToastPrimitives.Close
89
- ref={ref}
90
- className={cn(
91
- "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100",
92
- "group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
93
- "group-[.success]:text-green-100 group-[.success]:hover:text-white group-[.success]:focus:ring-green-300",
94
- "group-[.warning]:text-yellow-800 group-[.warning]:hover:text-black group-[.warning]:focus:ring-yellow-600",
95
- "group-[.info]:text-blue-100 group-[.info]:hover:text-white group-[.info]:focus:ring-blue-300",
96
- className
97
- )}
98
- toast-close=""
99
- {...props}
100
- >
101
- <X className="h-4 w-4" />
102
- </ToastPrimitives.Close>
103
- ))
104
- ToastClose.displayName = ToastPrimitives.Close.displayName
105
-
106
- const ToastTitle = React.forwardRef<
107
- React.ElementRef<typeof ToastPrimitives.Title>,
108
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
109
- >(({ className, ...props }, ref) => (
110
- <ToastPrimitives.Title
111
- ref={ref}
112
- className={cn("text-sm font-semibold", className)}
113
- {...props}
114
- />
115
- ))
116
- ToastTitle.displayName = ToastPrimitives.Title.displayName
117
-
118
- const ToastDescription = React.forwardRef<
119
- React.ElementRef<typeof ToastPrimitives.Description>,
120
- React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
121
- >(({ className, ...props }, ref) => (
122
- <ToastPrimitives.Description
123
- ref={ref}
124
- className={cn("text-sm opacity-90", className)}
125
- {...props}
126
- />
127
- ))
128
- ToastDescription.displayName = ToastPrimitives.Description.displayName
129
-
130
- type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
131
-
132
- type ToastActionElement = React.ReactElement<typeof ToastAction>
133
-
134
- export {
135
- type ToastProps,
136
- type ToastActionElement,
137
- ToastProvider,
138
- ToastViewport,
139
- Toast,
140
- ToastTitle,
141
- ToastDescription,
142
- ToastClose,
143
- ToastAction,
144
- }
@@ -1,41 +0,0 @@
1
- "use client"
2
-
3
- import { useEffect, useState } from 'react';
4
-
5
- import { useToast } from '../hooks/useToast';
6
- import {
7
- Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport
8
- } from './toast';
9
-
10
- export function Toaster() {
11
- const { toasts } = useToast()
12
- const [isMounted, setIsMounted] = useState(false)
13
-
14
- useEffect(() => {
15
- setIsMounted(true)
16
- }, [])
17
-
18
- if (!isMounted) {
19
- return null
20
- }
21
-
22
- return (
23
- <ToastProvider>
24
- {toasts.map(function ({ id, title, description, action, ...props }) {
25
- return (
26
- <Toast key={id} {...props}>
27
- <div className="grid gap-1">
28
- {title && <ToastTitle>{title}</ToastTitle>}
29
- {description && (
30
- <ToastDescription>{description}</ToastDescription>
31
- )}
32
- </div>
33
- {action}
34
- <ToastClose />
35
- </Toast>
36
- )
37
- })}
38
- <ToastViewport />
39
- </ToastProvider>
40
- )
41
- }