v-uixy 1.1.3 → 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.
package/commands/add.js CHANGED
@@ -10,12 +10,20 @@ const templatesRoot = path.resolve(
10
10
  "../templates"
11
11
  );
12
12
 
13
- const componentsDir = path.join(projectRoot, "components/ui");
14
- const composablesDir = path.join(projectRoot, "composables");
15
- const componentIndexPath = path.join(componentsDir, "index.ts");
16
- const composablesIndexPath = path.join(composablesDir, "index.ts");
17
13
  const registryPath = path.join(templatesRoot, "components.json");
18
14
 
15
+ async function getProjectPaths() {
16
+ const componentsDir = path.join(projectRoot, "app/components/ui");
17
+ const composablesDir = path.join(projectRoot, "app/composables");
18
+
19
+ return {
20
+ componentsDir,
21
+ composablesDir,
22
+ componentIndexPath: path.join(componentsDir, "index.ts"),
23
+ composablesIndexPath: path.join(composablesDir, "index.ts"),
24
+ };
25
+ }
26
+
19
27
  async function loadRegistry() {
20
28
  const exists = await fs.pathExists(registryPath);
21
29
  if (!exists) {
@@ -52,19 +60,21 @@ async function resolveDeps(name, registry, resolved = new Set()) {
52
60
 
53
61
  async function copyComponent(name) {
54
62
  const src = path.join(templatesRoot, `components/${name}`);
63
+ const { componentsDir } = await getProjectPaths();
55
64
  const dest = path.join(componentsDir, name);
56
65
 
57
66
  if (!(await fs.pathExists(src))) {
58
67
  console.log(chalk.yellow(`⚠️ No template found for component "${name}"`));
59
68
  return;
60
69
  }
61
-
70
+ await fs.ensureDir(componentsDir);
62
71
  await fs.copy(src, dest, { overwrite: true });
63
- console.log(chalk.green(`✔ Copied ${name} to components/ui/${name}`));
72
+ console.log(chalk.green(`✔ Copied ${name} to app/components/ui/${name}`));
64
73
  }
65
74
 
66
75
  async function copyComposable(name) {
67
76
  const src = path.join(templatesRoot, `composables/${name}.ts`);
77
+ const { composablesDir } = await getProjectPaths();
68
78
  const dest = path.join(composablesDir, `${name}.ts`);
69
79
 
70
80
  if (!(await fs.pathExists(src))) {
@@ -80,6 +90,7 @@ async function copyComposable(name) {
80
90
  }
81
91
 
82
92
  async function getExistingIndexComponents() {
93
+ const { componentIndexPath } = await getProjectPaths();
83
94
  if (!(await fs.pathExists(componentIndexPath))) return [];
84
95
 
85
96
  const content = await fs.readFile(componentIndexPath, "utf8");
@@ -92,6 +103,8 @@ async function getExistingIndexComponents() {
92
103
  }
93
104
 
94
105
  async function updateIndex(newComponents) {
106
+ const { componentIndexPath, componentsDir } = await getProjectPaths();
107
+ await fs.ensureDir(componentsDir);
95
108
  await fs.ensureFile(componentIndexPath);
96
109
 
97
110
  const existing = await getExistingIndexComponents();
@@ -107,18 +120,20 @@ async function updateIndex(newComponents) {
107
120
 
108
121
  const lines = merged.map((name) => `export * from "./${name}";`);
109
122
  await fs.writeFile(componentIndexPath, lines.join("\n") + "\n");
110
- console.log(chalk.green(`🔗 Updated components/ui/index.ts`));
123
+ console.log(chalk.green(`🔗 Updated app/components/ui/index.ts`));
111
124
  }
112
125
 
113
126
  async function updateComposablesIndex(name) {
127
+ const { composablesIndexPath, composablesDir } = await getProjectPaths();
114
128
  const line = `export * from "./${name}";`;
129
+ await fs.ensureDir(composablesDir);
115
130
  await fs.ensureFile(composablesIndexPath);
116
131
  const content = await fs.readFile(composablesIndexPath, "utf8");
117
132
  const lines = content.split("\n").map((line) => line.trim());
118
133
 
119
134
  if (!lines.includes(line)) {
120
135
  await fs.appendFile(composablesIndexPath, line + "\n");
121
- console.log(`🔗 Linked composable "${name}" in composables/index.ts`);
136
+ console.log(`🔗 Linked composable "${name}" in app/composables/index.ts`);
122
137
  } else {
123
138
  console.log(`ℹ️ Composable "${name}" already linked`);
124
139
  }
@@ -162,10 +177,48 @@ async function installPackages(componentNames, registry) {
162
177
  }
163
178
  }
164
179
 
180
+ async function ensureTypeScriptInstalled() {
181
+ const packageJsonPath = path.join(projectRoot, "package.json");
182
+ if (!(await fs.pathExists(packageJsonPath))) return;
183
+
184
+ const packageJson = await fs.readJson(packageJsonPath);
185
+ const hasTS = Boolean(
186
+ packageJson.dependencies?.typescript ||
187
+ packageJson.devDependencies?.typescript
188
+ );
189
+ if (hasTS) return;
190
+
191
+ console.log(chalk.blue("📦 Installing dev dependency: typescript"));
192
+ try {
193
+ await execa("npm", ["install", "-D", "typescript"], { stdio: "inherit" });
194
+ console.log(chalk.green("✔ Installed typescript"));
195
+ } catch (err) {
196
+ console.error(chalk.red("✖ Failed to install typescript"));
197
+ }
198
+ }
199
+
200
+ async function ensureTsconfig() {
201
+ const tsconfigDest = path.join(projectRoot, "tsconfig.json");
202
+ if (await fs.pathExists(tsconfigDest)) return;
203
+
204
+ const tsconfigSrc = path.join(templatesRoot, "tsconfig.json");
205
+ if (!(await fs.pathExists(tsconfigSrc))) return;
206
+
207
+ try {
208
+ await fs.copy(tsconfigSrc, tsconfigDest);
209
+ console.log(chalk.green("✔ Added tsconfig.json"));
210
+ } catch (err) {
211
+ console.error(chalk.red("✖ Failed to add tsconfig.json"));
212
+ }
213
+ }
214
+
165
215
  export default async function add(componentName) {
166
216
  const registry = await loadRegistry();
167
217
  let componentsToAdd = new Set();
168
218
 
219
+ await ensureTypeScriptInstalled();
220
+ await ensureTsconfig();
221
+
169
222
  if (componentName.toLowerCase() === "all") {
170
223
  componentsToAdd = new Set(registry.map((c) => c.name));
171
224
  } else {
package/commands/init.js CHANGED
@@ -13,19 +13,22 @@ const root = process.cwd();
13
13
  const templateRoot = path.resolve(__dirname, "../templates");
14
14
 
15
15
  const filesToCopy = [
16
- "components.json",
17
- "scripts/generateIconTypes.script.js",
18
- "assets/css/main.css",
19
- "tailwind.config.ts",
20
- "nuxt.config.ts",
21
- "tsconfig.json",
22
- "types/styles.d.ts",
23
- "types/icons.ts",
24
- "utils/styles.ts",
25
- "utils/twMerge.ts",
16
+ { src: "components.json", dest: "components.json" },
17
+ {
18
+ src: "scripts/generateIconTypes.script.js",
19
+ dest: "scripts/generateIconTypes.script.js",
20
+ },
21
+ { src: "assets/css/main.css", dest: "app/assets/css/main.css" },
22
+ { src: "tailwind.config.ts", dest: "tailwind.config.ts" },
23
+ { src: "nuxt.config.ts", dest: "nuxt.config.ts" },
24
+ { src: "tsconfig.json", dest: "tsconfig.json" },
25
+ { src: "types/styles.d.ts", dest: "types/styles.d.ts" },
26
+ { src: "types/icons.ts", dest: "app/types/icons.ts" },
27
+ { src: "utils/styles.ts", dest: "app/utils/styles.ts" },
28
+ { src: "utils/twMerge.ts", dest: "app/utils/twMerge.ts" },
26
29
  ];
27
30
 
28
- const dirsToCopy = ["assets/icons"];
31
+ const dirsToCopy = [{ src: "assets/icons", dest: "app/assets/icons" }];
29
32
 
30
33
  async function installPackages() {
31
34
  const packages = [
@@ -35,6 +38,7 @@ async function installPackages() {
35
38
  "vite-svg-loader",
36
39
  "tailwind-merge",
37
40
  "@tailwindcss/vite",
41
+ "typescript",
38
42
  ];
39
43
 
40
44
  console.log(chalk.blue("\n📦 Installing dependencies..."));
@@ -50,9 +54,9 @@ async function installPackages() {
50
54
  export default async function init(options = {}) {
51
55
  console.log(chalk.cyan("🧩 Initializing v-uixy..."));
52
56
 
53
- for (const relPath of filesToCopy) {
54
- const src = path.join(templateRoot, relPath);
55
- const dest = path.join(root, relPath);
57
+ for (const { src: srcRel, dest: destRel } of filesToCopy) {
58
+ const src = path.join(templateRoot, srcRel);
59
+ const dest = path.join(root, destRel);
56
60
 
57
61
  const exists = await fs.pathExists(dest);
58
62
  if (exists) {
@@ -60,7 +64,7 @@ export default async function init(options = {}) {
60
64
  {
61
65
  type: "confirm",
62
66
  name: "overwrite",
63
- message: `${chalk.yellow(relPath)} already exists. Overwrite?`,
67
+ message: `${chalk.yellow(destRel)} already exists. Overwrite?`,
64
68
  default: false,
65
69
  },
66
70
  ]);
@@ -73,12 +77,12 @@ export default async function init(options = {}) {
73
77
 
74
78
  await fs.ensureDir(path.dirname(dest));
75
79
  await fs.copy(src, dest);
76
- console.log(chalk.green(`✔ Created ${relPath}`));
80
+ console.log(chalk.green(`✔ Created ${destRel}`));
77
81
  }
78
82
 
79
- for (const relPath of dirsToCopy) {
80
- const src = path.join(templateRoot, relPath);
81
- const dest = path.join(root, relPath);
83
+ for (const { src: srcRel, dest: destRel } of dirsToCopy) {
84
+ const src = path.join(templateRoot, srcRel);
85
+ const dest = path.join(root, destRel);
82
86
 
83
87
  const exists = await fs.pathExists(dest);
84
88
  if (exists) {
@@ -87,7 +91,7 @@ export default async function init(options = {}) {
87
91
  type: "confirm",
88
92
  name: "overwrite",
89
93
  message: `${chalk.yellow(
90
- relPath
94
+ destRel
91
95
  )} already exists. Overwrite entire directory?`,
92
96
  default: false,
93
97
  },
@@ -102,7 +106,7 @@ export default async function init(options = {}) {
102
106
  }
103
107
 
104
108
  await fs.copy(src, dest);
105
- console.log(chalk.green(`✔ Copied directory ${relPath}`));
109
+ console.log(chalk.green(`✔ Copied directory ${destRel}`));
106
110
  }
107
111
 
108
112
  if (!options.withoutPackages) {
package/commands/list.js CHANGED
@@ -4,11 +4,11 @@ import chalk from "chalk";
4
4
  import { fileURLToPath } from "url";
5
5
 
6
6
  const projectRoot = process.cwd();
7
- const componentsDir = path.join(projectRoot, "components/ui");
8
7
  const templatesRoot = path.resolve(
9
8
  path.dirname(fileURLToPath(import.meta.url)),
10
9
  "../templates"
11
10
  );
11
+ const componentsDir = path.join(projectRoot, "app/components/ui");
12
12
 
13
13
  async function loadRegistry() {
14
14
  const registryPath = path.join(templatesRoot, "components.json");
@@ -28,8 +28,7 @@ async function listComponents() {
28
28
  const results = [];
29
29
 
30
30
  for (const { name } of registry) {
31
- const componentPath = path.join(componentsDir, name);
32
- const isInstalled = await fs.pathExists(componentPath);
31
+ const isInstalled = await fs.pathExists(path.join(componentsDir, name));
33
32
 
34
33
  results.push({
35
34
  name,
@@ -9,7 +9,7 @@ const templatesRoot = path.resolve(
9
9
  path.dirname(fileURLToPath(import.meta.url)),
10
10
  "../templates"
11
11
  );
12
- const componentsDir = path.join(projectRoot, "components/ui");
12
+ const componentsDir = path.join(projectRoot, "app/components/ui");
13
13
  const componentIndexPath = path.join(componentsDir, "index.ts");
14
14
 
15
15
  async function loadRegistry() {
@@ -34,8 +34,15 @@ async function removeIndexExport(name) {
34
34
  .filter((l) => l.trim() !== line)
35
35
  .join("\n");
36
36
 
37
- await fs.writeFile(componentIndexPath, newContent);
38
- console.log(`🧹 Removed export from index.ts for "${name}"`);
37
+ if (newContent !== content) {
38
+ await fs.writeFile(componentIndexPath, newContent);
39
+ console.log(
40
+ `🧹 Removed export from ${path.relative(
41
+ projectRoot,
42
+ componentIndexPath
43
+ )} for "${name}"`
44
+ );
45
+ }
39
46
  }
40
47
 
41
48
  async function removeComponent(name) {
@@ -60,11 +67,11 @@ async function removeComponent(name) {
60
67
 
61
68
  await fs.remove(pathToRemove);
62
69
  await removeIndexExport(name);
63
- console.log(chalk.green(`🗑️ Removed component "${name}"`));
70
+ console.log(
71
+ chalk.green(`🗑️ Removed component "${name}" from app/components/ui`)
72
+ );
64
73
  }
65
74
 
66
75
  export default async function remove(componentName) {
67
- const registry = await loadRegistry();
68
-
69
76
  await removeComponent(componentName);
70
77
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "v-uixy",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "private": false,
5
5
  "author": {
6
6
  "name": "Alan Haber",
@@ -1,28 +1,30 @@
1
1
  <template>
2
- <teleport to="body">
3
- <animate-presence>
4
- <motion.div
5
- v-if="open"
6
- :initial="{ opacity: 0 }"
7
- :animate="{ opacity: 1 }"
8
- :exit="{ opacity: 0 }"
9
- :transition="{ duration: 0.15, ease: 'easeInOut' }"
10
- :class="modalStyles($attrs.class as string)"
11
- data-modal
12
- @click.self="handleClick"
13
- @keydown{esc}="handleClick"
14
- >
2
+ <client-only>
3
+ <teleport to="body">
4
+ <animate-presence>
15
5
  <motion.div
16
- :initial="{ scale: 0.8 }"
17
- :animate="{ scale: 1 }"
18
- :exit="{ scale: 0.8 }"
6
+ v-if="open"
7
+ :initial="{ opacity: 0 }"
8
+ :animate="{ opacity: 1 }"
9
+ :exit="{ opacity: 0 }"
19
10
  :transition="{ duration: 0.15, ease: 'easeInOut' }"
11
+ :class="modalStyles($attrs.class as string)"
12
+ data-modal
13
+ @click.self="handleClick"
14
+ @keydown{esc}="handleClick"
20
15
  >
21
- <slot />
16
+ <motion.div
17
+ :initial="{ scale: 0.8 }"
18
+ :animate="{ scale: 1 }"
19
+ :exit="{ scale: 0.8 }"
20
+ :transition="{ duration: 0.15, ease: 'easeInOut' }"
21
+ >
22
+ <slot />
23
+ </motion.div>
22
24
  </motion.div>
23
- </motion.div>
24
- </animate-presence>
25
- </teleport>
25
+ </animate-presence>
26
+ </teleport>
27
+ </client-only>
26
28
  </template>
27
29
 
28
30
  <script setup lang="ts">
@@ -43,10 +45,14 @@
43
45
  };
44
46
 
45
47
  const toggleSiblingsInert = (enable?: boolean) => {
46
- Array.from(document.body.children).forEach((el) => {
47
- if (el === document.querySelector("[data-modal]")) return;
48
+ if (typeof document === "undefined") return;
49
+
50
+ const modalEl = document.querySelector(
51
+ "[data-modal]"
52
+ ) as HTMLElement | null;
48
53
 
49
- if (el.hasAttribute("data-modal")) return;
54
+ Array.from(document.body.children).forEach((el) => {
55
+ if (modalEl && (el === modalEl || el.contains(modalEl))) return;
50
56
 
51
57
  if (enable) {
52
58
  el.setAttribute("inert", "");
@@ -1,31 +1,33 @@
1
1
  <template>
2
- <teleport to="body">
3
- <animate-presence>
4
- <template v-if="open">
5
- <motion.div
6
- :initial="{ opacity: 0 }"
7
- :animate="{ opacity: 1 }"
8
- :exit="{ opacity: 0 }"
9
- :transition="{ ease: 'easeInOut', duration: 0.15 }"
10
- @click.self="handleClick"
11
- class="fixed left-0 top-0 z-20 size-full bg-white/5 backdrop-blur-[2px] dark:bg-gray-900/5"
12
- />
13
- <motion.div
14
- initial="initial"
15
- animate="animate"
16
- exit="exit"
17
- :variants="ANIMATIONS[props.direction ?? 'right']"
18
- :transition="{ ease: 'easeInOut', duration: 0.3 }"
19
- :class="
2
+ <client-only>
3
+ <teleport to="body">
4
+ <animate-presence>
5
+ <template v-if="open">
6
+ <motion.div
7
+ :initial="{ opacity: 0 }"
8
+ :animate="{ opacity: 1 }"
9
+ :exit="{ opacity: 0 }"
10
+ :transition="{ ease: 'easeInOut', duration: 0.15 }"
11
+ @click.self="handleClick"
12
+ class="fixed left-0 top-0 z-20 size-full bg-white/5 backdrop-blur-[2px] dark:bg-gray-900/5"
13
+ />
14
+ <motion.div
15
+ initial="initial"
16
+ animate="animate"
17
+ exit="exit"
18
+ :variants="ANIMATIONS[props.direction ?? 'right']"
19
+ :transition="{ ease: 'easeInOut', duration: 0.3 }"
20
+ :class="
20
21
  sheetStyles({ direction: props.direction ?? 'right' }, $attrs.class as string)
21
22
  "
22
- v-bind="filteredAttrs"
23
- >
24
- <slot />
25
- </motion.div>
26
- </template>
27
- </animate-presence>
28
- </teleport>
23
+ v-bind="filteredAttrs"
24
+ >
25
+ <slot />
26
+ </motion.div>
27
+ </template>
28
+ </animate-presence>
29
+ </teleport>
30
+ </client-only>
29
31
  </template>
30
32
 
31
33
  <script setup lang="ts">
@@ -1,16 +1,28 @@
1
1
  import svgLoader from "vite-svg-loader";
2
2
  import tailwindcss from "@tailwindcss/vite";
3
3
  import { fileURLToPath } from "url";
4
- import { dirname, join } from "path";
4
+ import { dirname, resolve } from "path";
5
5
 
6
- const currentDir = dirname(fileURLToPath(import.meta.url));
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
7
8
 
8
9
  // https://nuxt.com/docs/api/configuration/nuxt-config
9
10
  export default defineNuxtConfig({
10
- compatibilityDate: "2025-04-24",
11
+ modules: ["motion-v/nuxt"],
12
+ compatibilityDate: "2025-10-15",
11
13
  devtools: { enabled: true },
12
- css: [join(currentDir, "./assets/css/main.css")],
14
+ css: ["~/assets/css/main.css"],
13
15
  vite: {
14
16
  plugins: [svgLoader(), tailwindcss()],
17
+ resolve: {
18
+ alias: {
19
+ "~/utils": resolve(__dirname, "app/utils"),
20
+ "@/utils": resolve(__dirname, "app/utils"),
21
+ "~/assets": resolve(__dirname, "app/assets"),
22
+ "@/assets": resolve(__dirname, "app/assets"),
23
+ "~/types": resolve(__dirname, "app/types"),
24
+ "@/types": resolve(__dirname, "app/types"),
25
+ },
26
+ },
15
27
  },
16
28
  });
@@ -5,8 +5,8 @@ import { fileURLToPath } from "url";
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
 
8
- const svgDir = path.join(__dirname, "../assets/icons");
9
- const outputFile = path.join(__dirname, "../types/icons.ts");
8
+ const svgDir = path.join(__dirname, "../app/assets/icons");
9
+ const outputFile = path.join(__dirname, "../app/types/icons.ts");
10
10
 
11
11
  function formatKey(name) {
12
12
  return name
@@ -1,12 +1,5 @@
1
1
  /** @type {import('tailwindcss').Config} */
2
2
  export default {
3
- content: [
4
- "./components/**/*.{js,vue,ts}",
5
- "./layouts/**/*.vue",
6
- "./pages/**/*.vue",
7
- "./plugins/**/*.{js,ts}",
8
- "./app.vue",
9
- "./error.vue",
10
- ],
3
+ content: ["./app/**/*.{js,ts,vue}", "./error.vue"],
11
4
  darkMode: "class",
12
5
  };