@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.
@@ -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 = 1000;
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
  }