@quarklab/rad-ui 0.3.5 → 0.3.7

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 (2) hide show
  1. package/dist/index.js +160 -44
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -78,44 +78,48 @@ async function detectTailwindVersion(cwd, cssPath) {
78
78
  return "v4";
79
79
  }
80
80
  async function detectSrcDir(cwd) {
81
- const tsconfigPath = path.resolve(cwd, "tsconfig.json");
82
- if (await fs.pathExists(tsconfigPath)) {
83
- try {
84
- const raw = await fs.readFile(tsconfigPath, "utf-8");
85
- const cleaned = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
86
- const tsconfig = JSON.parse(cleaned);
87
- const paths = tsconfig?.compilerOptions?.paths;
88
- if (paths) {
89
- const aliasKey = Object.keys(paths).find(
90
- (k) => k === "@/*" || k === "~/*"
91
- );
92
- if (aliasKey) {
93
- const aliasTargets = paths[aliasKey];
94
- if (Array.isArray(aliasTargets) && aliasTargets.length > 0) {
95
- const target = aliasTargets[0].replace(/^\.\//, "").replace(/\/\*$/, "");
96
- if (target) return target;
81
+ const configFiles = ["tsconfig.json", "jsconfig.json"];
82
+ for (const file of configFiles) {
83
+ const configPath = path.resolve(cwd, file);
84
+ if (await fs.pathExists(configPath)) {
85
+ try {
86
+ const raw = await fs.readFile(configPath, "utf-8");
87
+ const cleaned = raw.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
88
+ const config = JSON.parse(cleaned);
89
+ const paths = config?.compilerOptions?.paths;
90
+ if (paths) {
91
+ const aliasKey = Object.keys(paths).find(
92
+ (k) => k === "@/*" || k === "~/*"
93
+ );
94
+ if (aliasKey) {
95
+ const aliasTargets = paths[aliasKey];
96
+ if (Array.isArray(aliasTargets) && aliasTargets.length > 0) {
97
+ const target = aliasTargets[0].replace(/^\.\//, "").replace(/\/\*$/, "");
98
+ return target === "" ? "." : target;
99
+ }
97
100
  }
98
101
  }
102
+ } catch {
99
103
  }
100
- } catch {
101
104
  }
102
105
  }
103
106
  if (await fs.pathExists(path.resolve(cwd, "src"))) return "src";
104
107
  if (await fs.pathExists(path.resolve(cwd, "app"))) return "app";
105
108
  return "src";
106
109
  }
107
- function getDefaultCssPath(projectType) {
110
+ function getDefaultCssPath(projectType, srcDir) {
111
+ const prefix = srcDir === "." ? "" : srcDir ? `${srcDir}/` : "src/";
108
112
  switch (projectType) {
109
113
  case "nextjs":
110
- return "src/app/globals.css";
114
+ return `${prefix}app/globals.css`;
111
115
  case "vite":
112
- return "src/index.css";
116
+ return `${prefix}index.css`;
113
117
  case "cra":
114
- return "src/index.css";
118
+ return `${prefix}index.css`;
115
119
  case "expo":
116
120
  return "global.css";
117
121
  default:
118
- return "src/globals.css";
122
+ return `${prefix}globals.css`;
119
123
  }
120
124
  }
121
125
  function getDefaultTailwindConfig(projectType) {
@@ -143,19 +147,32 @@ async function readConfig(cwd) {
143
147
  if (!exists) {
144
148
  throw new Error(`Configuration file not found. Run \`rad-ui init\` first.`);
145
149
  }
146
- return fs2.readJson(configPath);
150
+ const config = await fs2.readJson(configPath);
151
+ if (!config.aliases.hooks) {
152
+ config.aliases.hooks = "@/hooks";
153
+ }
154
+ return config;
147
155
  }
148
156
  async function writeConfig(cwd, config) {
149
157
  const configPath = getConfigPath(cwd);
150
158
  await fs2.writeJson(configPath, config, { spaces: 2 });
151
159
  }
152
160
  function resolveAlias(alias, srcDir) {
153
- return alias.replace(/^@\//, srcDir + "/").replace(/^~\//, srcDir + "/");
161
+ const prefix = srcDir === "." ? "" : srcDir + "/";
162
+ return alias.replace(/^@\//, prefix).replace(/^~\//, prefix);
154
163
  }
155
164
  function resolveComponentsDir(cwd, config) {
156
165
  const relPath = resolveAlias(config.aliases.components, config.srcDir);
157
166
  return path2.resolve(cwd, relPath);
158
167
  }
168
+ function resolveUtilsDir(cwd, config) {
169
+ const relPath = resolveAlias(config.aliases.utils, config.srcDir);
170
+ return path2.resolve(cwd, path2.dirname(relPath));
171
+ }
172
+ function resolveHooksDir(cwd, config) {
173
+ const relPath = resolveAlias(config.aliases.hooks, config.srcDir);
174
+ return path2.resolve(cwd, relPath);
175
+ }
159
176
 
160
177
  // src/registry/themes.ts
161
178
  var themes = [
@@ -166,7 +183,7 @@ var themes = [
166
183
  light: {
167
184
  "--background": "40 20% 98%",
168
185
  "--foreground": "220 15% 15%",
169
- "--primary": "175 100% 31%",
186
+ "--primary": "35 55% 51%",
170
187
  "--primary-foreground": "0 0% 100%",
171
188
  "--secondary": "40 10% 92%",
172
189
  "--secondary-foreground": "220 15% 15%",
@@ -177,13 +194,13 @@ var themes = [
177
194
  "--border": "40 10% 88%",
178
195
  "--muted": "40 10% 92%",
179
196
  "--muted-foreground": "220 10% 40%",
180
- "--ring": "175 100% 31%",
197
+ "--ring": "35 55% 51%",
181
198
  "--radius": "0.5rem"
182
199
  },
183
200
  dark: {
184
201
  "--background": "222 47% 11%",
185
202
  "--foreground": "210 40% 98%",
186
- "--primary": "170 65% 50%",
203
+ "--primary": "35 55% 60%",
187
204
  "--primary-foreground": "222 47% 11%",
188
205
  "--secondary": "217 33% 17%",
189
206
  "--secondary-foreground": "210 40% 98%",
@@ -194,7 +211,7 @@ var themes = [
194
211
  "--border": "217 33% 20%",
195
212
  "--muted": "217 33% 17%",
196
213
  "--muted-foreground": "210 20% 70%",
197
- "--ring": "170 65% 50%"
214
+ "--ring": "35 55% 60%"
198
215
  }
199
216
  }
200
217
  },
@@ -462,14 +479,16 @@ async function initCommand(opts) {
462
479
  const projectType = await detectProjectType(cwd);
463
480
  const packageManager = await detectPackageManager(cwd);
464
481
  const detectedSrcDir = await detectSrcDir(cwd);
465
- const defaultCssPath = getDefaultCssPath(projectType);
482
+ const defaultCssPath = getDefaultCssPath(projectType, detectedSrcDir);
466
483
  const tailwindVersion = await detectTailwindVersion(cwd, defaultCssPath);
467
484
  if (projectType !== "unknown") {
468
485
  p.log.info(`Detected project: ${chalk2.cyan(projectType)}`);
469
486
  }
470
487
  p.log.info(`Package manager: ${chalk2.cyan(packageManager)}`);
471
488
  p.log.info(`Tailwind CSS: ${chalk2.cyan(tailwindVersion)}`);
472
- p.log.info(`Source directory: ${chalk2.cyan(detectedSrcDir + "/")}`);
489
+ const srcDirDisplay = detectedSrcDir === "." ? "project root" : detectedSrcDir + "/";
490
+ p.log.info(`Source directory: ${chalk2.cyan(srcDirDisplay)}`);
491
+ const examplePrefix = detectedSrcDir === "." ? "" : detectedSrcDir + "/";
473
492
  const responses = await p.group(
474
493
  {
475
494
  theme: () => p.select({
@@ -481,20 +500,25 @@ async function initCommand(opts) {
481
500
  initialValue: "kahgel"
482
501
  }),
483
502
  srcDir: () => p.text({
484
- message: `Base directory that @ resolves to:`,
503
+ message: `Base directory that @ resolves to (use "." for project root):`,
485
504
  placeholder: detectedSrcDir,
486
505
  defaultValue: detectedSrcDir
487
506
  }),
488
507
  componentsPath: () => p.text({
489
- message: `Where to add UI components (e.g. ${detectedSrcDir}/components/ui):`,
508
+ message: `Where to add UI components (e.g. ${examplePrefix}components/ui):`,
490
509
  placeholder: "@/components/ui",
491
510
  defaultValue: "@/components/ui"
492
511
  }),
493
512
  utilsPath: () => p.text({
494
- message: `Where to create the cn() helper (e.g. ${detectedSrcDir}/lib/utils.ts):`,
513
+ message: `Where to create the cn() helper (e.g. ${examplePrefix}lib/utils.ts):`,
495
514
  placeholder: "@/lib/utils",
496
515
  defaultValue: "@/lib/utils"
497
516
  }),
517
+ hooksPath: () => p.text({
518
+ message: `Where to add hooks (e.g. ${examplePrefix}hooks):`,
519
+ placeholder: "@/hooks",
520
+ defaultValue: "@/hooks"
521
+ }),
498
522
  cssPath: () => p.text({
499
523
  message: `Path to your global CSS file:`,
500
524
  placeholder: defaultCssPath,
@@ -531,7 +555,8 @@ async function initCommand(opts) {
531
555
  },
532
556
  aliases: {
533
557
  components: responses.componentsPath,
534
- utils: responses.utilsPath
558
+ utils: responses.utilsPath,
559
+ hooks: responses.hooksPath
535
560
  }
536
561
  };
537
562
  await writeConfig(cwd, config);
@@ -547,8 +572,12 @@ async function initCommand(opts) {
547
572
  }
548
573
  s.stop("Theme configured.");
549
574
  s.start("Setting up utilities...");
575
+ const resolveAliasPath = (alias) => {
576
+ const prefix = srcDir === "." ? "" : srcDir + "/";
577
+ return alias.replace(/^@\//, prefix).replace(/^~\//, prefix);
578
+ };
550
579
  const utilsAlias = config.aliases.utils;
551
- const utilsRelPath = utilsAlias.replace(/^@\//, srcDir + "/").replace(/^~\//, srcDir + "/") + ".ts";
580
+ const utilsRelPath = resolveAliasPath(utilsAlias) + ".ts";
552
581
  const utilsDestPath = path3.resolve(cwd, utilsRelPath);
553
582
  const utilsDir = path3.dirname(utilsDestPath);
554
583
  await fs3.ensureDir(utilsDir);
@@ -564,7 +593,7 @@ export function cn(...inputs: ClassValue[]) {
564
593
  if (tailwindVersion === "v3" && config.tailwind.config) {
565
594
  s.start("Configuring Tailwind CSS...");
566
595
  const tailwindConfigPath = path3.resolve(cwd, config.tailwind.config);
567
- const componentsRelPath = config.aliases.components.replace(/^@\//, srcDir + "/").replace(/^~\//, srcDir + "/");
596
+ const componentsRelPath = resolveAliasPath(config.aliases.components);
568
597
  if (await fs3.pathExists(tailwindConfigPath)) {
569
598
  let tailwindContent = await fs3.readFile(tailwindConfigPath, "utf-8");
570
599
  const contentPath = `./${componentsRelPath}/**/*.{ts,tsx}`;
@@ -712,6 +741,10 @@ module.exports = {
712
741
  p.log.warn(`Please manually install: ${chalk2.cyan(baseDeps.join(" "))}`);
713
742
  }
714
743
  logger.break();
744
+ const showResolvedPath = (alias, addExtension) => {
745
+ const resolved = resolveAliasPath(alias);
746
+ return resolved + (addExtension || "");
747
+ };
715
748
  p.note(
716
749
  [
717
750
  `${chalk2.green("Rad UI has been initialized!")}`,
@@ -721,8 +754,9 @@ module.exports = {
721
754
  "",
722
755
  `Theme: ${chalk2.cyan(selectedTheme?.label || config.theme)}`,
723
756
  `Tailwind: ${chalk2.cyan(tailwindVersion)}`,
724
- `Components: ${chalk2.cyan(config.aliases.components)} \u2192 ${chalk2.dim(srcDir + "/" + config.aliases.components.replace(/^@\//, "").replace(/^~\//, ""))}`,
725
- `Utils: ${chalk2.cyan(config.aliases.utils)} \u2192 ${chalk2.dim(srcDir + "/" + config.aliases.utils.replace(/^@\//, "").replace(/^~\//, "") + ".ts")}`
757
+ `Components: ${chalk2.cyan(config.aliases.components)} \u2192 ${chalk2.dim(showResolvedPath(config.aliases.components))}`,
758
+ `Utils: ${chalk2.cyan(config.aliases.utils)} \u2192 ${chalk2.dim(showResolvedPath(config.aliases.utils, ".ts"))}`,
759
+ `Hooks: ${chalk2.cyan(config.aliases.hooks)} \u2192 ${chalk2.dim(showResolvedPath(config.aliases.hooks))}`
726
760
  ].join("\n"),
727
761
  "Next steps"
728
762
  );
@@ -1035,6 +1069,48 @@ async function fetchRegistryIndex(registryUrl) {
1035
1069
  }
1036
1070
 
1037
1071
  // src/commands/add.ts
1072
+ function classifyFile(filePath) {
1073
+ const fileName = path4.basename(filePath);
1074
+ const ext = path4.extname(filePath);
1075
+ if (/^use[-_]?[a-zA-Z]/.test(fileName) && ext === ".ts") {
1076
+ return "hook";
1077
+ }
1078
+ if (ext === ".tsx") {
1079
+ return "component";
1080
+ }
1081
+ if (ext === ".ts") {
1082
+ return "util";
1083
+ }
1084
+ return "component";
1085
+ }
1086
+ function transformRelativeImports(content, originalFilePath, componentFiles, config, componentName) {
1087
+ let result = content;
1088
+ const relativeImportRegex = /from\s+["']\.\/([^"']+)["']/g;
1089
+ let match;
1090
+ while ((match = relativeImportRegex.exec(content)) !== null) {
1091
+ const importedModule = match[1];
1092
+ const importedFile = componentFiles.find((f) => {
1093
+ const baseName = path4.basename(f.originalPath, path4.extname(f.originalPath));
1094
+ return baseName === importedModule || f.originalPath.includes(`/${importedModule}.`) || f.originalPath.endsWith(`/${importedModule}.ts`) || f.originalPath.endsWith(`/${importedModule}.tsx`);
1095
+ });
1096
+ if (importedFile && importedFile.fileType !== "component") {
1097
+ let newImportPath;
1098
+ if (importedFile.fileType === "hook") {
1099
+ const hookFileName = path4.basename(importedFile.destPath, ".ts");
1100
+ newImportPath = `${config.aliases.hooks}/${hookFileName}`;
1101
+ } else {
1102
+ const utilFileName = path4.basename(importedFile.destPath, ".ts");
1103
+ const utilsBase = config.aliases.utils.replace(/\/utils$/, "");
1104
+ newImportPath = `${utilsBase}/${componentName}/${utilFileName}`;
1105
+ }
1106
+ result = result.replace(
1107
+ new RegExp(`from\\s+["']\\.\\/${importedModule}["']`, "g"),
1108
+ `from "${newImportPath}"`
1109
+ );
1110
+ }
1111
+ }
1112
+ return result;
1113
+ }
1038
1114
  async function getComponentContent(name, config) {
1039
1115
  if (!config.registry) {
1040
1116
  throw new Error(
@@ -1114,6 +1190,8 @@ async function addCommand(componentNames, opts) {
1114
1190
  );
1115
1191
  }
1116
1192
  const componentsDir = opts.path ? path4.resolve(cwd, opts.path) : resolveComponentsDir(cwd, config);
1193
+ const hooksDir = resolveHooksDir(cwd, config);
1194
+ const utilsDir = resolveUtilsDir(cwd, config);
1117
1195
  if (!opts.overwrite) {
1118
1196
  const existing = [];
1119
1197
  for (const name of allNames) {
@@ -1146,8 +1224,13 @@ async function addCommand(componentNames, opts) {
1146
1224
  const s = p2.spinner();
1147
1225
  s.start("Adding components...");
1148
1226
  await fs4.ensureDir(componentsDir);
1227
+ await fs4.ensureDir(hooksDir);
1228
+ await fs4.ensureDir(utilsDir);
1149
1229
  let copiedCount = 0;
1230
+ let hooksCount = 0;
1231
+ let utilsCount = 0;
1150
1232
  const allDeps = [];
1233
+ const filePlacements = /* @__PURE__ */ new Map();
1151
1234
  for (const name of allNames) {
1152
1235
  const item = await getComponentContent(name, config);
1153
1236
  if (!item || item.files.length === 0) {
@@ -1157,17 +1240,50 @@ async function addCommand(componentNames, opts) {
1157
1240
  if (item.dependencies) {
1158
1241
  allDeps.push(...item.dependencies);
1159
1242
  }
1243
+ const componentFiles = [];
1160
1244
  for (const file of item.files) {
1161
- let content = file.content;
1162
- content = transformImports(content, config);
1163
1245
  const relPath = file.path.replace(/^ui\//, "");
1164
- const destPath = path4.resolve(componentsDir, relPath);
1165
- await fs4.ensureDir(path4.dirname(destPath));
1166
- await fs4.writeFile(destPath, content, "utf-8");
1246
+ const fileName = path4.basename(relPath);
1247
+ const fileType = classifyFile(relPath);
1248
+ let destPath;
1249
+ if (fileType === "component") {
1250
+ destPath = path4.resolve(componentsDir, relPath);
1251
+ } else if (fileType === "hook") {
1252
+ destPath = path4.resolve(hooksDir, fileName);
1253
+ hooksCount++;
1254
+ } else {
1255
+ destPath = path4.resolve(utilsDir, name, fileName);
1256
+ utilsCount++;
1257
+ }
1258
+ componentFiles.push({
1259
+ originalPath: relPath,
1260
+ destPath,
1261
+ content: file.content,
1262
+ fileType
1263
+ });
1264
+ filePlacements.set(relPath, { destPath, fileType });
1265
+ }
1266
+ for (const fileInfo of componentFiles) {
1267
+ let content = fileInfo.content;
1268
+ content = transformImports(content, config);
1269
+ if (fileInfo.fileType === "component") {
1270
+ content = transformRelativeImports(
1271
+ content,
1272
+ fileInfo.originalPath,
1273
+ componentFiles,
1274
+ config,
1275
+ name
1276
+ );
1277
+ }
1278
+ await fs4.ensureDir(path4.dirname(fileInfo.destPath));
1279
+ await fs4.writeFile(fileInfo.destPath, content, "utf-8");
1167
1280
  copiedCount++;
1168
1281
  }
1169
1282
  }
1170
- s.stop(`Added ${copiedCount} component file(s).`);
1283
+ const summary = [`Added ${copiedCount} file(s)`];
1284
+ if (hooksCount > 0) summary.push(`${hooksCount} hook(s)`);
1285
+ if (utilsCount > 0) summary.push(`${utilsCount} util(s)`);
1286
+ s.stop(summary.join(", ") + ".");
1171
1287
  const npmDeps = collectNpmDependencies(allNames);
1172
1288
  const depsFromRegistry = [...new Set(allDeps)];
1173
1289
  const depsToInstall = Object.entries(npmDeps).map(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quarklab/rad-ui",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "A CLI for adding Rad UI components to your project. Beautiful Persian-themed React components built on Radix UI and Tailwind CSS.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -44,5 +44,5 @@
44
44
  "type": "git",
45
45
  "url": "git+https://github.com/quarklab/rad-ui.git"
46
46
  },
47
- "homepage": "https://rad-ui.com"
47
+ "homepage": "https://quarklab.dev"
48
48
  }