pejay-ui 1.0.4 → 1.2.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.
Files changed (29) hide show
  1. package/README.md +12 -0
  2. package/bin/cli.js +83 -20
  3. package/package.json +3 -1
  4. package/registry.json +78 -224
  5. package/templates/scaffolds/react-router/hook/useRouterSearch.ts +131 -0
  6. package/templates/scaffolds/react-router/router/guards/private.route.tsx +56 -0
  7. package/templates/scaffolds/react-router/router/guards/public.route.tsx +21 -0
  8. package/templates/scaffolds/react-router/router/index.ts +186 -0
  9. package/templates/scaffolds/react-router/router/layouts/auth.layout.tsx +5 -0
  10. package/templates/scaffolds/react-router/router/layouts/error.layout.tsx +15 -0
  11. package/templates/scaffolds/react-router/router/layouts/main.layout.tsx +5 -0
  12. package/templates/scaffolds/react-router/router/path.ts +7 -0
  13. package/templates/scaffolds/react-router/routes/not-found/index.tsx +3 -0
  14. package/templates/scaffolds/react-router/routes/page-auth/index.tsx +3 -0
  15. package/templates/scaffolds/react-router/routes/page-one/index.tsx +85 -0
  16. package/templates/scaffolds/react-router/routes/page-one/search.page-one.ts +5 -0
  17. package/templates/scaffolds/react-router/routes/page-root/index.tsx +3 -0
  18. package/templates/scaffolds/react-router/routes/page-two/index.tsx +3 -0
  19. package/templates/scaffolds/react-router/routes/page-two/search.page-two.ts +5 -0
  20. package/templates/scaffolds/tanstack-query/api-base.ts +68 -0
  21. package/templates/scaffolds/tanstack-query/api-mutations.ts +15 -0
  22. package/templates/scaffolds/tanstack-query/api-queries.ts +293 -0
  23. package/templates/scaffolds/tanstack-query/client.ts +63 -0
  24. package/templates/scaffolds/tanstack-query/module/index.ts +12 -0
  25. package/templates/scaffolds/tanstack-query/module/keys.ts +17 -0
  26. package/templates/scaffolds/tanstack-query/module/mappers.ts +15 -0
  27. package/templates/scaffolds/tanstack-query/module/mutations.ts +55 -0
  28. package/templates/scaffolds/tanstack-query/module/queries.ts +156 -0
  29. package/templates/scaffolds/tanstack-query/module/services.ts +66 -0
package/README.md CHANGED
@@ -129,3 +129,15 @@ Below is the list of components you can add. Each has a copyable command block w
129
129
  ```bash
130
130
  npx pejay-ui add dropdown/multiselect-input
131
131
  ```
132
+
133
+ ### Scaffolds & Templates
134
+
135
+ * **`tanstack-query-client`**: Bare-bone TanStack Query client and context provider setup, copied directly into your project's `src/tanstack-query/`.
136
+ ```bash
137
+ npx pejay-ui add tanstack-query-client
138
+ ```
139
+ * **`react-router-client`**: Bare-bone React Router client layout, routing structure, and route guard setup.
140
+ ```bash
141
+ npx pejay-ui add react-router-client
142
+ ```
143
+
package/bin/cli.js CHANGED
@@ -21,6 +21,23 @@ const prompt = async (questions) => {
21
21
  return inquirer.default.prompt(questions);
22
22
  };
23
23
 
24
+ // Helper to get all files recursively from a directory
25
+ const getFilesRecursively = async (dir) => {
26
+ let results = [];
27
+ const list = await fs.readdir(dir);
28
+ for (const file of list) {
29
+ const filePath = path.join(dir, file);
30
+ const stat = await fs.stat(filePath);
31
+ if (stat && stat.isDirectory()) {
32
+ const subFiles = await getFilesRecursively(filePath);
33
+ results = results.concat(subFiles);
34
+ } else {
35
+ results.push(filePath);
36
+ }
37
+ }
38
+ return results;
39
+ };
40
+
24
41
  program
25
42
  .name("pejay-ui")
