saha-ui 1.11.0 → 1.11.1

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/bin/cli.js +703 -82
  2. package/package.json +1 -1
package/bin/cli.js CHANGED
@@ -3,26 +3,109 @@
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { execSync } from "node:child_process";
6
+ import { fileURLToPath } from "node:url";
6
7
 
7
8
  const R = process.cwd();
8
9
  const a = process.argv.slice(2);
9
10
  const c = a[ 0 ] || "init";
10
11
 
11
- // ───────────────────────────────────────────────
12
- // 🆕 Step 1: Ensure saha-ui is installed in project root
13
- // ───────────────────────────────────────────────
12
+ // Helpers
13
+ const rd = (f) => fs.readFileSync(f, "utf8");
14
+ const wr = (f, s) => {
15
+ fs.mkdirSync(path.dirname(f), { recursive: true });
16
+ fs.writeFileSync(f, s, "utf8");
17
+ };
18
+ const fE = (f) => fs.existsSync(f) && fs.statSync(f).isFile();
19
+ const dE = (d) => fs.existsSync(d) && fs.statSync(d).isDirectory();
20
+
21
+ // package.json
22
+ const P = (() => {
23
+ try {
24
+ return JSON.parse(rd(path.join(R, "package.json")));
25
+ } catch {
26
+ return {};
27
+ }
28
+ })();
29
+ const D = { ...(P.dependencies || {}), ...(P.devDependencies || {}) };
30
+ const F = D.next ? "next" : "react";
31
+
32
+ // ----------------------------------------------
33
+ // Tailwind detection from package.json only
34
+ // ----------------------------------------------
35
+ const TAILWIND_CONFIG_FILES = [
36
+ "tailwind.config.js",
37
+ "tailwind.config.ts",
38
+ "tailwind.config.mjs",
39
+ "tailwind.config.cjs",
40
+ "tailwind.config.mts",
41
+ "tailwind.config.cts",
42
+ ];
14
43
 
