pejay-ui 1.3.4 → 1.4.0

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,320 @@
1
+ import { useEffect, useState, useRef } from "react";
2
+ import type { ToastData } from "./types";
3
+ import { toastStore } from "./store";
4
+ import {
5
+ CheckCircle,
6
+ AlertCircle,
7
+ AlertTriangle,
8
+ Info,
9
+ X,
10
+ } from "lucide-react";
11
+ import { cn } from "@/utils/cn";
12
+ import { Portal } from "../overlays";
13
+
14
+ // Dictionary mapping toast types to their respective Lucide icons and Tailwind styles
15
+ const TOAST_STYLES = {
16
+ success: {
17
+ Icon: CheckCircle,
18
+ iconColor: "text-emerald-500",
19
+ borderColor: "border-emerald-500/20",
20
+ bgGlow: "shadow-emerald-500/5",
21
+ accentColor: "bg-emerald-500",
22
+ },
23
+ error: {
24
+ Icon: AlertCircle,
25
+ iconColor: "text-red-500",
26
+ borderColor: "border-red-500/20",
27
+ bgGlow: "shadow-red-500/5",
28
+ accentColor: "bg-red-500",
29
+ },
30
+ warning: {
31
+ Icon: AlertTriangle,
32
+ iconColor: "text-amber-500",
33
+ borderColor: "border-amber-500/20",
34
+ bgGlow: "shadow-amber-500/5",
35
+ accentColor: "bg-amber-500",
36
+ },
37
+ info: {
38
+ Icon: Info,
39
+ iconColor: "text-sky-500",
40
+ borderColor: "border-sky-500/20",
41
+ bgGlow: "shadow-sky-500/5",
42
+ accentColor: "bg-sky-500",
43
+ },
44
+ } as const;
45
+
46
+ function ToastItem({
47
+ toast,
48
+ placement = "top-right",
49
+ animationType = "fade",
50
+ }: {
51
+ toast: ToastData;
52
+ placement?: "top-right" | "top-left" | "bottom-right" | "bottom-left";
53
+ animationType?: "slide" | "fade";
54
+ }) {
55
+ const [dragOffset, setDragOffset] = useState(0);
56
+ const [isDragging, setIsDragging] = useState(false);
57
+ const [isDismissing, setIsDismissing] = useState(false);
58
+ const [animateState, setAnimateState] = useState(false);
59
+
60
+ const startXRef = useRef(0);
61
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
62
+ const isHoveringRef = useRef(false);
63
+
64
+ const isLeft = placement.endsWith("-left");
65
+ const slideOffset = isLeft ? -500 : 500;
66
+
67
+ // Helper to trigger the exit animation and remove toast from store after transition
68
+ const triggerDismiss = (customOffset?: number) => {
69
+ setIsDismissing(true);
70
+ if (customOffset !== undefined) {
71
+ setDragOffset(customOffset);
72
+ }
73
+ setTimeout(() => {
74
+ toastStore.remove(toast.id);
75
+ }, 400);
76
+ };
77
+
78
+ const startTimer = () => {
79
+ if (timerRef.current) clearTimeout(timerRef.current);
80
+ if (isHoveringRef.current) return;
81
+ const duration = toast.duration ?? 4000;
82
+ if (duration === Infinity) return;
83
+ timerRef.current = setTimeout(() => {
84
+ triggerDismiss();
85
+ }, duration);
86
+ };
87
+
88
+ const stopTimer = () => {
89
+ if (timerRef.current) {
90
+ clearTimeout(timerRef.current);
91
+ timerRef.current = null;
92
+ }
93
+ };
94
+
95
+ useEffect(() => {
96
+ startTimer();
97
+ return () => stopTimer();
98
+ }, [toast.id, toast.duration]);
99
+
100
+ // Use requestAnimationFrame to guarantee the browser registers the initial off-screen paint before triggering the transition
101
+ useEffect(() => {
102
+ let frame1: number;
103
+ let frame2: number;
104
+ frame1 = requestAnimationFrame(() => {
105
+ frame2 = requestAnimationFrame(() => {
106
+ setAnimateState(true);
107
+ });
108
+ });
109
+ return () => {
110
+ cancelAnimationFrame(frame1);
111
+ if (frame2) cancelAnimationFrame(frame2);
112
+ };
113
+ }, []);
114
+
115
+ const handleMouseEnter = () => {
116
+ isHoveringRef.current = true;
117
+ stopTimer();
118
+ };
119
+
120
+ const handleMouseLeave = () => {
121
+ isHoveringRef.current = false;
122
+ startTimer();
123
+ };
124
+
125
+ const handlePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
126
+ if (isDismissing) return;
127
+ if (e.pointerType === "mouse" && e.button !== 0) return;
128
+
129
+ // Prevent dragging when clicking interactive elements (buttons, inputs, links, etc.)
130
+ const target = e.target as HTMLElement;
131
+ if (target.closest("button, input, select, textarea, a")) return;
132
+
133
+ setIsDragging(true);
134
+ startXRef.current = e.clientX;
135
+ stopTimer();
136
+ e.currentTarget.setPointerCapture(e.pointerId);
137
+ };
138
+
139
+ const handlePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
140
+ if (!isDragging) return;
141
+ const offset = e.clientX - startXRef.current;
142
+ setDragOffset(offset);
143
+ };
144
+
145
+ const handlePointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
146
+ if (!isDragging) return;
147
+ setIsDragging(false);
148
+ e.currentTarget.releasePointerCapture(e.pointerId);
149
+
150
+ // Dismiss toast if dragged beyond threshold, otherwise snap back
151
+ if (Math.abs(dragOffset) > 100) {
152
+ triggerDismiss(dragOffset > 0 ? 500 : -500);
153
+ } else {
154
+ setDragOffset(0);
155
+ startTimer();
156
+ }
157
+ };
158
+
159
+ // Determine transition styles based on active animation option
160
+ const opacity = Math.max(0, 1 - Math.abs(dragOffset) / 300);
161
+
162
+ const opacityStyle = animationType === "slide"
163
+ ? (isDragging ? opacity : 1)
164
+ : (isDismissing || !animateState ? 0 : opacity);
165
+
166
+ const transformStyle = animationType === "slide"
167
+ ? (isDismissing
168
+ ? `translate3d(${dragOffset === 0 ? slideOffset : (dragOffset > 0 ? 500 : -500)}px, 0, 0)`
169
+ : (!animateState ? `translate3d(${slideOffset}px, 0, 0)` : `translate3d(${dragOffset}px, 0, 0)`))
170
+ : `translate3d(${dragOffset}px, 0, 0)`;
171
+
172
+ const itemStyle = {
173
+ transform: transformStyle,
174
+ opacity: opacityStyle,
175
+ touchAction: "none" as const,
176
+ willChange: "transform, opacity",
177
+ transition: isDragging
178
+ ? "none"
179
+ : "transform 0.4s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.4s ease-in-out",
180
+ };
181
+
182
+ // Return custom component directly, avoiding standard layout properties
183
+ if (toast.type === "custom") {
184
+ return (
185
+ <div
186
+ onPointerDown={handlePointerDown}
187
+ onPointerMove={handlePointerMove}
188
+ onPointerUp={handlePointerUp}
189
+ onPointerCancel={handlePointerUp}
190
+ onMouseEnter={handleMouseEnter}
191
+ onMouseLeave={handleMouseLeave}
192
+ style={itemStyle}
193
+ className={cn(
194
+ "relative overflow-hidden min-w-[320px] max-w-[400px] rounded-xl border backdrop-blur-md shadow-2xl flex items-start",
195
+ "select-none cursor-pointer active:cursor-grabbing",
196
+ "bg-gray-950/95 border-violet-500/20 shadow-violet-500/5",
197
+ )}
198
+ >
199
+ {typeof toast.content === "function" ? toast.content(toast.id!) : toast.content}
200
+ </div>
201
+ );
202
+ }
203
+
204
+ // Standard toast style configuration mapping
205
+ const typeKey = (toast.type && toast.type in TOAST_STYLES) ? (toast.type as keyof typeof TOAST_STYLES) : "info";
206
+ const { Icon, iconColor, borderColor, bgGlow, accentColor } = TOAST_STYLES[typeKey];
207
+
208
+ return (
209
+ <div
210
+ onPointerDown={handlePointerDown}
211
+ onPointerMove={handlePointerMove}
212
+ onPointerUp={handlePointerUp}
213
+ onPointerCancel={handlePointerUp}
214
+ onMouseEnter={handleMouseEnter}
215
+ onMouseLeave={handleMouseLeave}
216
+ style={itemStyle}
217
+ className={cn(
218
+ "relative overflow-hidden min-w-[320px] max-w-[400px] rounded-xl border backdrop-blur-md p-4 shadow-2xl flex gap-3 items-start",
219
+ "select-none cursor-pointer active:cursor-grabbing",
220
+ "bg-gray-950/95",
221
+ borderColor,
222
+ bgGlow,
223
+ )}
224
+ >
225
+ {accentColor && (
226
+ <div
227
+ className={cn("absolute left-0 top-0 bottom-0 w-[3px]", accentColor)}
228
+ />
229
+ )}
230
+
231
+ {toast.icon ? (
232
+ <div className="shrink-0 mt-0.5 ml-1 pointer-events-none flex items-center justify-center">
233
+ {toast.icon}
234
+ </div>
235
+ ) : (
236
+ <Icon
237
+ className={cn("shrink-0 mt-0.5 ml-1 pointer-events-none", iconColor)}
238
+ size={18}
239
+ />
240
+ )}
241
+
242
+ <div className="flex-1 flex flex-col gap-0.5 pointer-events-none">
243
+ {toast.message && (
244
+ <p className="text-sm font-medium text-white leading-normal pr-4">
245
+ {toast.message}
246
+ </p>
247
+ )}
248
+ {toast.title && (
249
+ <h3
250
+ className="font-semibold text-sm tracking-tight leading-tight pr-4 text-white"
251
+ >
252
+ {toast.title}
253
+ </h3>
254
+ )}
255
+ {toast.description && (
256
+ <p
257
+ className="text-xs font-medium mt-1 leading-normal text-gray-400"
258
+ >
259
+ {toast.description}
260
+ </p>
261
+ )}
262
+ </div>
263
+
264
+ {toast.showClose && (
265
+ <button
266
+ onClick={e => {
267
+ e.stopPropagation();
268
+ triggerDismiss();
269
+ }}
270
+ className="text-gray-500 hover:text-white transition-colors p-1 rounded-md hover:bg-gray-900 shrink-0 relative z-10"
271
+ >
272
+ <X size={14} />
273
+ </button>
274
+ )}
275
+ </div>
276
+ );
277
+ }
278
+
279
+ interface ToastContainerProps {
280
+ placement?: "top-right" | "top-left" | "bottom-right" | "bottom-left";
281
+ "animation-type"?: "slide" | "fade";
282
+ animationType?: "slide" | "fade";
283
+ }
284
+
285
+ export function ToastContainer({
286
+ placement = "top-right",
287
+ "animation-type": animationTypeHyphen,
288
+ animationType = animationTypeHyphen ?? "fade",
289
+ }: ToastContainerProps) {
290
+ const [toasts, setToasts] = useState<ToastData[]>(toastStore.getToasts());
291
+
292
+ useEffect(() => {
293
+ const unsubscribe = toastStore.subscribe(setToasts);
294
+ return unsubscribe;
295
+ }, []);
296
+
297
+ const placementClasses = {
298
+ "top-right": "fixed top-4 right-4 flex-col",
299
+ "top-left": "fixed top-4 left-4 flex-col",
300
+ "bottom-right": "fixed bottom-4 right-4 flex-col-reverse",
301
+ "bottom-left": "fixed bottom-4 left-4 flex-col-reverse",
302
+ };
303
+
304
+ const containerClass = placementClasses[placement] || placementClasses["top-right"];
305
+
306
+ return (
307
+ <Portal>
308
+ <div className={cn("z-[9999] flex gap-3", containerClass)}>
309
+ {toasts.map(toast => (
310
+ <ToastItem
311
+ key={toast.id}
312
+ toast={toast}
313
+ placement={placement}
314
+ animationType={animationType}
315
+ />
316
+ ))}
317
+ </div>
318
+ </Portal>
319
+ );
320
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./container";
2
+ export * from "./toast";
3
+ export * from "./types";
4
+ export * from "./store";
@@ -0,0 +1,35 @@
1
+ import type { Listener, ToastData } from "./types";
2
+
3
+ class ToastStore {
4
+ private toasts: ToastData[] = [];
5
+ private listeners: Listener[] = [];
6
+
7
+ subscribe(listener: Listener) {
8
+ this.listeners.push(listener);
9
+ return () => {
10
+ this.listeners = this.listeners.filter((l) => l !== listener);
11
+ };
12
+ }
13
+ private publish() {
14
+ this.listeners.forEach((listener) => listener(this.toasts));
15
+ }
16
+ add(toast: ToastData) {
17
+ const newToast: ToastData = {
18
+ id: toast.id ?? crypto.randomUUID(),
19
+ ...toast,
20
+ };
21
+ this.toasts = [newToast, ...this.toasts];
22
+ this.publish();
23
+ return newToast.id!;
24
+ }
25
+ remove(id: string | undefined) {
26
+ this.toasts = this.toasts.filter((toast) => toast.id !== id);
27
+
28
+ this.publish();
29
+ }
30
+ getToasts() {
31
+ return this.toasts;
32
+ }
33
+ }
34
+
35
+ export const toastStore = new ToastStore();
@@ -0,0 +1,54 @@
1
+ import { toastStore } from "./store";
2
+ import type { ToastData } from "./types";
3
+
4
+ type ToastOptions = ToastData;
5
+ type ShortcutOptions = Omit<ToastData, "type">;
6
+
7
+ function createToast(options: ToastOptions) {
8
+ if (options.dismiss) {
9
+ toastStore.remove(options.dismiss);
10
+ }
11
+ return toastStore.add(options);
12
+ }
13
+
14
+ const handleShortcut = (
15
+ type: ToastData["type"],
16
+ messageOrOptions: string | ShortcutOptions,
17
+ options?: ShortcutOptions
18
+ ) => {
19
+ if (typeof messageOrOptions === "string") {
20
+ return createToast({ message: messageOrOptions, ...options, type });
21
+ }
22
+ return createToast({ ...messageOrOptions, type });
23
+ };
24
+
25
+ export const toast = Object.assign(
26
+ (options: ToastOptions) => {
27
+ return createToast(options);
28
+ },
29
+ {
30
+ success: (messageOrOptions: string | ShortcutOptions, options?: ShortcutOptions) => {
31
+ return handleShortcut("success", messageOrOptions, options);
32
+ },
33
+
34
+ error: (messageOrOptions: string | ShortcutOptions, options?: ShortcutOptions) => {
35
+ return handleShortcut("error", messageOrOptions, options);
36
+ },
37
+
38
+ info: (messageOrOptions: string | ShortcutOptions, options?: ShortcutOptions) => {
39
+ return handleShortcut("info", messageOrOptions, options);
40
+ },
41
+
42
+ warning: (messageOrOptions: string | ShortcutOptions, options?: ShortcutOptions) => {
43
+ return handleShortcut("warning", messageOrOptions, options);
44
+ },
45
+
46
+ custom: (options: { content: React.ReactNode | ((id: string) => React.ReactNode); id?: string; duration?: number; dismiss?: string }) => {
47
+ return createToast({ ...options, type: "custom" });
48
+ },
49
+
50
+ dismiss: (id: string) => {
51
+ toastStore.remove(id);
52
+ },
53
+ },
54
+ );
@@ -0,0 +1,15 @@
1
+ export type ToastType = "info" | "error" | "warning" | "success" | "custom";
2
+
3
+ export type Listener = (toasts: ToastData[]) => void;
4
+ export interface ToastData {
5
+ id?: string;
6
+ title?: string;
7
+ description?: string;
8
+ message?: string;
9
+ type?: ToastType;
10
+ duration?: number;
11
+ showClose?: boolean;
12
+ dismiss?: string;
13
+ icon?: React.ReactNode;
14
+ content?: React.ReactNode | ((id: string) => React.ReactNode);
15
+ }
package/registry.json DELETED
@@ -1,256 +0,0 @@
1
- {
2
- "button": {
3
- "name": "Button",
4
- "category": "button",
5
- "files": ["templates/button/Button.tsx", "templates/button/tooltip.tsx"],
6
- "utils": ["cn.ts"],
7
- "peerDependencies": ["clsx", "tailwind-merge"]
8
- },
9
- "form/input": {
10
- "name": "Input",
11
- "category": "form",
12
- "files": ["templates/form/input.tsx"],
13
- "utils": ["cn.ts"],
14
- "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
15
- },
16
- "form/amount-input": {
17
- "name": "AmountInput",
18
- "category": "form",
19
- "files": ["templates/form/amount-input.tsx"],
20
- "utils": ["cn.ts"],
21
- "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
22
- },
23
- "form/checkbox": {
24
- "name": "Checkbox",
25
- "category": "form",
26
- "files": ["templates/form/checkbox.tsx"],
27
- "utils": ["cn.ts"],
28
- "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
29
- },
30
- "form/checkbox-group": {
31
- "name": "CheckboxGroup",
32
- "category": "form",
33
- "files": ["templates/form/checkbox-group.tsx"],
34
- "utils": ["cn.ts"],
35
- "peerDependencies": ["clsx", "tailwind-merge"],
36
- "dependencies": ["form/checkbox"]
37
- },
38
- "form/date-picker": {
39
- "name": "DatePicker",
40
- "category": "form",
41
- "files": ["templates/form/date-picker.tsx"],
42
- "utils": ["cn.ts"],
43
- "peerDependencies": [
44
- "clsx",
45
- "tailwind-merge",
46
- "lucide-react",
47
- "@floating-ui/react"
48
- ],
49
- "dependencies": ["select-dropdown/select-input"]
50
- },
51
- "form/date-range-picker": {
52
- "name": "DateRangePicker",
53
- "category": "form",
54
- "files": ["templates/form/date-range-picker.tsx"],
55
- "utils": ["cn.ts"],
56
- "peerDependencies": [
57
- "clsx",
58
- "tailwind-merge",
59
- "lucide-react",
60
- "@floating-ui/react"
61
- ],
62
- "dependencies": ["select-dropdown/select-input"]
63
- },
64
- "form/email-input": {
65
- "name": "EmailInput",
66
- "category": "form",
67
- "files": ["templates/form/email-input.tsx"],
68
- "utils": ["cn.ts"],
69
- "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
70
- },
71
- "form/file-input": {
72
- "name": "FileInput",
73
- "category": "form",
74
- "files": ["templates/form/file-input.tsx"],
75
- "utils": ["cn.ts"],
76
- "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
77
- },
78
- "form/number-input": {
79
- "name": "NumberInput",
80
- "category": "form",
81
- "files": ["templates/form/number-input.tsx"],
82
- "utils": ["cn.ts"],
83
- "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
84
- },
85
- "form/password-input": {
86
- "name": "PasswordInput",
87
- "category": "form",
88
- "files": ["templates/form/password-input.tsx"],
89
- "utils": ["cn.ts"],
90
- "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
91
- },
92
- "form/phone-input": {
93
- "name": "PhoneInput",
94
- "category": "form",
95
- "files": ["templates/form/phone-input.tsx"],
96
- "utils": ["cn.ts"],
97
- "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
98
- },
99
- "form/radio": {
100
- "name": "Radio",
101
- "category": "form",
102
- "files": ["templates/form/radio.tsx"],
103
- "utils": ["cn.ts"],
104
- "peerDependencies": ["clsx", "tailwind-merge"]
105
- },
106
- "form/radio-group": {
107
- "name": "RadioGroup",
108
- "category": "form",
109
- "files": ["templates/form/radio-group.tsx"],
110
- "utils": ["cn.ts"],
111
- "peerDependencies": ["clsx", "tailwind-merge"],
112
- "dependencies": ["form/radio"]
113
- },
114
- "form/range-slider": {
115
- "name": "RangeSlider",
116
- "category": "form",
117
- "files": ["templates/form/range-slider.tsx"],
118
- "utils": ["cn.ts"],
119
- "peerDependencies": ["clsx", "tailwind-merge"]
120
- },
121
- "form/switch": {
122
- "name": "Switch",
123
- "category": "form",
124
- "files": ["templates/form/switch.tsx"],
125
- "utils": ["cn.ts"],
126
- "peerDependencies": ["clsx", "tailwind-merge"]
127
- },
128
- "form/textarea": {
129
- "name": "Textarea",
130
- "category": "form",
131
- "files": ["templates/form/textarea.tsx"],
132
- "utils": ["cn.ts"],
133
- "peerDependencies": ["clsx", "tailwind-merge"]
134
- },
135
- "form/time-picker": {
136
- "name": "TimePicker",
137
- "category": "form",
138
- "files": ["templates/form/time-picker.tsx"],
139
- "utils": ["cn.ts"],
140
- "peerDependencies": [
141
- "clsx",
142
- "tailwind-merge",
143
- "lucide-react",
144
- "@floating-ui/react"
145
- ],
146
- "dependencies": ["select-dropdown/select-input"]
147
- },
148
- "form/time-range-picker": {
149
- "name": "TimeRangePicker",
150
- "category": "form",
151
- "files": ["templates/form/time-range-picker.tsx"],
152
- "utils": ["cn.ts"],
153
- "peerDependencies": [
154
- "clsx",
155
- "tailwind-merge",
156
- "lucide-react",
157
- "@floating-ui/react"
158
- ],
159
- "dependencies": ["select-dropdown/select-input"]
160
- },
161
- "form/url-input": {
162
- "name": "UrlInput",
163
- "category": "form",
164
- "files": ["templates/form/url-input.tsx"],
165
- "utils": ["cn.ts"],
166
- "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
167
- },
168
- "dropdown/select-input": {
169
- "name": "SelectInput",
170
- "category": "select-dropdown",
171
- "files": ["templates/select-dropdown/select-input.tsx"],
172
- "utils": ["cn.ts"],
173
- "peerDependencies": [
174
- "clsx",
175
- "tailwind-merge",
176
- "lucide-react",
177
- "@floating-ui/react"
178
- ]
179
- },
180
- "dropdown/multiselect-input": {
181
- "name": "MultiselectInput",
182
- "category": "select-dropdown",
183
- "files": ["templates/select-dropdown/multiselect-input.tsx"],
184
- "utils": ["cn.ts"],
185
- "peerDependencies": [
186
- "clsx",
187
- "tailwind-merge",
188
- "lucide-react",
189
- "@floating-ui/react"
190
- ]
191
- },
192
- "tanstack-query-client": {
193
- "name": "TanstackQueryClient",
194
- "category": "scaffold",
195
- "subcategory": "tanstack-query",
196
- "targetDirName": "tanstack-query",
197
- "files": ["templates/scaffolds/tanstack-query"],
198
- "peerDependencies": ["@tanstack/react-query"]
199
- },
200
- "react-router-client": {
201
- "name": "ReactRouterClient",
202
- "category": "scaffold",
203
- "subcategory": "react-router",
204
- "targetDirName": "react-router",
205
- "files": ["templates/scaffolds/react-router"],
206
- "peerDependencies": ["react-router-dom"]
207
- },
208
- "tanstack-router-client": {
209
- "name": "TanstackRouterClient",
210
- "category": "scaffold",
211
- "subcategory": "tanstack-router",
212
- "targetDirName": "tanstack-router",
213
- "files": ["templates/scaffolds/tanstack-router"],
214
- "peerDependencies": ["@tanstack/react-router"]
215
- },
216
- "axios-client": {
217
- "name": "AxiosClient",
218
- "category": "scaffold",
219
- "subcategory": "axios",
220
- "targetDirName": "axios",
221
- "files": ["templates/scaffolds/axios"],
222
- "peerDependencies": ["axios"]
223
- },
224
- "redux-store-client": {
225
- "name": "ReduxStoreClient",
226
- "category": "scaffold",
227
- "subcategory": "redux-store",
228
- "targetDirName": "redux-store",
229
- "files": ["templates/scaffolds/redux-store"],
230
- "peerDependencies": ["@reduxjs/toolkit", "react-redux", "redux-persist"]
231
- },
232
- "rtk-query-client": {
233
- "name": "RtkQueryClient",
234
- "category": "scaffold",
235
- "subcategory": "rtk-query",
236
- "targetDirName": "rtk-query",
237
- "files": ["templates/scaffolds/rtk-query"],
238
- "peerDependencies": ["@reduxjs/toolkit", "react-redux"]
239
- },
240
- "layouts/lv1": {
241
- "name": "AppLayout",
242
- "category": "layouts",
243
- "files": [
244
- "templates/layouts/lv1/app-layout.tsx",
245
- "templates/layouts/lv1/sidebar-menu.tsx",
246
- "templates/layouts/lv1/index.ts"
247
- ],
248
- "utils": ["cn.ts"],
249
- "peerDependencies": [
250
- "clsx",
251
- "tailwind-merge",
252
- "lucide-react"
253
- ]
254
- }
255
- }
256
-