26
43
  .description("CLI to initialize, add, and remove React UI components")
@@ -178,7 +195,12 @@ program
178
195
  }
179
196
 
180
197
  // 3. Process & Copy Component Files
181
- const targetDir = path.join(cwd, config.baseDir, "components", componentData.category);
198
+ let targetDir;
199
+ if (componentData.category === "tanstack-query") {
200
+ targetDir = path.join(cwd, "src", "tanstack-query");
201
+ } else {
202
+ targetDir = path.join(cwd, config.baseDir, "components", componentData.category);
203
+ }
182
204
  const outputExt = isTsProject ? "tsx" : "jsx";
183
205
 
184
206
  // Determine list of files to copy
@@ -192,29 +214,70 @@ program
192
214
  process.exit(1);
193
215
  }
194
216
 
195
- const filename = path.basename(srcFilePath).replace(/\.(tsx|ts)$/, (match) => {
196
- return match === ".tsx" ? `.${outputExt}` : `.${isTsProject ? "ts" : "js"}`;
197
- });
198
- const targetFile = path.join(targetDir, filename);
199
-
200
- let componentCode = await fs.readFile(templateSrc, "utf-8");
217
+ const isDir = (await fs.stat(templateSrc)).isDirectory();
218
+ if (isDir) {
219
+ const allFiles = await getFilesRecursively(templateSrc);
220
+ for (const file of allFiles) {
221
+ const relativePath = path.relative(templateSrc, file);
222
+ const filename = relativePath.replace(/\.(tsx|ts)$/, (match) => {
223
+ return match === ".tsx" ? `.${outputExt}` : `.${isTsProject ? "ts" : "js"}`;
224
+ });
225
+ const targetFile = path.join(targetDir, filename);
226
+
227
+ let componentCode = await fs.readFile(file, "utf-8");
228
+
229
+ const fileDir = path.dirname(targetFile);
230
+ const relativeToUtils = path.relative(fileDir, targetUtilsDir).replace(/\\/g, "/");
231
+ const cnImportPath = isTsProject
232
+ ? `${relativeToUtils}/cn`
233
+ : `${relativeToUtils}/cn.js`;
234
+ componentCode = componentCode.replace(/@\/utils\/cn/g, cnImportPath);
201
235
 
202
- // Replace `@/utils/cn` with relative path to the local utils/cn folder
203
- const cnImportPath = isTsProject ? "../../utils/cn" : "../../utils/cn.js";
204
- componentCode = componentCode.replace(/@\/utils\/cn/g, cnImportPath);
236
+ if (!isTsProject) {
237
+ const transformed = babel.transformSync(componentCode, {
238
+ presets: ["@babel/preset-typescript"],
239
+ filename: path.basename(file),
240
+ });
241
+ componentCode = transformed?.code || componentCode;
242
+ }
205
243
 
206
- if (!isTsProject) {
207
- const transformed = babel.transformSync(componentCode, {
208
- presets: ["@babel/preset-typescript"],
209
- filename: path.basename(srcFilePath),
244
+ await fs.ensureDir(path.dirname(targetFile));
245
+ await fs.writeFile(targetFile, componentCode, "utf-8");
246
+
247
+ const relativeTargetFile = path.relative(cwd, targetFile).replace(/\\/g, "/");
248
+ console.log(`✅ Created ${relativeTargetFile}`);
249
+ installedFiles.push(path.relative(path.join(cwd, config.baseDir), targetFile).replace(/\\/g, "/"));
250
+ }
251
+ } else {
252
+ const filename = path.basename(srcFilePath).replace(/\.(tsx|ts)$/, (match) => {
253
+ return match === ".tsx" ? `.${outputExt}` : `.${isTsProject ? "ts" : "js"}`;
210
254
  });
211
- componentCode = transformed?.code || componentCode;
212
- }
255
+ const targetFile = path.join(targetDir, filename);
256
+
257
+ let componentCode = await fs.readFile(templateSrc, "utf-8");
258
+
259
+ const fileDir = path.dirname(targetFile);
260
+ const relativeToUtils = path.relative(fileDir, targetUtilsDir).replace(/\\/g, "/");
261
+ const cnImportPath = isTsProject
262
+ ? `${relativeToUtils}/cn`
263
+ : `${relativeToUtils}/cn.js`;
264
+ componentCode = componentCode.replace(/@\/utils\/cn/g, cnImportPath);
265
+
266
+ if (!isTsProject) {
267
+ const transformed = babel.transformSync(componentCode, {
268
+ presets: ["@babel/preset-typescript"],
269
+ filename: path.basename(srcFilePath),
270
+ });
271
+ componentCode = transformed?.code || componentCode;
272
+ }
213
273
 
214
- await fs.ensureDir(targetDir);
215
- await fs.writeFile(targetFile, componentCode, "utf-8");
216
- console.log(`✅ Created components/${componentData.category}/${filename}`);
217
- installedFiles.push(path.join("components", componentData.category, filename));
274
+ await fs.ensureDir(targetDir);
275
+ await fs.writeFile(targetFile, componentCode, "utf-8");
276
+
277
+ const relativeTargetFile = path.relative(cwd, targetFile).replace(/\\/g, "/");
278
+ console.log(`✅ Created ${relativeTargetFile}`);
279
+ installedFiles.push(path.relative(path.join(cwd, config.baseDir), targetFile).replace(/\\/g, "/"));
280
+ }
218
281
  }
219
282
 
220
283
  // 4. Update State tracking in config
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pejay-ui",
3
- "version": "1.0.4",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "react ui components",
6
6
  "bin": {
@@ -38,6 +38,7 @@
38
38
  "homepage": "https://github.com/pejaybro/pejay-ui#readme",
39
39
  "devDependencies": {
40
40
  "@floating-ui/react": "^0.27.19",
41
+ "@tanstack/react-query": "^5.101.0",
41
42
  "@types/react": "^19.2.15",
42
43
  "@types/react-dom": "^19.2.3",
43
44
  "clsx": "^2.1.1",
@@ -45,6 +46,7 @@
45
46
  "lucide-react": "^1.17.0",
46
47
  "react": "^19.2.6",
47
48
  "react-dom": "^19.2.6",
49
+ "react-router-dom": "^7.16.0",
48
50
  "tailwind-merge": "^3.6.0",
49
51
  "tailwindcss": "^4.3.0",
50
52
  "typescript": "^6.0.3"
package/registry.json CHANGED
@@ -2,328 +2,174 @@
2
2
  "button": {
3
3
  "name": "Button",
4
4
  "category": "button",
5
- "files": [
6
- "templates/button/Button.tsx",
7
- "templates/button/tooltip.tsx"
8
- ],
9
- "utils": [
10
- "cn.ts"
11
- ],
12
- "peerDependencies": [
13
- "clsx",
14
- "tailwind-merge"
15
- ]
5
+ "files": ["templates/button/Button.tsx", "templates/button/tooltip.tsx"],
6
+ "utils": ["cn.ts"],
7
+ "peerDependencies": ["clsx", "tailwind-merge"]
16
8
  },
17
9
  "form/input": {
18
10
  "name": "Input",
19
11
  "category": "form",
20
- "files": [
21
- "templates/form/input.tsx"
22
- ],
23
- "utils": [
24
- "cn.ts"
25
- ],
26
- "peerDependencies": [
27
- "clsx",
28
- "tailwind-merge",
29
- "lucide-react"
30
- ]
12
+ "files": ["templates/form/input.tsx"],
13
+ "utils": ["cn.ts"],
14
+ "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
31
15
  },
32
16
  "form/amount-input": {
33
17
  "name": "AmountInput",
34
18
  "category": "form",
35
- "files": [
36
- "templates/form/amount-input.tsx"
37
- ],
38
- "utils": [
39
- "cn.ts"
40
- ],
41
- "peerDependencies": [
42
- "clsx",
43
- "tailwind-merge",
44
- "lucide-react"
45
- ]
19
+ "files": ["templates/form/amount-input.tsx"],
20
+ "utils": ["cn.ts"],
21
+ "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
46
22
  },
47
23
  "form/checkbox": {
48
24
  "name": "Checkbox",
49
25
  "category": "form",
50
- "files": [
51
- "templates/form/checkbox.tsx"
52
- ],
53
- "utils": [
54
- "cn.ts"
55
- ],
56
- "peerDependencies": [
57
- "clsx",
58
- "tailwind-merge",
59
- "lucide-react"
60
- ]
26
+ "files": ["templates/form/checkbox.tsx"],
27
+ "utils": ["cn.ts"],
28
+ "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
61
29
  },
62
30
  "form/checkbox-group": {
63
31
  "name": "CheckboxGroup",
64
32
  "category": "form",
65
- "files": [
66
- "templates/form/checkbox-group.tsx"
67
- ],
68
- "utils": [
69
- "cn.ts"
70
- ],
71
- "peerDependencies": [
72
- "clsx",
73
- "tailwind-merge"
74
- ],
75
- "dependencies": [
76
- "form/checkbox"
77
- ]
33
+ "files": ["templates/form/checkbox-group.tsx"],
34
+ "utils": ["cn.ts"],
35
+ "peerDependencies": ["clsx", "tailwind-merge"],
36
+ "dependencies": ["form/checkbox"]
78
37
  },
79
38
  "form/date-picker": {
80
39
  "name": "DatePicker",
81
40
  "category": "form",
82
- "files": [
83
- "templates/form/date-picker.tsx"
84
- ],
85
- "utils": [
86
- "cn.ts"
87
- ],
41
+ "files": ["templates/form/date-picker.tsx"],
42
+ "utils": ["cn.ts"],
88
43
  "peerDependencies": [
89
44
  "clsx",
90
45
  "tailwind-merge",
91
46
  "lucide-react",
92
47
  "@floating-ui/react"
93
48
  ],
94
- "dependencies": [
95
- "select-dropdown/select-input"
96
- ]
49
+ "dependencies": ["select-dropdown/select-input"]
97
50
  },
98
51
  "form/date-range-picker": {
99
52
  "name": "DateRangePicker",
100
53
  "category": "form",
101
- "files": [
102
- "templates/form/date-range-picker.tsx"
103
- ],
104
- "utils": [
105
- "cn.ts"
106
- ],
54
+ "files": ["templates/form/date-range-picker.tsx"],
55
+ "utils": ["cn.ts"],
107
56
  "peerDependencies": [
108
57
  "clsx",
109
58
  "tailwind-merge",
110
59
  "lucide-react",
111
60
  "@floating-ui/react"
112
61
  ],
113
- "dependencies": [
114
- "select-dropdown/select-input"
115
- ]
62
+ "dependencies": ["select-dropdown/select-input"]
116
63
  },
117
64
  "form/email-input": {
118
65
  "name": "EmailInput",
119
66
  "category": "form",
120
- "files": [
121
- "templates/form/email-input.tsx"
122
- ],
123
- "utils": [
124
- "cn.ts"
125
- ],
126
- "peerDependencies": [
127
- "clsx",
128
- "tailwind-merge",
129
- "lucide-react"
130
- ]
67
+ "files": ["templates/form/email-input.tsx"],
68
+ "utils": ["cn.ts"],
69
+ "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
131
70
  },
132
71
  "form/file-input": {
133
72
  "name": "FileInput",
134
73
  "category": "form",
135
- "files": [
136
- "templates/form/file-input.tsx"
137
- ],
138
- "utils": [
139
- "cn.ts"
140
- ],
141
- "peerDependencies": [
142
- "clsx",
143
- "tailwind-merge",
144
- "lucide-react"
145
- ]
74
+ "files": ["templates/form/file-input.tsx"],
75
+ "utils": ["cn.ts"],
76
+ "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
146
77
  },
147
78
  "form/number-input": {
148
79
  "name": "NumberInput",
149
80
  "category": "form",
150
- "files": [
151
- "templates/form/number-input.tsx"
152
- ],
153
- "utils": [
154
- "cn.ts"
155
- ],
156
- "peerDependencies": [
157
- "clsx",
158
- "tailwind-merge",
159
- "lucide-react"
160
- ]
81
+ "files": ["templates/form/number-input.tsx"],
82
+ "utils": ["cn.ts"],
83
+ "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
161
84
  },
162
85
  "form/password-input": {
163
86
  "name": "PasswordInput",
164
87
  "category": "form",
165
- "files": [
166
- "templates/form/password-input.tsx"
167
- ],
168
- "utils": [
169
- "cn.ts"
170
- ],
171
- "peerDependencies": [
172
- "clsx",
173
- "tailwind-merge",
174
- "lucide-react"
175
- ]
88
+ "files": ["templates/form/password-input.tsx"],
89
+ "utils": ["cn.ts"],
90
+ "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
176
91
  },
177
92
  "form/phone-input": {
178
93
  "name": "PhoneInput",
179
94
  "category": "form",
180
- "files": [
181
- "templates/form/phone-input.tsx"
182
- ],
183
- "utils": [
184
- "cn.ts"
185
- ],
186
- "peerDependencies": [
187
- "clsx",
188
- "tailwind-merge",
189
- "lucide-react"
190
- ]
95
+ "files": ["templates/form/phone-input.tsx"],
96
+ "utils": ["cn.ts"],
97
+ "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
191
98
  },
192
99
  "form/radio": {
193
100
  "name": "Radio",
194
101
  "category": "form",
195
- "files": [
196
- "templates/form/radio.tsx"
197
- ],
198
- "utils": [
199
- "cn.ts"
200
- ],
201
- "peerDependencies": [
202
- "clsx",
203
- "tailwind-merge"
204
- ]
102
+ "files": ["templates/form/radio.tsx"],
103
+ "utils": ["cn.ts"],
104
+ "peerDependencies": ["clsx", "tailwind-merge"]
205
105
  },
206
106
  "form/radio-group": {
207
107
  "name": "RadioGroup",
208
108
  "category": "form",
209
- "files": [
210
- "templates/form/radio-group.tsx"
211
- ],
212
- "utils": [
213
- "cn.ts"
214
- ],
215
- "peerDependencies": [
216
- "clsx",
217
- "tailwind-merge"
218
- ],
219
- "dependencies": [
220
- "form/radio"
221
- ]
109
+ "files": ["templates/form/radio-group.tsx"],
110
+ "utils": ["cn.ts"],
111
+ "peerDependencies": ["clsx", "tailwind-merge"],
112
+ "dependencies": ["form/radio"]
222
113
  },
223
114
  "form/range-slider": {
224
115
  "name": "RangeSlider",
225
116
  "category": "form",
226
- "files": [
227
- "templates/form/range-slider.tsx"
228
- ],
229
- "utils": [
230
- "cn.ts"
231
- ],
232
- "peerDependencies": [
233
- "clsx",
234
- "tailwind-merge"
235
- ]
117
+ "files": ["templates/form/range-slider.tsx"],
118
+ "utils": ["cn.ts"],
119
+ "peerDependencies": ["clsx", "tailwind-merge"]
236
120
  },
237
121
  "form/switch": {
238
122
  "name": "Switch",
239
123
  "category": "form",
240
- "files": [
241
- "templates/form/switch.tsx"
242
- ],
243
- "utils": [
244
- "cn.ts"
245
- ],
246
- "peerDependencies": [
247
- "clsx",
248
- "tailwind-merge"
249
- ]
124
+ "files": ["templates/form/switch.tsx"],
125
+ "utils": ["cn.ts"],
126
+ "peerDependencies": ["clsx", "tailwind-merge"]
250
127
  },
251
128
  "form/textarea": {
252
129
  "name": "Textarea",
253
130
  "category": "form",
254
- "files": [
255
- "templates/form/textarea.tsx"
256
- ],
257
- "utils": [
258
- "cn.ts"
259
- ],
260
- "peerDependencies": [
261
- "clsx",
262
- "tailwind-merge"
263
- ]
131
+ "files": ["templates/form/textarea.tsx"],
132
+ "utils": ["cn.ts"],
133
+ "peerDependencies": ["clsx", "tailwind-merge"]
264
134
  },
265
135
  "form/time-picker": {
266
136
  "name": "TimePicker",
267
137
  "category": "form",
268
- "files": [
269
- "templates/form/time-picker.tsx"
270
- ],
271
- "utils": [
272
- "cn.ts"
273
- ],
138
+ "files": ["templates/form/time-picker.tsx"],
139
+ "utils": ["cn.ts"],
274
140
  "peerDependencies": [
275
141
  "clsx",
276
142
  "tailwind-merge",
277
143
  "lucide-react",
278
144
  "@floating-ui/react"
279
145
  ],
280
- "dependencies": [
281
- "select-dropdown/select-input"
282
- ]
146
+ "dependencies": ["select-dropdown/select-input"]
283
147
  },
284
148
  "form/time-range-picker": {
285
149
  "name": "TimeRangePicker",
286
150
  "category": "form",
287
- "files": [
288
- "templates/form/time-range-picker.tsx"
289
- ],
290
- "utils": [
291
- "cn.ts"
292
- ],
151
+ "files": ["templates/form/time-range-picker.tsx"],
152
+ "utils": ["cn.ts"],
293
153
  "peerDependencies": [
294
154
  "clsx",
295
155
  "tailwind-merge",
296
156
  "lucide-react",
297
157
  "@floating-ui/react"
298
158
  ],
299
- "dependencies": [
300
- "select-dropdown/select-input"
301
- ]
159
+ "dependencies": ["select-dropdown/select-input"]
302
160
  },
303
161
  "form/url-input": {
304
162
  "name": "UrlInput",
305
163
  "category": "form",
306
- "files": [
307
- "templates/form/url-input.tsx"
308
- ],
309
- "utils": [
310
- "cn.ts"
311
- ],
312
- "peerDependencies": [
313
- "clsx",
314
- "tailwind-merge",
315
- "lucide-react"
316
- ]
164
+ "files": ["templates/form/url-input.tsx"],
165
+ "utils": ["cn.ts"],
166
+ "peerDependencies": ["clsx", "tailwind-merge", "lucide-react"]
317
167
  },
318
168
  "dropdown/select-input": {
319
169
  "name": "SelectInput",
320
170
  "category": "select-dropdown",
321
- "files": [
322
- "templates/select-dropdown/select-input.tsx"
323
- ],
324
- "utils": [
325
- "cn.ts"
326
- ],
171
+ "files": ["templates/select-dropdown/select-input.tsx"],
172
+ "utils": ["cn.ts"],
327
173
  "peerDependencies": [
328
174
  "clsx",
329
175
  "tailwind-merge",
@@ -334,17 +180,25 @@
334
180
  "dropdown/multiselect-input": {
335
181
  "name": "MultiselectInput",
336
182
  "category": "select-dropdown",
337
- "files": [
338
- "templates/select-dropdown/multiselect-input.tsx"
339
- ],
340
- "utils": [
341
- "cn.ts"
342
- ],
183
+ "files": ["templates/select-dropdown/multiselect-input.tsx"],
184
+ "utils": ["cn.ts"],
343
185
  "peerDependencies": [
344
186
  "clsx",
345
187
  "tailwind-merge",
346
188
  "lucide-react",
347
189
  "@floating-ui/react"
348
190
  ]
191
+ },
192
+ "tanstack-query-client": {
193
+ "name": "TanstackQueryClient",
194
+ "category": "tanstack-query",
195
+ "files": ["templates/scaffolds/tanstack-query"],
196
+ "peerDependencies": ["@tanstack/react-query"]
197
+ },
198
+ "react-router-client": {
199
+ "name": "ReactRouterClient",
200
+ "category": "react-router",
201
+ "files": ["templates/scaffolds/react-router"],
202
+ "peerDependencies": ["react-router-dom"]
349
203
  }
350
204
  }