44
+ const parseMajor = (v) => {
45
+ const m = String(v || "").match(/(\d+)/);
46
+ return m ? parseInt(m[ 1 ], 10) : 0;
47
+ };
48
+
49
+ function assertTailwindFromPkgJson() {
50
+ const versionRange =
51
+ (P.dependencies && P.dependencies.tailwindcss) ||
52
+ (P.devDependencies && P.devDependencies.tailwindcss);
53
+
54
+ if (!versionRange) {
55
+ console.error("❌ Tailwind CSS is not listed in package.json.");
56
+ console.error("Please add Tailwind first, then re-run this command.");
57
+ console.error("Examples:");
58
+ console.error(" - v4: npm i -D tailwindcss");
59
+ console.error(" Optional: npm i -D @tailwindcss/postcss // PostCSS flow");
60
+ console.error(" Optional: npm i -D @tailwindcss/cli // CLI flow");
61
+ console.error(" - v3: npm i -D tailwindcss postcss autoprefixer && npx tailwindcss init -p");
62
+ process.exit(1);
63
+ }
64
+
65
+ const major = parseMajor(versionRange);
66
+ console.log(`🔎 Tailwind in package.json: "${versionRange}" (detected major v${major})`);
67
+ return { versionRange, major };
68
+ }
69
+
70
+ // v3 requires a tailwind.config.* file to exist
71
+ function assertTailwindV3ConfigPresent() {
72
+ const configPath = TAILWIND_CONFIG_FILES
73
+ .map((f) => path.join(R, f))
74
+ .find((p) => fE(p));
75
+ if (!configPath) {
76
+ console.error("❌ Tailwind v3 detected, but no tailwind.config.* found.");
77
+ console.error("Please run: npx tailwindcss init -p");
78
+ process.exit(1);
79
+ }
80
+ console.log("✅ Tailwind v3 config file detected.");
81
+ }
82
+
83
+ // ----------------------------------------------
84
+ // React presence
85
+ // ----------------------------------------------
86
+ function assertReactPresent() {
87
+ if (!D.react) {
88
+ console.error("❌ This setup currently supports React/Next projects only.");
89
+ console.error("React dependency was not found in package.json.");
90
+ process.exit(1);
91
+ }
92
+ console.log("✅ React detected.");
93
+ }
94
+
95
+ // ----------------------------------------------
96
+ // saha-ui presence + deps sync
97
+ // ----------------------------------------------
15
98
  function ensureSahaUIInstalled() {
16
99
  try {
17
100
  const pkgPath = path.join(R, "package.json");
18
- if (!fs.existsSync(pkgPath)) {
101
+ if (!fE(pkgPath)) {
19
102
  console.error("❌ No package.json found in this directory.");
20
103
  console.error("Please run this command inside your project folder.");
21
104
  process.exit(1);
22
105
  }
23
106
 
24
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
25
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
107
+ const pkg = JSON.parse(rd(pkgPath));
108
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
26
109
  if (!deps[ "saha-ui" ]) {
27
110
  console.log("\n📦 saha-ui not found in your project.");
28
111
  console.log("Installing saha-ui@latest for you...\n");
@@ -41,21 +124,17 @@ function ensureSahaUIInstalled() {
41
124
  }
42
125
  }
43
126
 
44
- // ───────────────────────────────────────────────
45
- // 🆕 Step 2: Install saha-ui dependencies in main project
46
- // ───────────────────────────────────────────────
47
-
48
127
  function installSahaUIDeps() {
49
128
  try {
50
- const sahaPath = path.dirname(new URL(import.meta.url).pathname);
129
+ const sahaPath = path.dirname(fileURLToPath(import.meta.url));
51
130
  const pkgPath = path.resolve(sahaPath, "../package.json");
52
131
 
53
- if (!fs.existsSync(pkgPath)) {
132
+ if (!fE(pkgPath)) {
54
133
  console.warn("⚠️ Could not find saha-ui package.json to sync dependencies.");
55
134
  return;
56
135
  }
57
136
 
58
- const sahaPkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
137
+ const sahaPkg = JSON.parse(rd(pkgPath));
59
138
  const deps = { ...(sahaPkg.dependencies || {}), ...(sahaPkg.peerDependencies || {}) };
60
139
 
61
140
  const depNames = Object.entries(deps)
@@ -67,7 +146,7 @@ function installSahaUIDeps() {
67
146
  return;
68
147
  }
69
148
 
70
- console.log("📦 Installing saha-ui peer dependencies in your project...");
149
+ console.log("📦 Installing saha-ui dependencies into your project...");
71
150
  execSync(`npm install ${depNames} --save-dev`, {
72
151
  stdio: "inherit",
73
152
  cwd: R,
@@ -78,63 +157,9 @@ function installSahaUIDeps() {
78
157
  }
79
158
  }
80
159
 
81
- ensureSahaUIInstalled();
82
-
83
- // ───────────────────────────────────────────────
84
- // Your Original saha-ui Tailwind/CSS logic below
85
- // ───────────────────────────────────────────────
86
-
87
- const rd = (f) => fs.readFileSync(f, "utf8");
88
- const wr = (f, s) => {
89
- fs.mkdirSync(path.dirname(f), { recursive: true });
90
- fs.writeFileSync(f, s, "utf8");
91
- };
92
- const fE = (f) => fs.existsSync(f) && fs.statSync(f).isFile();
93
- const dE = (d) => fs.existsSync(d) && fs.statSync(d).isDirectory();
94
-
95
- // Parse package.json
96
- const P = (() => {
97
- try {
98
- return JSON.parse(rd(path.join(R, "package.json")));
99
- } catch {
100
- return {};
101
- }
102
- })();
103
-
104
- const D = { ...(P.dependencies || {}), ...(P.devDependencies || {}) };
105
- const F = D.next ? "next" : "react";
106
-
107
- const checkTailwind = () => {
108
- const tailwindVersion = D.tailwindcss || D.tailwind;
109
-
110
- if (!tailwindVersion) {
111
- console.error("\n❌ Error: Tailwind CSS is not installed!");
112
- console.error("\nInstalling Tailwind CSS automatically for you...");
113
- execSync("npm install -D tailwindcss postcss autoprefixer", { stdio: "inherit", cwd: R });
114
- execSync("npx tailwindcss init -p", { stdio: "inherit", cwd: R });
115
- console.log("\n✅ Tailwind CSS installed and configured!");
116
- }
117
-
118
- const configFiles = [
119
- "tailwind.config.js",
120
- "tailwind.config.ts",
121
- "tailwind.config.mjs",
122
- "tailwind.config.cjs",
123
- ];
124
- const hasConfig = configFiles.some((f) => fE(path.join(R, f)));
125
-
126
- if (!hasConfig) {
127
- console.log("\n⚙️ Creating Tailwind config...");
128
- execSync("npx tailwindcss init -p", { stdio: "inherit", cwd: R });
129
- }
130
-
131
- const versionMatch = (D.tailwindcss || "3.0.0").match(/(\d+)\.(\d+)\.(\d+)/);
132
- const majorVersion = versionMatch ? parseInt(versionMatch[ 1 ]) : 3;
133
- console.log(`✅ Tailwind CSS v${D.tailwindcss || "latest"} detected`);
134
-
135
- return { version: D.tailwindcss || "latest", major: majorVersion };
136
- };
137
-
160
+ // ----------------------------------------------
161
+ // CSS file selection (Next vs React)
162
+ // ----------------------------------------------
138
163
  const N = [
139
164
  "app/globals.css",
140
165
  "app/global.css",
@@ -153,6 +178,7 @@ const pick = () => {
153
178
  const f = path.join(R, x);
154
179
  if (fE(f)) return f;
155
180
  }
181
+
156
182
  if (F === "next") {
157
183
  const A = dE(path.join(R, "app")) && path.join(R, "app/globals.css");
158
184
  const B = dE(path.join(R, "src/app")) && path.join(R, "src/app/globals.css");
@@ -160,20 +186,601 @@ const pick = () => {
160
186
  const T = dE(path.join(R, "src/styles")) && path.join(R, "src/styles/globals.css");
161
187
  return A || B || S || T || path.join(R, "app/globals.css");
162
188
  }
189
+
163
190
  return dE(path.join(R, "src")) ? path.join(R, "src/index.css") : path.join(R, "index.css");
164
191
  };
165
192
 
166
- // (keep the rest of your CSS injection logic exactly as in your current file)
167
-
193
+ // ----------------------------------------------
194
+ // CSS payloads (your CSS)
195
+ // ----------------------------------------------
168
196
  const M = "/* saha-ui */";
169
197
  const TW = /@import\s+["']tailwindcss["'];?/;
170
198
 
171
- // ... (your full CSS_V4, CSS_V3, updateTailwindConfig, inject etc remain unchanged)
199
+ // CSS for Tailwind v4+
200
+ const CSS_V4 = `@import "tailwindcss";
201
+
202
+ @custom-variant dark (&:is(.dark *));
203
+
204
+ @theme inline {
205
+ --radius-sm: calc(var(--radius) - 4px);
206
+ --radius-md: calc(var(--radius) - 2px);
207
+ --radius-lg: var(--radius);
208
+ --radius-xl: calc(var(--radius) + 4px);
209
+
210
+ --color-background: var(--background);
211
+ --color-foreground: var(--foreground);
212
+ --color-card: var(--card);
213
+ --color-card-foreground: var(--card-foreground);
214
+ --color-popover: var(--popover);
215
+ --color-popover-foreground: var(--popover-foreground);
216
+ --color-primary: var(--primary);
217
+ --color-primary-foreground: var(--primary-foreground);
218
+ --color-secondary: var(--secondary);
219
+ --color-secondary-foreground: var(--secondary-foreground);
220
+ --color-muted: var(--muted);
221
+ --color-muted-foreground: var(--muted-foreground);
222
+ --color-accent: var(--accent);
223
+ --color-accent-foreground: var(--accent-foreground);
224
+ --color-destructive: var(--destructive);
225
+ --color-destructive-foreground: var(--destructive-foreground);
226
+ --color-border: var(--border);
227
+ --color-input: var(--input);
228
+ --color-ring: var(--ring);
229
+ --color-chart-1: var(--chart-1);
230
+ --color-chart-2: var(--chart-2);
231
+ --color-chart-3: var(--chart-3);
232
+ --color-chart-4: var(--chart-4);
233
+ --color-chart-5: var(--chart-5);
234
+ --color-success: var(--success);
235
+ --color-success-foreground: var(--success-foreground);
236
+ --color-warning: var(--warning);
237
+ --color-warning-foreground: var(--warning-foreground);
238
+ --color-error: var(--error);
239
+ --color-error-foreground: var(--error-foreground);
240
+ --color-info: var(--info);
241
+ --color-info-foreground: var(--info-foreground);
242
+ }
243
+
244
+ :root {
245
+ --radius: 0.625rem;
246
+ --background: oklch(0.98 0.003 200);
247
+ --foreground: oklch(0.15 0.01 200);
248
+ --card: oklch(1 0 0);
249
+ --card-foreground: oklch(0.15 0.01 200);
250
+ --popover: oklch(1 0 0);
251
+ --popover-foreground: oklch(0.15 0.01 200);
252
+ --primary: oklch(48.151% 0.23085 269.463);
253
+ --primary-foreground: oklch(1 0 0);
254
+ --secondary: oklch(0.65 0.25 340);
255
+ --secondary-foreground: oklch(1 0 0);
256
+ --muted: oklch(0.96 0.005 200);
257
+ --muted-foreground: oklch(0.45 0.01 200);
258
+ --accent: oklch(0.65 0.12 185);
259
+ --accent-foreground: oklch(1 0 0);
260
+ --success: oklch(0.60 0.15 145);
261
+ --success-foreground: oklch(1 0 0);
262
+ --warning: oklch(0.70 0.15 65);
263
+ --warning-foreground: oklch(0.15 0.01 200);
264
+ --error: oklch(0.60 0.20 25);
265
+ --error-foreground: oklch(1 0 0);
266
+ --destructive: oklch(0.60 0.20 25);
267
+ --destructive-foreground: oklch(1 0 0);
268
+ --info: oklch(0.60 0.15 250);
269
+ --info-foreground: oklch(1 0 0);
270
+ --border: oklch(0.92 0.005 200);
271
+ --input: oklch(0.96 0.005 200);
272
+ --ring: oklch(0.60 0.18 275);
273
+ --chart-1: oklch(0.60 0.18 275);
274
+ --chart-2: oklch(0.60 0.15 145);
275
+ --chart-3: oklch(0.60 0.15 250);
276
+ --chart-4: oklch(0.65 0.25 340);
277
+ --chart-5: oklch(0.65 0.12 185);
278
+
279
+ --glass-bg: oklch(1 0 0 / 0.25);
280
+ --glass-bg-hover: oklch(1 0 0 / 0.35);
281
+ --glass-border: oklch(0.60 0.18 275 / 0.15);
282
+ --glass-shadow: 0 8px 32px 0 oklch(0.60 0.18 275 / 0.12);
283
+ --glass-blur: 16px;
284
+ }
285
+
286
+ .dark {
287
+ --background: oklch(0.08 0.005 200);
288
+ --foreground: oklch(0.95 0.005 200);
289
+ --card: oklch(0.12 0.01 200);
290
+ --card-foreground: oklch(0.95 0.005 200);
291
+ --popover: oklch(0.12 0.01 200);
292
+ --popover-foreground: oklch(0.95 0.005 200);
293
+ --primary: oklch(41.145% 0.14945 272.396);
294
+ --primary-foreground: oklch(0.98 0.003 200);
295
+ --secondary: oklch(0.70 0.25 340);
296
+ --secondary-foreground: oklch(0.98 0.003 200);
297
+ --muted: oklch(0.15 0.01 200);
298
+ --muted-foreground: oklch(0.65 0.005 200);
299
+ --accent: oklch(0.70 0.15 185);
300
+ --accent-foreground: oklch(0.98 0.003 200);
301
+ --success: oklch(0.65 0.18 145);
302
+ --success-foreground: oklch(0.98 0.003 200);
303
+ --warning: oklch(0.75 0.18 65);
304
+ --warning-foreground: oklch(0.98 0.003 200);
305
+ --error: oklch(0.65 0.22 25);
306
+ --error-foreground: oklch(0.98 0.003 200);
307
+ --destructive: oklch(0.65 0.22 25);
308
+ --destructive-foreground: oklch(0.98 0.003 200);
309
+ --info: oklch(0.65 0.18 250);
310
+ --info-foreground: oklch(0.98 0.003 200);
311
+ --border: oklch(0.20 0.01 200);
312
+ --input: oklch(0.15 0.01 200);
313
+ --ring: oklch(0.68 0.20 275);
314
+ --chart-1: oklch(0.68 0.20 275);
315
+ --chart-2: oklch(0.65 0.18 145);
316
+ --chart-3: oklch(0.65 0.18 250);
317
+ --chart-4: oklch(0.70 0.25 340);
318
+ --chart-5: oklch(0.70 0.15 185);
319
+
320
+ --glass-bg: oklch(0.12 0.01 200 / 0.5);
321
+ --glass-bg-hover: oklch(0.12 0.01 200 / 0.7);
322
+ --glass-border: oklch(0.68 0.20 275 / 0.2);
323
+ --glass-shadow: 0 8px 32px 0 oklch(0 0 0 / 0.6);
324
+ --glass-blur: 16px;
325
+ }
326
+
327
+ @layer base {
328
+ * {
329
+ @apply border-border outline-ring/50;
330
+ }
331
+
332
+ body {
333
+ @apply bg-background text-foreground;
334
+ background: linear-gradient(
335
+ 135deg,
336
+ oklch(0.98 0.003 200) 0%,
337
+ oklch(0.96 0.02 270) 50%,
338
+ oklch(0.98 0.02 340) 100%
339
+ );
340
+ background-attachment: fixed;
341
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
342
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
343
+ -webkit-font-smoothing: antialiased;
344
+ -moz-osx-font-smoothing: grayscale;
345
+ }
346
+
347
+ .dark body {
348
+ @apply text-foreground;
349
+ color: oklch(0.95 0.005 200);
350
+ background: linear-gradient(
351
+ 135deg,
352
+ oklch(0.08 0.005 200) 0%,
353
+ oklch(0.10 0.01 200) 50%,
354
+ oklch(0.12 0.01 200) 100%
355
+ );
356
+ background-attachment: fixed;
357
+ }
358
+
359
+ .dark {
360
+ color-scheme: dark;
361
+ }
362
+ }
363
+
364
+ @layer components {
365
+ .glass {
366
+ background: var(--glass-bg);
367
+ backdrop-filter: blur(var(--glass-blur)) saturate(180%);
368
+ -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(180%);
369
+ border: 1px solid var(--glass-border);
370
+ box-shadow: var(--glass-shadow);
371
+ position: relative;
372
+ }
373
+
374
+ .glass::before {
375
+ content: '';
376
+ position: absolute;
377
+ inset: 0;
378
+ border-radius: inherit;
379
+ padding: 1px;
380
+ background: linear-gradient(
381
+ 135deg,
382
+ oklch(1 0 0 / 0.4) 0%,
383
+ oklch(1 0 0 / 0.1) 50%,
384
+ oklch(1 0 0 / 0) 100%
385
+ );
386
+ -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
387
+ -webkit-mask-composite: xor;
388
+ mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
389
+ mask-composite: exclude;
390
+ pointer-events: none;
391
+ }
392
+
393
+ .dark .glass::before {
394
+ background: linear-gradient(
395
+ 135deg,
396
+ oklch(0.68 0.20 275 / 0.3) 0%,
397
+ oklch(0.68 0.20 275 / 0.1) 50%,
398
+ oklch(0.68 0.20 275 / 0) 100%
399
+ );
400
+ }
401
+
402
+ .glass-hover:hover {
403
+ background: var(--glass-bg-hover);
404
+ transform: translateY(-2px);
405
+ box-shadow: 0 12px 48px 0 oklch(0.60 0.18 275 / 0.2);
406
+ }
407
+
408
+ .dark .glass-hover:hover {
409
+ box-shadow: 0 12px 48px 0 oklch(0 0 0 / 0.7);
410
+ }
411
+
412
+ .glass-strong {
413
+ background: var(--glass-bg);
414
+ backdrop-filter: blur(24px) saturate(200%);
415
+ -webkit-backdrop-filter: blur(24px) saturate(200%);
416
+ border: 1px solid var(--glass-border);
417
+ box-shadow: var(--glass-shadow), inset 0 1px 0 oklch(1 0 0 / 0.3);
418
+ }
419
+
420
+ .dark .glass-strong {
421
+ box-shadow: var(--glass-shadow), inset 0 1px 0 oklch(0.68 0.20 275 / 0.2);
422
+ }
423
+
424
+ .glass-subtle {
425
+ background: var(--glass-bg);
426
+ backdrop-filter: blur(8px) saturate(150%);
427
+ -webkit-backdrop-filter: blur(8px) saturate(150%);
428
+ border: 1px solid var(--glass-border);
429
+ box-shadow: 0 1px 2px 0 oklch(0 0 0 / 0.05);
430
+ }
431
+ }
432
+
433
+ @layer utilities {
434
+ @keyframes progress-stripes {
435
+ 0% {
436
+ background-position: 0 0;
437
+ }
438
+ 100% {
439
+ background-position: 1.5rem 0;
440
+ }
441
+ }
442
+
443
+ @keyframes progress-indeterminate {
444
+ 0% {
445
+ left: -40%;
446
+ transform: scaleX(0.6);
447
+ }
448
+ 50% {
449
+ transform: scaleX(1);
450
+ }
451
+ 100% {
452
+ left: 100%;
453
+ transform: scaleX(0.6);
454
+ }
455
+ }
456
+
457
+ @keyframes progress-shimmer {
458
+ 0% {
459
+ transform: translateX(-100%) scaleX(0);
460
+ opacity: 0;
461
+ }
462
+ 10% {
463
+ opacity: 1;
464
+ }
465
+ 50% {
466
+ transform: translateX(0%) scaleX(1);
467
+ }
468
+ 90% {
469
+ opacity: 1;
470
+ }
471
+ 100% {
472
+ transform: translateX(100%) scaleX(0);
473
+ opacity: 0;
474
+ }
475
+ }
476
+
477
+ @keyframes progress-glow-pulse {
478
+ 0%, 100% {
479
+ filter: brightness(1) saturate(1);
480
+ }
481
+ 50% {
482
+ filter: brightness(1.15) saturate(1.2);
483
+ }
484
+ }
485
+
486
+ @keyframes collapsible-down {
487
+ from {
488
+ height: 0;
489
+ opacity: 0;
490
+ }
491
+ to {
492
+ height: var(--radix-collapsible-content-height);
493
+ opacity: 1;
494
+ }
495
+ }
496
+
497
+ @keyframes collapsible-up {
498
+ from {
499
+ height: var(--radix-collapsible-content-height);
500
+ opacity: 1;
501
+ }
502
+ to {
503
+ height: 0;
504
+ opacity: 0;
505
+ }
506
+ }
507
+
508
+ .scrollbar-none {
509
+ -ms-overflow-style: none;
510
+ scrollbar-width: none;
511
+ }
512
+
513
+ .scrollbar-none::-webkit-scrollbar {
514
+ display: none;
515
+ }
516
+
517
+ [role="menu"],
518
+ [role="listbox"] {
519
+ -ms-overflow-style: none;
520
+ scrollbar-width: none;
521
+ }
522
+
523
+ [role="menu"]::-webkit-scrollbar,
524
+ [role="listbox"]::-webkit-scrollbar {
525
+ display: none;
526
+ }
527
+
528
+ [contenteditable][data-placeholder]:empty:before {
529
+ content: attr(data-placeholder);
530
+ color: hsl(var(--muted-foreground));
531
+ opacity: 0.5;
532
+ pointer-events: none;
533
+ }
534
+
535
+ [contenteditable]:focus:empty:before {
536
+ content: attr(data-placeholder);
537
+ color: hsl(var(--muted-foreground));
538
+ opacity: 0.3;
539
+ }
540
+ }`;
541
+
542
+ // CSS for Tailwind v3
543
+ const CSS_V3 = `@tailwind base;
544
+ @tailwind components;
545
+ @tailwind utilities;
546
+
547
+ :root {
548
+ --radius: 0.625rem;
549
+ --background: 0 0% 98%;
550
+ --foreground: 222 47% 11%;
551
+ --card: 0 0% 100%;
552
+ --card-foreground: 222 47% 11%;
553
+ --popover: 0 0% 100%;
554
+ --popover-foreground: 222 47% 11%;
555
+ --primary: 269 70% 48%;
556
+ --primary-foreground: 0 0% 100%;
557
+ --secondary: 340 75% 65%;
558
+ --secondary-foreground: 0 0% 100%;
559
+ --muted: 220 13% 95%;
560
+ --muted-foreground: 220 9% 46%;
561
+ --accent: 185 50% 65%;
562
+ --accent-foreground: 0 0% 100%;
563
+ --success: 145 60% 60%;
564
+ --success-foreground: 0 0% 100%;
565
+ --warning: 65 60% 70%;
566
+ --warning-foreground: 222 47% 11%;
567
+ --error: 25 80% 60%;
568
+ --error-foreground: 0 0% 100%;
569
+ --destructive: 25 80% 60%;
570
+ --destructive-foreground: 0 0% 100%;
571
+ --info: 250 60% 60%;
572
+ --info-foreground: 0 0% 100%;
573
+ --border: 220 13% 91%;
574
+ --input: 220 13% 95%;
575
+ --ring: 275 70% 60%;
576
+ }
577
+
578
+ .dark {
579
+ --background: 222 47% 8%;
580
+ --foreground: 220 13% 95%;
581
+ --card: 222 47% 12%;
582
+ --card-foreground: 220 13% 95%;
583
+ --popover: 222 47% 12%;
584
+ --popover-foreground: 220 13% 95%;
585
+ --primary: 272 60% 41%;
586
+ --primary-foreground: 0 0% 98%;
587
+ --secondary: 340 75% 70%;
588
+ --secondary-foreground: 0 0% 98%;
589
+ --muted: 222 47% 15%;
590
+ --muted-foreground: 220 13% 65%;
591
+ --accent: 185 60% 70%;
592
+ --accent-foreground: 0 0% 98%;
593
+ --success: 145 70% 65%;
594
+ --success-foreground: 0 0% 98%;
595
+ --warning: 65 70% 75%;
596
+ --warning-foreground: 0 0% 98%;
597
+ --error: 25 85% 65%;
598
+ --error-foreground: 0 0% 98%;
599
+ --destructive: 25 85% 65%;
600
+ --destructive-foreground: 0 0% 98%;
601
+ --info: 250 70% 65%;
602
+ --info-foreground: 0 0% 98%;
603
+ --border: 222 47% 20%;
604
+ --input: 222 47% 15%;
605
+ --ring: 275 75% 68%;
606
+ }
607
+
608
+ @layer base {
609
+ * {
610
+ @apply border-border;
611
+ }
612
+
613
+ body {
614
+ @apply bg-background text-foreground;
615
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
616
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
617
+ -webkit-font-smoothing: antialiased;
618
+ -moz-osx-font-smoothing: grayscale;
619
+ }
620
+ }
621
+
622
+ @layer components {
623
+ .glass {
624
+ background: rgba(255, 255, 255, 0.25);
625
+ backdrop-filter: blur(16px) saturate(180%);
626
+ -webkit-backdrop-filter: blur(16px) saturate(180%);
627
+ border: 1px solid rgba(255, 255, 255, 0.18);
628
+ }
629
+
630
+ .dark .glass {
631
+ background: rgba(18, 18, 23, 0.5);
632
+ border: 1px solid rgba(139, 92, 246, 0.2);
633
+ }
634
+ }`;
635
+
636
+ // ----------------------------------------------
637
+ // Update Tailwind config for v3 (manual hints)
638
+ // ----------------------------------------------
639
+ const updateTailwindConfig = () => {
640
+ const configFiles = [
641
+ "tailwind.config.js",
642
+ "tailwind.config.ts",
643
+ "tailwind.config.mjs",
644
+ "tailwind.config.cjs",
645
+ ];
646
+
647
+ let configPath = null;
648
+ for (const cf of configFiles) {
649
+ const p = path.join(R, cf);
650
+ if (fE(p)) {
651
+ configPath = p;
652
+ break;
653
+ }
654
+ }
655
+
656
+ if (!configPath) {
657
+ console.warn("⚠️ Warning: Could not find Tailwind config file");
658
+ return;
659
+ }
172
660
 
173
- // ───────────────────────────────────────────────
174
- // Main Run
175
- // ───────────────────────────────────────────────
661
+ const config = rd(configPath);
662
+
663
+ if (config.includes("theme:") && config.includes("extend:")) {
664
+ console.log("ℹ️ Tailwind config already has theme.extend");
665
+ console.log("\n📝 Please manually add these to your tailwind.config:");
666
+ console.log(`
667
+ theme: {
668
+ extend: {
669
+ colors: {
670
+ border: "hsl(var(--border))",
671
+ input: "hsl(var(--input))",
672
+ ring: "hsl(var(--ring))",
673
+ background: "hsl(var(--background))",
674
+ foreground: "hsl(var(--foreground))",
675
+ primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))" },
676
+ secondary: { DEFAULT: "hsl(var(--secondary))", foreground: "hsl(var(--secondary-foreground))" },
677
+ muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))" },
678
+ accent: { DEFAULT: "hsl(var(--accent))", foreground: "hsl(var(--accent-foreground))" },
679
+ destructive: { DEFAULT: "hsl(var(--destructive))", foreground: "hsl(var(--destructive-foreground))" },
680
+ success: { DEFAULT: "hsl(var(--success))", foreground: "hsl(var(--success-foreground))" },
681
+ warning: { DEFAULT: "hsl(var(--warning))", foreground: "hsl(var(--warning-foreground))" },
682
+ error: { DEFAULT: "hsl(var(--error))", foreground: "hsl(var(--error-foreground))" },
683
+ info: { DEFAULT: "hsl(var(--info))", foreground: "hsl(var(--info-foreground))" },
684
+ card: { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))" },
685
+ popover: { DEFAULT: "hsl(var(--popover))", foreground: "hsl(var(--popover-foreground))" },
686
+ chart: { 1: "hsl(var(--chart-1))", 2: "hsl(var(--chart-2))", 3: "hsl(var(--chart-3))", 4: "hsl(var(--chart-4))", 5: "hsl(var(--chart-5))" },
687
+ },
688
+ borderRadius: {
689
+ lg: "var(--radius)",
690
+ md: "calc(var(--radius) - 2px)",
691
+ sm: "calc(var(--radius) - 4px)",
692
+ },
693
+ keyframes: {
694
+ "progress-stripes": { "0%": { backgroundPosition: "0 0" }, "100%": { backgroundPosition: "1.5rem 0" } },
695
+ "progress-indeterminate": { "0%": { left: "-40%", transform: "scaleX(0.6)" }, "50%": { transform: "scaleX(1)" }, "100%": { left: "100%", transform: "scaleX(0.6)" } },
696
+ "progress-shimmer": { "0%": { transform: "translateX(-100%) scaleX(0)", opacity: "0" }, "10%": { opacity: "1" }, "50%": { transform: "translateX(0%) scaleX(1)" }, "90%": { opacity: "1" }, "100%": { transform: "translateX(100%) scaleX(0)", opacity: "0" } },
697
+ "progress-glow-pulse": { "0%, 100%": { filter: "brightness(1) saturate(1)" }, "50%": { filter: "brightness(1.15) saturate(1.2)" } },
698
+ "collapsible-down": { from: { height: "0", opacity: "0" }, to: { height: "var(--radix-collapsible-content-height)", opacity: "1" } },
699
+ "collapsible-up": { from: { height: "var(--radix-collapsible-content-height)", opacity: "1" }, to: { height: "0", opacity: "0" } },
700
+ },
701
+ animation: {
702
+ "progress-stripes": "progress-stripes 1s linear infinite",
703
+ "progress-indeterminate": "progress-indeterminate 1.5s ease-in-out infinite",
704
+ "progress-shimmer": "progress-shimmer 2s ease-in-out infinite",
705
+ "progress-glow-pulse": "progress-glow-pulse 2s ease-in-out infinite",
706
+ "collapsible-down": "collapsible-down 0.2s ease-out",
707
+ "collapsible-up": "collapsible-up 0.2s ease-out",
708
+ },
709
+ },
710
+ }
711
+ `);
712
+ }
713
+ };
714
+
715
+ // ----------------------------------------------
716
+ // Inject
717
+ // ----------------------------------------------
718
+ const inject = (f, tailwindInfo) => {
719
+ const ex = fE(f);
720
+ const cur = ex ? rd(f) : "";
721
+
722
+ if (cur.includes(M)) {
723
+ console.log(`✅ saha-ui: CSS already injected in ${path.relative(R, f)}`);
724
+ return;
725
+ }
726
+
727
+ const hasTailwindV4Import = TW.test(cur);
728
+ const hasTailwindV3Import = cur.includes("@tailwind");
729
+ const hasTailwindImport = hasTailwindV4Import || hasTailwindV3Import;
730
+
731
+ const CSS = tailwindInfo.major >= 4 ? CSS_V4 : CSS_V3;
732
+
733
+ let cssToInject = CSS;
734
+ if (hasTailwindImport) {
735
+ if (hasTailwindV4Import) {
736
+ cssToInject = CSS.replace(/^@import\s+["']tailwindcss["'];?\n*/, "").trim();
737
+ } else if (hasTailwindV3Import) {
738
+ cssToInject = CSS.replace(/@tailwind\s+(base|components|utilities);?\n*/g, "").trim();
739
+ }
740
+ }
176
741
 
742
+ let out;
743
+ if (hasTailwindV4Import) {
744
+ out = cur.replace(TW, (m) => `${m}\n${M}\n${cssToInject}`);
745
+ } else if (hasTailwindV3Import) {
746
+ const tailwindDirectives = cur.match(/@tailwind\s+(base|components|utilities);?\n*/g);
747
+ if (tailwindDirectives) {
748
+ const lastDirective = tailwindDirectives[ tailwindDirectives.length - 1 ];
749
+ const lastIndex = cur.lastIndexOf(lastDirective);
750
+ const beforeDirective = cur.substring(0, lastIndex + lastDirective.length);
751
+ const afterDirective = cur.substring(lastIndex + lastDirective.length);
752
+ out = `${beforeDirective}\n${M}\n${cssToInject}\n${afterDirective}`;
753
+ } else {
754
+ out = `${M}\n${cssToInject}\n\n${cur}`;
755
+ }
756
+ } else {
757
+ out = `${M}\n${CSS}\n\n${cur}`;
758
+ }
759
+
760
+ wr(f, out);
761
+ console.log(`\n✅ saha-ui: Injected CSS into ${path.relative(R, f)} (${F})`);
762
+ console.log(`📦 Using Tailwind v${tailwindInfo.major} configuration`);
763
+
764
+ if (tailwindInfo.major < 4) {
765
+ console.log("\n⚠️ Tailwind v3 detected - you may need to update your tailwind.config");
766
+ updateTailwindConfig();
767
+ }
768
+
769
+ if (!ex) {
770
+ if (F === "next") {
771
+ console.log("\n📝 Next steps:");
772
+ console.log(" - Ensure root layout imports this CSS:");
773
+ console.log(" app/layout.tsx -> import './globals.css'");
774
+ } else {
775
+ console.log("\n📝 Next steps:");
776
+ console.log(" - Ensure src/main.tsx imports './index.css'");
777
+ }
778
+ }
779
+ };
780
+
781
+ // ----------------------------------------------
782
+ // Main
783
+ // ----------------------------------------------
177
784
  const run = () => {
178
785
  if (c !== "init") {
179
786
  console.log("Usage: npx saha-ui init");
@@ -182,13 +789,27 @@ const run = () => {
182
789
 
183
790
  console.log("\n🚀 Initializing saha-ui...\n");
184
791
 
185
- const tailwindInfo = checkTailwind();
186
- inject(pick(), tailwindInfo);
792
+ // 1) Determine Tailwind version from package.json only
793
+ const tailwindInfo = assertTailwindFromPkgJson();
187
794
 
188
- // 🆕 Install saha-ui’s dependencies last
795
+ // 2) For v3, ensure config exists. For v4, no mandatory CLI/PostCSS check.
796
+ if (tailwindInfo.major < 4) {
797
+ assertTailwindV3ConfigPresent();
798
+ } else {
799
+ console.log("ℹ️ Tailwind v4 detected. PostCSS/CLI are optional; proceeding to inject CSS.");
800
+ }
801
+
802
+ // 3) Proceed only if React is present
803
+ assertReactPresent();
804
+
805
+ // 4) Ensure saha-ui is installed, then install its deps to the main project
806
+ ensureSahaUIInstalled();
189
807
  installSahaUIDeps();
190
808
 
191
- console.log("\n✨ Done! saha-ui setup completed successfully.\n");
809
+ // 5) Inject CSS (v4: just CSS; v3: CSS + config hints)
810
+ inject(pick(), tailwindInfo);
811
+
812
+ console.log("\n✨ Done!\n");
192
813
  };
193
814
 
194
- run();
815
+ run();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saha-ui",
3
- "version": "1.11.0",
3
+ "version": "1.11.1",
4
4
  "description": "Ultra-modern React component library built with TypeScript, Tailwind CSS v4, and OKLCH colors",
5
5
  "type": "module",
6
6
  "license": "MIT",