@marv3l/canopy-ui 1.0.1 → 1.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.
- package/LICENSE +20 -20
- package/README.md +261 -261
- package/dist/index.js +0 -0
- package/package.json +1 -1
- package/templates/toast/toast.tsx +184 -184
- package/templates/toast/use-toast.ts +213 -213
|
@@ -1,214 +1,214 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
// Import React to access state hooks and ReactNode type definitions
|
|
4
|
-
import * as React from "react";
|
|
5
|
-
|
|
6
|
-
// Define the supported visual preset types for toast notifications
|
|
7
|
-
export type ToastVariant = "default" | "success" | "error" | "warning" | "info" | "custom";
|
|
8
|
-
|
|
9
|
-
// Interface defining all configurable parameters when triggering a toast notification
|
|
10
|
-
export interface ToastOptions {
|
|
11
|
-
// Unique identifier for toast; auto-generated if omitted
|
|
12
|
-
id?: string;
|
|
13
|
-
// Primary header text or React component
|
|
14
|
-
title?: React.ReactNode;
|
|
15
|
-
// Secondary descriptive message or details
|
|
16
|
-
description?: React.ReactNode;
|
|
17
|
-
// Optional action button or interactive element
|
|
18
|
-
action?: React.ReactNode;
|
|
19
|
-
// Visual style preset (success, error, warning, info, default, custom)
|
|
20
|
-
variant?: ToastVariant;
|
|
21
|
-
// Individual lifespan in milliseconds; overrides the global layout default
|
|
22
|
-
duration?: number;
|
|
23
|
-
// User-defined custom styling parameters for dynamic themes
|
|
24
|
-
customColor?: {
|
|
25
|
-
// Custom CSS background color (HEX, RGB, or HSL)
|
|
26
|
-
bg?: string;
|
|
27
|
-
// Custom text color
|
|
28
|
-
text?: string;
|
|
29
|
-
// Custom border stroke color
|
|
30
|
-
border?: string;
|
|
31
|
-
//Custom progress bar stroke color
|
|
32
|
-
progress?: string;
|
|
33
|
-
// Custom icon fill/stroke tint
|
|
34
|
-
icon?: string;
|
|
35
|
-
};
|
|
36
|
-
// Additional Tailwind or custom CSS classes applied to toast container
|
|
37
|
-
className?: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Internal representation of an active toast item containing open state
|
|
41
|
-
export interface ToastItem extends ToastOptions {
|
|
42
|
-
// Guaranteed string ID for DOM key mapping
|
|
43
|
-
id: string;
|
|
44
|
-
// Boolean flag controlling entrance and exit animations
|
|
45
|
-
open: boolean;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Maximum number of visible toast cards on screen simultaneously
|
|
49
|
-
const TOAST_LIMIT = 5;
|
|
50
|
-
// Delay before removed toasts are completely purged from memory (allows exit transition)
|
|
51
|
-
const TOAST_REMOVE_DELAY =
|
|
52
|
-
|
|
53
|
-
// Discriminated union type representing all possible reducer actions
|
|
54
|
-
type Action =
|
|
55
|
-
// Adds a newly triggered toast to state
|
|
56
|
-
| { type: "ADD_TOAST"; toast: ToastItem }
|
|
57
|
-
// Modifies properties of an existing active toast
|
|
58
|
-
| { type: "UPDATE_TOAST"; toast: Partial<ToastItem> }
|
|
59
|
-
// Initiates dismiss sequence (triggers exit animation)
|
|
60
|
-
| { type: "DISMISS_TOAST"; toastId?: string }
|
|
61
|
-
// Purges toast object from memory after exit animation finishes
|
|
62
|
-
| { type: "REMOVE_TOAST"; toastId?: string };
|
|
63
|
-
|
|
64
|
-
// Structure of global toast memory state
|
|
65
|
-
interface State {
|
|
66
|
-
toasts: ToastItem[];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Monotonically increasing counter for collision-free ID generation
|
|
70
|
-
let count = 0;
|
|
71
|
-
|
|
72
|
-
// Generates unique string identifiers for toast items
|
|
73
|
-
function genId(): string {
|
|
74
|
-
count = (count + 1) % Number.MAX_SAFE_INTEGER;
|
|
75
|
-
return count.toString();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Map tracking active removal timers to prevent duplicate schedule queues
|
|
79
|
-
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
|
80
|
-
|
|
81
|
-
// Schedule the hard removal of a dismissed toast after its exit animation completes
|
|
82
|
-
const addToRemoveQueue = (toastId: string) => {
|
|
83
|
-
// If a timeout is already scheduled for this ID, skip to avoid duplicates
|
|
84
|
-
if (toastTimeouts.has(toastId)) return;
|
|
85
|
-
|
|
86
|
-
// Schedule state dispatch after delay
|
|
87
|
-
const timeout = setTimeout(() => {
|
|
88
|
-
// Clean up timeout reference from tracking map
|
|
89
|
-
toastTimeouts.delete(toastId);
|
|
90
|
-
// Dispatch removal action to purge from state
|
|
91
|
-
dispatch({ type: "REMOVE_TOAST", toastId });
|
|
92
|
-
}, TOAST_REMOVE_DELAY);
|
|
93
|
-
|
|
94
|
-
// Store reference in tracking map
|
|
95
|
-
toastTimeouts.set(toastId, timeout);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// Pure reducer function handling toast state transitions
|
|
99
|
-
export const reducer = (state: State, action: Action): State => {
|
|
100
|
-
switch (action.type) {
|
|
101
|
-
case "ADD_TOAST":
|
|
102
|
-
return {
|
|
103
|
-
...state,
|
|
104
|
-
// Prepend new toast and enforce maximum visible limit
|
|
105
|
-
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
case "UPDATE_TOAST":
|
|
109
|
-
return {
|
|
110
|
-
...state,
|
|
111
|
-
// Map through toasts and merge updated properties onto target ID
|
|
112
|
-
toasts: state.toasts.map((t) =>
|
|
113
|
-
t.id === action.toast.id ? { ...t, ...action.toast } : t
|
|
114
|
-
),
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
case "DISMISS_TOAST": {
|
|
118
|
-
const { toastId } = action;
|
|
119
|
-
|
|
120
|
-
// If a specific ID is provided, schedule removal for only that toast
|
|
121
|
-
if (toastId) {
|
|
122
|
-
addToRemoveQueue(toastId);
|
|
123
|
-
} else {
|
|
124
|
-
// Otherwise schedule removal for all currently open toasts
|
|
125
|
-
state.toasts.forEach((toast) => addToRemoveQueue(toast.id));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return {
|
|
129
|
-
...state,
|
|
130
|
-
// Mark target toasts as closed to trigger CSS fade-out
|
|
131
|
-
toasts: state.toasts.map((t) =>
|
|
132
|
-
t.id === toastId || toastId === undefined ? { ...t, open: false } : t
|
|
133
|
-
),
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
case "REMOVE_TOAST":
|
|
138
|
-
// Clear entire array if no specific ID passed
|
|
139
|
-
if (action.toastId === undefined) return { ...state, toasts: [] };
|
|
140
|
-
|
|
141
|
-
return {
|
|
142
|
-
...state,
|
|
143
|
-
// Filter out target toast from state memory
|
|
144
|
-
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
default:
|
|
148
|
-
return state;
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
// Array of subscriber callbacks implementing the Observer pattern
|
|
153
|
-
const listeners: Array<(state: State) => void> = [];
|
|
154
|
-
|
|
155
|
-
// Singleton state variable preserving toast state across entire application
|
|
156
|
-
let memoryState: State = { toasts: [] };
|
|
157
|
-
|
|
158
|
-
// Dispatches actions to state and notifies all registered React hook subscribers
|
|
159
|
-
function dispatch(action: Action) {
|
|
160
|
-
// Update in-memory singleton state
|
|
161
|
-
memoryState = reducer(memoryState, action);
|
|
162
|
-
// Notify every mounted React component listener
|
|
163
|
-
listeners.forEach((listener) => listener(memoryState));
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Imperative toast function callable from anywhere (inside or outside React lifecycle)
|
|
167
|
-
export function toast(props: ToastOptions) {
|
|
168
|
-
// Use provided ID or generate a new unique identifier
|
|
169
|
-
const id = props.id || genId();
|
|
170
|
-
|
|
171
|
-
// Helper to dynamically update this specific toast
|
|
172
|
-
const update = (updatedProps: ToastOptions) =>
|
|
173
|
-
dispatch({ type: "UPDATE_TOAST", toast: { ...updatedProps, id } });
|
|
174
|
-
|
|
175
|
-
// Helper to dismiss this specific toast
|
|
176
|
-
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
|
|
177
|
-
|
|
178
|
-
// Dispatch action to push toast into visible queue
|
|
179
|
-
dispatch({
|
|
180
|
-
type: "ADD_TOAST",
|
|
181
|
-
toast: {
|
|
182
|
-
...props,
|
|
183
|
-
id,
|
|
184
|
-
open: true,
|
|
185
|
-
},
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// Return control object allowing caller to dismiss or update toast programmatically
|
|
189
|
-
return { id, dismiss, update };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Custom React hook subscribing components to real-time toast updates
|
|
193
|
-
export function useToast() {
|
|
194
|
-
// Local state synced with singleton memory state
|
|
195
|
-
const [state, setState] = React.useState<State>(memoryState);
|
|
196
|
-
|
|
197
|
-
// Register listener on mount; unregister on unmount
|
|
198
|
-
React.useEffect(() => {
|
|
199
|
-
listeners.push(setState);
|
|
200
|
-
return () => {
|
|
201
|
-
const index = listeners.indexOf(setState);
|
|
202
|
-
if (index > -1) {
|
|
203
|
-
listeners.splice(index, 1);
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
}, []); // Empty array ensures registration only happens on mount/unmount
|
|
207
|
-
|
|
208
|
-
// Expose current state, trigger function, and dismiss helper
|
|
209
|
-
return {
|
|
210
|
-
...state,
|
|
211
|
-
toast,
|
|
212
|
-
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
|
213
|
-
};
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
// Import React to access state hooks and ReactNode type definitions
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
|
|
6
|
+
// Define the supported visual preset types for toast notifications
|
|
7
|
+
export type ToastVariant = "default" | "success" | "error" | "warning" | "info" | "custom";
|
|
8
|
+
|
|
9
|
+
// Interface defining all configurable parameters when triggering a toast notification
|
|
10
|
+
export interface ToastOptions {
|
|
11
|
+
// Unique identifier for toast; auto-generated if omitted
|
|
12
|
+
id?: string;
|
|
13
|
+
// Primary header text or React component
|
|
14
|
+
title?: React.ReactNode;
|
|
15
|
+
// Secondary descriptive message or details
|
|
16
|
+
description?: React.ReactNode;
|
|
17
|
+
// Optional action button or interactive element
|
|
18
|
+
action?: React.ReactNode;
|
|
19
|
+
// Visual style preset (success, error, warning, info, default, custom)
|
|
20
|
+
variant?: ToastVariant;
|
|
21
|
+
// Individual lifespan in milliseconds; overrides the global layout default
|
|
22
|
+
duration?: number;
|
|
23
|
+
// User-defined custom styling parameters for dynamic themes
|
|
24
|
+
customColor?: {
|
|
25
|
+
// Custom CSS background color (HEX, RGB, or HSL)
|
|
26
|
+
bg?: string;
|
|
27
|
+
// Custom text color
|
|
28
|
+
text?: string;
|
|
29
|
+
// Custom border stroke color
|
|
30
|
+
border?: string;
|
|
31
|
+
//Custom progress bar stroke color
|
|
32
|
+
progress?: string;
|
|
33
|
+
// Custom icon fill/stroke tint
|
|
34
|
+
icon?: string;
|
|
35
|
+
};
|
|
36
|
+
// Additional Tailwind or custom CSS classes applied to toast container
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Internal representation of an active toast item containing open state
|
|
41
|
+
export interface ToastItem extends ToastOptions {
|
|
42
|
+
// Guaranteed string ID for DOM key mapping
|
|
43
|
+
id: string;
|
|
44
|
+
// Boolean flag controlling entrance and exit animations
|
|
45
|
+
open: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Maximum number of visible toast cards on screen simultaneously
|
|
49
|
+
const TOAST_LIMIT = 5;
|
|
50
|
+
// Delay before removed toasts are completely purged from memory (allows exit transition)
|
|
51
|
+
const TOAST_REMOVE_DELAY = 200;
|
|
52
|
+
|
|
53
|
+
// Discriminated union type representing all possible reducer actions
|
|
54
|
+
type Action =
|
|
55
|
+
// Adds a newly triggered toast to state
|
|
56
|
+
| { type: "ADD_TOAST"; toast: ToastItem }
|
|
57
|
+
// Modifies properties of an existing active toast
|
|
58
|
+
| { type: "UPDATE_TOAST"; toast: Partial<ToastItem> }
|
|
59
|
+
// Initiates dismiss sequence (triggers exit animation)
|
|
60
|
+
| { type: "DISMISS_TOAST"; toastId?: string }
|
|
61
|
+
// Purges toast object from memory after exit animation finishes
|
|
62
|
+
| { type: "REMOVE_TOAST"; toastId?: string };
|
|
63
|
+
|
|
64
|
+
// Structure of global toast memory state
|
|
65
|
+
interface State {
|
|
66
|
+
toasts: ToastItem[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Monotonically increasing counter for collision-free ID generation
|
|
70
|
+
let count = 0;
|
|
71
|
+
|
|
72
|
+
// Generates unique string identifiers for toast items
|
|
73
|
+
function genId(): string {
|
|
74
|
+
count = (count + 1) % Number.MAX_SAFE_INTEGER;
|
|
75
|
+
return count.toString();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Map tracking active removal timers to prevent duplicate schedule queues
|
|
79
|
+
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
|
|
80
|
+
|
|
81
|
+
// Schedule the hard removal of a dismissed toast after its exit animation completes
|
|
82
|
+
const addToRemoveQueue = (toastId: string) => {
|
|
83
|
+
// If a timeout is already scheduled for this ID, skip to avoid duplicates
|
|
84
|
+
if (toastTimeouts.has(toastId)) return;
|
|
85
|
+
|
|
86
|
+
// Schedule state dispatch after delay
|
|
87
|
+
const timeout = setTimeout(() => {
|
|
88
|
+
// Clean up timeout reference from tracking map
|
|
89
|
+
toastTimeouts.delete(toastId);
|
|
90
|
+
// Dispatch removal action to purge from state
|
|
91
|
+
dispatch({ type: "REMOVE_TOAST", toastId });
|
|
92
|
+
}, TOAST_REMOVE_DELAY);
|
|
93
|
+
|
|
94
|
+
// Store reference in tracking map
|
|
95
|
+
toastTimeouts.set(toastId, timeout);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Pure reducer function handling toast state transitions
|
|
99
|
+
export const reducer = (state: State, action: Action): State => {
|
|
100
|
+
switch (action.type) {
|
|
101
|
+
case "ADD_TOAST":
|
|
102
|
+
return {
|
|
103
|
+
...state,
|
|
104
|
+
// Prepend new toast and enforce maximum visible limit
|
|
105
|
+
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
case "UPDATE_TOAST":
|
|
109
|
+
return {
|
|
110
|
+
...state,
|
|
111
|
+
// Map through toasts and merge updated properties onto target ID
|
|
112
|
+
toasts: state.toasts.map((t) =>
|
|
113
|
+
t.id === action.toast.id ? { ...t, ...action.toast } : t
|
|
114
|
+
),
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
case "DISMISS_TOAST": {
|
|
118
|
+
const { toastId } = action;
|
|
119
|
+
|
|
120
|
+
// If a specific ID is provided, schedule removal for only that toast
|
|
121
|
+
if (toastId) {
|
|
122
|
+
addToRemoveQueue(toastId);
|
|
123
|
+
} else {
|
|
124
|
+
// Otherwise schedule removal for all currently open toasts
|
|
125
|
+
state.toasts.forEach((toast) => addToRemoveQueue(toast.id));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
...state,
|
|
130
|
+
// Mark target toasts as closed to trigger CSS fade-out
|
|
131
|
+
toasts: state.toasts.map((t) =>
|
|
132
|
+
t.id === toastId || toastId === undefined ? { ...t, open: false } : t
|
|
133
|
+
),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
case "REMOVE_TOAST":
|
|
138
|
+
// Clear entire array if no specific ID passed
|
|
139
|
+
if (action.toastId === undefined) return { ...state, toasts: [] };
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
...state,
|
|
143
|
+
// Filter out target toast from state memory
|
|
144
|
+
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
default:
|
|
148
|
+
return state;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Array of subscriber callbacks implementing the Observer pattern
|
|
153
|
+
const listeners: Array<(state: State) => void> = [];
|
|
154
|
+
|
|
155
|
+
// Singleton state variable preserving toast state across entire application
|
|
156
|
+
let memoryState: State = { toasts: [] };
|
|
157
|
+
|
|
158
|
+
// Dispatches actions to state and notifies all registered React hook subscribers
|
|
159
|
+
function dispatch(action: Action) {
|
|
160
|
+
// Update in-memory singleton state
|
|
161
|
+
memoryState = reducer(memoryState, action);
|
|
162
|
+
// Notify every mounted React component listener
|
|
163
|
+
listeners.forEach((listener) => listener(memoryState));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Imperative toast function callable from anywhere (inside or outside React lifecycle)
|
|
167
|
+
export function toast(props: ToastOptions) {
|
|
168
|
+
// Use provided ID or generate a new unique identifier
|
|
169
|
+
const id = props.id || genId();
|
|
170
|
+
|
|
171
|
+
// Helper to dynamically update this specific toast
|
|
172
|
+
const update = (updatedProps: ToastOptions) =>
|
|
173
|
+
dispatch({ type: "UPDATE_TOAST", toast: { ...updatedProps, id } });
|
|
174
|
+
|
|
175
|
+
// Helper to dismiss this specific toast
|
|
176
|
+
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
|
|
177
|
+
|
|
178
|
+
// Dispatch action to push toast into visible queue
|
|
179
|
+
dispatch({
|
|
180
|
+
type: "ADD_TOAST",
|
|
181
|
+
toast: {
|
|
182
|
+
...props,
|
|
183
|
+
id,
|
|
184
|
+
open: true,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Return control object allowing caller to dismiss or update toast programmatically
|
|
189
|
+
return { id, dismiss, update };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Custom React hook subscribing components to real-time toast updates
|
|
193
|
+
export function useToast() {
|
|
194
|
+
// Local state synced with singleton memory state
|
|
195
|
+
const [state, setState] = React.useState<State>(memoryState);
|
|
196
|
+
|
|
197
|
+
// Register listener on mount; unregister on unmount
|
|
198
|
+
React.useEffect(() => {
|
|
199
|
+
listeners.push(setState);
|
|
200
|
+
return () => {
|
|
201
|
+
const index = listeners.indexOf(setState);
|
|
202
|
+
if (index > -1) {
|
|
203
|
+
listeners.splice(index, 1);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}, []); // Empty array ensures registration only happens on mount/unmount
|
|
207
|
+
|
|
208
|
+
// Expose current state, trigger function, and dismiss helper
|
|
209
|
+
return {
|
|
210
|
+
...state,
|
|
211
|
+
toast,
|
|
212
|
+
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
|
213
|
+
};
|
|
214
214
|
}
|