ui-thing 0.1.28 → 0.1.30

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-thing",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "description": "CLI used to add Nuxt 3 components to a project",
5
5
  "keywords": [
6
6
  "cli",
@@ -44,28 +44,28 @@
44
44
  "c12": "^2.0.1",
45
45
  "commander": "^12.1.0",
46
46
  "defu": "^6.1.4",
47
- "execa": "^9.4.1",
47
+ "execa": "^9.5.1",
48
48
  "figlet": "^1.8.0",
49
49
  "fs-extra": "^11.2.0",
50
50
  "kleur": "^4.1.5",
51
51
  "lodash": "^4.17.21",
52
52
  "nypm": "^0.3.12",
53
- "ora": "^8.1.0",
53
+ "ora": "^8.1.1",
54
54
  "prompts": "^2.4.2"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@gmrchk/cli-testing-library": "^0.1.2",
58
- "@ianvs/prettier-plugin-sort-imports": "^4.3.1",
58
+ "@ianvs/prettier-plugin-sort-imports": "^4.4.0",
59
59
  "@types/figlet": "^1.7.0",
60
60
  "@types/fs-extra": "^11.0.4",
61
- "@types/lodash": "^4.17.11",
62
- "@types/node": "^22.7.6",
61
+ "@types/lodash": "^4.17.13",
62
+ "@types/node": "^22.9.0",
63
63
  "@types/prompts": "^2.4.9",
64
- "@vitest/coverage-v8": "^2.1.3",
64
+ "@vitest/coverage-v8": "^2.1.5",
65
65
  "magicast": "^0.3.5",
66
- "tsup": "^8.3.0",
66
+ "tsup": "^8.3.5",
67
67
  "typescript": "^5.6.3",
68
- "vitest": "^2.1.3"
68
+ "vitest": "^2.1.5"
69
69
  },
70
70
  "publishConfig": {
71
71
  "access": "public"
@@ -1,8 +1,6 @@
1
- import { readFileSync } from "fs";
2
1
  import path from "node:path";
3
2
  import { Command } from "commander";
4
3
  import { consola } from "consola";
5
- import { defu } from "defu";
6
4
  import kleur from "kleur";
7
5
  import _ from "lodash";
8
6
  import prompts from "prompts";
@@ -144,11 +142,8 @@ export const add = new Command()
144
142
  (i: any) => i.from === "vue-sonner" && i.name === "toast"
145
143
  );
146
144
  if (!sonnerExists) {
147
- cfg.defaultExport.imports.imports.push({
148
- from: "vue-sonner",
149
- name: "toast",
150
- as: "useSonner",
151
- });
145
+ // prettier-ignore
146
+ cfg.defaultExport.imports.imports.push({ from: "vue-sonner", name: "toast", as: "useSonner" });
152
147
  }
153
148
  const transpileExists = cfg.defaultExport.build.transpile.find((i: any) => "vue-sonner");
154
149
  if (!transpileExists) {
@@ -162,21 +157,21 @@ export const add = new Command()
162
157
  cfg.defaultExport.app.head.script ||= [];
163
158
  const scriptOneExists = cfg.defaultExport.app.head.script.find(
164
159
  (i: any) =>
165
- i.src === "https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.9/pdfmake.min.js"
160
+ i.src === "https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.12/pdfmake.min.js"
166
161
  );
167
162
  if (!scriptOneExists) {
168
163
  cfg.defaultExport.app.head.script.push({
169
- src: "https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.9/pdfmake.min.js",
164
+ src: "https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.12/pdfmake.min.js",
170
165
  defer: true,
171
166
  });
172
167
  }
173
168
  const scriptTwoExists = cfg.defaultExport.app.head.script.find(
174
169
  (i: any) =>
175
- i.src === "https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.9/vfs_fonts.min.js"
170
+ i.src === "https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.12/vfs_fonts.min.js"
176
171
  );
177
172
  if (!scriptTwoExists) {
178
173
  cfg.defaultExport.app.head.script.push({
179
- src: "https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.9/vfs_fonts.min.js",
174
+ src: "https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.12/vfs_fonts.min.js",
180
175
  defer: true,
181
176
  });
182
177
  }
@@ -16,10 +16,11 @@ export const init = new Command()
16
16
  .command("init")
17
17
  .name("init")
18
18
  .description(
19
- "Initialize UI Thing in your Nuxt3 project. This will: 1. Create a tailwind.config.js file 2. Update your nuxt.config.ts file 3. Add the necessary dependencies 4. Create a ui-thing.config.ts file with the default configuration"
19
+ "Initialize UI Thing in your Nuxt project. This will: 1. Create a tailwind.config.js file 2. Update your nuxt.config.ts file 3. Add the necessary dependencies 4. Create a ui-thing.config.ts file with the default configuration"
20
20
  )
21
21
  .option("-f --force", "Overwrite config file if it exists.", false)
22
22
  .option("-y --yes", "Skip prompts and use default values.", false)
23
+ .option("-n --nuxtVersion <number>", "Specify the Nuxt version you are using.", "3")
23
24
  .action(async (options: InitOptions) => {
24
25
  // Get nuxt config
25
26
  const cfg = await getNuxtConfig();
@@ -37,7 +38,10 @@ export const init = new Command()
37
38
  // Add init modules ot nuxt cinfig
38
39
  addModuleToConfig(cfg.nuxtConfig, INIT_MODULES);
39
40
  // Configure modules in nuxt config
40
- cfg.defaultExport.tailwindcss = defu(cfg.defaultExport.tailwindcss, { exposeConfig: true });
41
+ cfg.defaultExport.tailwindcss = defu(cfg.defaultExport.tailwindcss, {
42
+ exposeConfig: true,
43
+ editorSupport: true,
44
+ });
41
45
  cfg.defaultExport.colorMode = defu(cfg.defaultExport.colorMode, { classSuffix: "" });
42
46
  cfg.defaultExport.imports ||= {};
43
47
  cfg.defaultExport.imports.imports ||= [];
@@ -2,6 +2,7 @@ import { Command } from "commander";
2
2
  import prompts from "prompts";
3
3
 
4
4
  import { addPrettierConfig } from "../utils/addPrettierConfig";
5
+ import { PACKAGE_MANAGER_CHOICES } from "../utils/constants";
5
6
  import { installPackages } from "../utils/installPackages";
6
7
  import { printFancyBoxMessage } from "../utils/printFancyBoxMessage";
7
8
 
@@ -24,12 +25,7 @@ export const addPrettier = new Command()
24
25
  name: "pkgManager",
25
26
  type: "select",
26
27
  message: "Which package manager are you using?",
27
- choices: [
28
- { title: "npm", value: "npm" },
29
- { title: "yarn", value: "yarn" },
30
- { title: "pnpm", value: "pnpm" },
31
- { title: "bun", value: "bun" },
32
- ],
28
+ choices: PACKAGE_MANAGER_CHOICES,
33
29
  });
34
30
  if (!pkgManager) return process.exit(0);
35
31
 
@@ -3,6 +3,7 @@ import prompts from "prompts";
3
3
 
4
4
  import { addShortcutFiles } from "../utils/addShortcutFiles";
5
5
  import { addModuleToConfig, getNuxtConfig, updateConfig } from "../utils/config";
6
+ import { PACKAGE_MANAGER_CHOICES } from "../utils/constants";
6
7
  import { installPackages } from "../utils/installPackages";
7
8
  import { printFancyBoxMessage } from "../utils/printFancyBoxMessage";
8
9
 
@@ -23,12 +24,7 @@ export const addShortcuts = new Command()
23
24
  name: "pkgManager",
24
25
  type: "select",
25
26
  message: "Which package manager are you using?",
26
- choices: [
27
- { title: "npm", value: "npm" },
28
- { title: "yarn", value: "yarn" },
29
- { title: "pnpm", value: "pnpm" },
30
- { title: "bun", value: "bun" },
31
- ],
27
+ choices: PACKAGE_MANAGER_CHOICES,
32
28
  });
33
29
  if (!pkgManager) return process.exit(0);
34
30
 
package/src/comps.ts CHANGED
@@ -76,7 +76,7 @@ export default [
76
76
  fileName: "shared.styles.ts",
77
77
  dirPath: "utils",
78
78
  fileContent:
79
- '// Add here because button styles are used in several components\nexport const buttonStyles = tv({\n base: "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",\n variants: {\n variant: {\n default: "bg-primary text-primary-foreground hover:bg-primary/90",\n destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",\n outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",\n secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",\n ghost: "hover:bg-accent hover:text-accent-foreground",\n link: "text-primary underline-offset-4 hover:underline",\n },\n size: {\n default: "h-10 px-4 py-2",\n xs: "h-7 rounded px-2",\n sm: "h-9 rounded-md px-3",\n lg: "h-11 rounded-md px-8",\n "icon-sm": "h-9 w-9",\n icon: "h-10 w-10",\n },\n disabled: {\n true: "pointer-events-none opacity-50",\n },\n },\n defaultVariants: {\n variant: "default",\n size: "default",\n },\n});\n',
79
+ '// Add here because button styles are used in several components\nexport const buttonStyles = tv({\n base: "group inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",\n variants: {\n variant: {\n default: "bg-primary text-primary-foreground hover:bg-primary/90",\n destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",\n outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",\n secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",\n ghost: "hover:bg-accent hover:text-accent-foreground",\n link: "text-primary underline-offset-4 hover:underline",\n expandIcon: "group relative bg-primary text-primary-foreground hover:bg-primary/90",\n ringHover:\n "bg-primary text-primary-foreground transition-all duration-300 hover:bg-primary/90 hover:ring-2 hover:ring-primary/90 hover:ring-offset-2",\n shine:\n "animate-shine bg-gradient-to-r from-primary via-primary/75 to-primary bg-[length:400%_100%] text-primary-foreground",\n gooeyRight:\n "relative z-0 overflow-hidden bg-primary from-primary-foreground/40 text-primary-foreground transition-all duration-500 before:absolute before:inset-0 before:-z-10 before:translate-x-[150%] before:translate-y-[150%] before:scale-[2.5] before:rounded-[100%] before:bg-gradient-to-r before:transition-transform before:duration-1000 hover:before:translate-x-[0%] hover:before:translate-y-[0%]",\n gooeyLeft:\n "relative z-0 overflow-hidden bg-primary from-primary-foreground/40 text-primary-foreground transition-all duration-500 after:absolute after:inset-0 after:-z-10 after:translate-x-[-150%] after:translate-y-[150%] after:scale-[2.5] after:rounded-[100%] after:bg-gradient-to-l after:transition-transform after:duration-1000 hover:after:translate-x-[0%] hover:after:translate-y-[0%]",\n linkHover1:\n "relative after:absolute after:bottom-2 after:h-[1px] after:w-2/3 after:origin-bottom-left after:scale-x-100 after:bg-primary after:transition-transform after:duration-300 after:ease-in-out hover:after:origin-bottom-right hover:after:scale-x-0",\n linkHover2:\n "relative after:absolute after:bottom-2 after:h-[1px] after:w-2/3 after:origin-bottom-right after:scale-x-0 after:bg-primary after:transition-transform after:duration-300 after:ease-in-out hover:after:origin-bottom-left hover:after:scale-x-100",\n },\n size: {\n xs: "h-8 px-2",\n sm: "h-9 px-3",\n default: "h-10 px-4 py-2",\n lg: "h-11 px-8",\n "icon-xs": "h-8 w-8",\n "icon-sm": "h-9 w-9",\n icon: "h-10 w-10",\n },\n disabled: {\n true: "pointer-events-none opacity-50",\n },\n hasIcon: {\n false: "gap-2",\n },\n },\n defaultVariants: {\n variant: "default",\n size: "default",\n },\n});\n',
80
80
  },
81
81
  ],
82
82
  files: [
@@ -300,7 +300,7 @@ export default [
300
300
  fileName: "Badge.vue",
301
301
  dirPath: "components/UI",
302
302
  fileContent:
303
- '<template>\n <component\n :is="elementType"\n :to="to"\n :href="href"\n :disabled="disabled"\n :class="badgeVariants({ disabled, variant, class: props.class })"\n @click="onClick"\n >\n <slot />\n </component>\n</template>\n\n<script lang="ts" setup>\n const badgeVariants = tv({\n base: "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",\n variants: {\n variant: {\n default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",\n secondary:\n "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",\n destructive:\n "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",\n outline: "text-foreground",\n },\n disabled: {\n true: "cursor-not-allowed opacity-50",\n },\n },\n defaultVariants: {\n variant: "default",\n },\n });\n\n type BadgeProps = VariantProps<typeof badgeVariants>;\n\n const props = defineProps<{\n class?: any;\n variant?: BadgeProps["variant"];\n onClick?: () => void;\n to?: string;\n href?: string;\n disabled?: boolean;\n tag?: string;\n }>();\n\n const elementType = computed(() => {\n if (props.tag) return props.tag;\n if (props.href || props.to) return resolveComponent("NuxtLink");\n return props.tag || "div";\n });\n</script>\n',
303
+ '<template>\n <component\n :is="elementType"\n :class="badgeVariants({ disabled, size, variant, class: props.class })"\n v-bind="forwarded"\n @click="onClick"\n >\n <slot />\n </component>\n</template>\n\n<script lang="ts">\n import { reactiveOmit } from "@vueuse/core";\n import { useForwardProps } from "radix-vue";\n import type { NuxtLinkProps } from "#app/components";\n</script>\n<script lang="ts" setup>\n const badgeVariants = tv({\n base: "inline-flex items-center rounded-full border font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",\n variants: {\n variant: {\n default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",\n secondary:\n "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",\n destructive:\n "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",\n outline: "text-foreground",\n },\n disabled: {\n true: "cursor-not-allowed opacity-50",\n },\n size: {\n sm: "px-2.5 py-0.5 text-xs font-medium",\n md: "px-2.5 py-1 text-sm font-semibold",\n lg: "px-3 py-1.5 text-sm font-semibold",\n },\n },\n defaultVariants: {\n variant: "default",\n disabled: false,\n size: "sm",\n },\n });\n\n type BadgeProps = VariantProps<typeof badgeVariants>;\n\n const props = defineProps<\n NuxtLinkProps & {\n /** Any additional class that should be added to the badge */\n class?: any;\n /** The variant of the badge */\n variant?: BadgeProps["variant"];\n /** The size of the badge */\n size?: BadgeProps["size"];\n /** The action to perform when the badge is clicked */\n onClick?: () => void;\n /** Should the badge be disabled or not */\n disabled?: boolean;\n /** The element to render the badge as */\n tag?: string;\n }\n >();\n\n const forwarded = useForwardProps(reactiveOmit(props, "class", "variant", "onClick", "disabled"));\n\n const elementType = computed(() => {\n if (props.tag) return props.tag;\n if (props.href || props.to) return resolveComponent("NuxtLink");\n if (props.onClick) return "button";\n return props.tag || "div";\n });\n</script>\n',
304
304
  },
305
305
  ],
306
306
  utils: [],
@@ -330,7 +330,7 @@ export default [
330
330
  fileName: "shared.styles.ts",
331
331
  dirPath: "utils",
332
332
  fileContent:
333
- '// Add here because button styles are used in several components\nexport const buttonStyles = tv({\n base: "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",\n variants: {\n variant: {\n default: "bg-primary text-primary-foreground hover:bg-primary/90",\n destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",\n outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",\n secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",\n ghost: "hover:bg-accent hover:text-accent-foreground",\n link: "text-primary underline-offset-4 hover:underline",\n },\n size: {\n default: "h-10 px-4 py-2",\n xs: "h-7 rounded px-2",\n sm: "h-9 rounded-md px-3",\n lg: "h-11 rounded-md px-8",\n "icon-sm": "h-9 w-9",\n icon: "h-10 w-10",\n },\n disabled: {\n true: "pointer-events-none opacity-50",\n },\n },\n defaultVariants: {\n variant: "default",\n size: "default",\n },\n});\n',
333
+ '// Add here because button styles are used in several components\nexport const buttonStyles = tv({\n base: "group inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",\n variants: {\n variant: {\n default: "bg-primary text-primary-foreground hover:bg-primary/90",\n destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",\n outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",\n secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",\n ghost: "hover:bg-accent hover:text-accent-foreground",\n link: "text-primary underline-offset-4 hover:underline",\n expandIcon: "group relative bg-primary text-primary-foreground hover:bg-primary/90",\n ringHover:\n "bg-primary text-primary-foreground transition-all duration-300 hover:bg-primary/90 hover:ring-2 hover:ring-primary/90 hover:ring-offset-2",\n shine:\n "animate-shine bg-gradient-to-r from-primary via-primary/75 to-primary bg-[length:400%_100%] text-primary-foreground",\n gooeyRight:\n "relative z-0 overflow-hidden bg-primary from-primary-foreground/40 text-primary-foreground transition-all duration-500 before:absolute before:inset-0 before:-z-10 before:translate-x-[150%] before:translate-y-[150%] before:scale-[2.5] before:rounded-[100%] before:bg-gradient-to-r before:transition-transform before:duration-1000 hover:before:translate-x-[0%] hover:before:translate-y-[0%]",\n gooeyLeft:\n "relative z-0 overflow-hidden bg-primary from-primary-foreground/40 text-primary-foreground transition-all duration-500 after:absolute after:inset-0 after:-z-10 after:translate-x-[-150%] after:translate-y-[150%] after:scale-[2.5] after:rounded-[100%] after:bg-gradient-to-l after:transition-transform after:duration-1000 hover:after:translate-x-[0%] hover:after:translate-y-[0%]",\n linkHover1:\n "relative after:absolute after:bottom-2 after:h-[1px] after:w-2/3 after:origin-bottom-left after:scale-x-100 after:bg-primary after:transition-transform after:duration-300 after:ease-in-out hover:after:origin-bottom-right hover:after:scale-x-0",\n linkHover2:\n "relative after:absolute after:bottom-2 after:h-[1px] after:w-2/3 after:origin-bottom-right after:scale-x-0 after:bg-primary after:transition-transform after:duration-300 after:ease-in-out hover:after:origin-bottom-left hover:after:scale-x-100",\n },\n size: {\n xs: "h-8 px-2",\n sm: "h-9 px-3",\n default: "h-10 px-4 py-2",\n lg: "h-11 px-8",\n "icon-xs": "h-8 w-8",\n "icon-sm": "h-9 w-9",\n icon: "h-10 w-10",\n },\n disabled: {\n true: "pointer-events-none opacity-50",\n },\n hasIcon: {\n false: "gap-2",\n },\n },\n defaultVariants: {\n variant: "default",\n size: "default",\n },\n});\n',
334
334
  },
335
335
  ],
336
336
  files: [
@@ -338,7 +338,7 @@ export default [
338
338
  fileName: "Button.vue",
339
339
  dirPath: "components/UI",
340
340
  fileContent:
341
- '<template>\n <component\n :is="elementType"\n :class="\n buttonStyles({\n disabled: disabled || loading,\n variant: variant,\n size: size,\n class: props.class,\n })\n "\n :disabled="disabled || loading"\n :to="to"\n :href="href"\n :type="type"\n @click="onClick"\n >\n <slot>{{ text }}</slot>\n </component>\n</template>\n\n<script setup lang="ts">\n import type { RouteLocationRaw } from "vue-router";\n\n type ButtonProps = VariantProps<typeof buttonStyles>;\n const props = withDefaults(\n defineProps<{\n /** The type fro the button */\n type?: "button" | "submit" | "reset";\n /** Whether the button is disabled */\n disabled?: boolean;\n /** Whether the button is loading */\n loading?: boolean;\n /** The action to perform when the button is clicked */\n onClick?: any;\n /** The location to navigate to when the button is clicked */\n to?: string | RouteLocationRaw;\n /** The location to navigate to when the button is clicked */\n href?: string;\n /** The element to render the button as */\n as?: string;\n /** Custom class(es) to add to parent element */\n class?: any;\n /** The variant of the button */\n variant?: ButtonProps["variant"];\n /** The size of the button */\n size?: ButtonProps["size"];\n /** The text to display in the button */\n text?: string;\n }>(),\n {\n type: "button",\n onClick: undefined,\n to: undefined,\n href: undefined,\n as: undefined,\n class: undefined,\n text: undefined,\n variant: "default",\n size: "default",\n }\n );\n\n const elementType = computed(() => {\n if (props.as) return props.as;\n if (props.href || props.to) return resolveComponent("NuxtLink");\n return "button";\n });\n</script>\n',
341
+ '<template>\n <component\n :is="elementType"\n :class="\n buttonStyles({\n hasIcon: !!icon,\n disabled: disabled || loading,\n variant: variant,\n size: size,\n class: props.class,\n })\n "\n :disabled="disabled || loading"\n v-bind="forwarded"\n >\n <slot name="iconLeft">\n <div\n v-if="icon && iconPlacement == \'left\'"\n className="w-0 flex items-center shrink-0 justify-center translate-x-[0%] pr-0 opacity-0 transition-all duration-200 group-hover:w-6 group-hover:translate-x-100 group-hover:pr-2 group-hover:opacity-100"\n >\n <Icon :name="icon" class="size-5" />\n </div>\n </slot>\n <slot>\n <span v-if="text">{{ text }}</span>\n </slot>\n <slot name="iconRight">\n <div\n v-if="icon && iconPlacement == \'right\'"\n className="w-0 flex items-center justify-center shrink-0 translate-x-[100%] pl-0 opacity-0 transition-all duration-200 group-hover:w-6 group-hover:translate-x-0 group-hover:pl-2 group-hover:opacity-100"\n >\n <Icon :name="icon" class="size-5" />\n </div>\n </slot>\n </component>\n</template>\n\n<script setup lang="ts">\n import { reactiveOmit } from "@vueuse/core";\n import { useForwardProps } from "radix-vue";\n import type { NuxtLinkProps } from "#app/components";\n\n type ButtonProps = VariantProps<typeof buttonStyles>;\n const props = withDefaults(\n defineProps<\n NuxtLinkProps & {\n /** The type fro the button */\n type?: "button" | "submit" | "reset";\n /** Whether the button is disabled */\n disabled?: boolean;\n /** Whether the button is loading */\n loading?: boolean;\n /** The action to perform when the button is clicked */\n onClick?: any;\n /** The element to render the button as */\n as?: string;\n /** Custom class(es) to add to parent element */\n class?: any;\n /** The variant of the button */\n variant?: ButtonProps["variant"];\n /** The size of the button */\n size?: ButtonProps["size"];\n /** The text to display in the button */\n text?: string;\n /** Should the icon be displayed on the `left` or the `right`? */\n iconPlacement?: "left" | "right";\n /** The icon to display in the button */\n icon?: string;\n }\n >(),\n {\n type: "button",\n }\n );\n\n const elementType = computed(() => {\n if (props.as) return props.as;\n if (props.href || props.to || props.target) return resolveComponent("NuxtLink");\n return "button";\n });\n\n const forwarded = useForwardProps(\n reactiveOmit(\n props,\n "class",\n "text",\n "icon",\n "iconPlacement",\n "size",\n "variant",\n "as",\n "loading",\n "disabled"\n )\n );\n</script>\n',
342
342
  },
343
343
  ],
344
344
  composables: [],
@@ -1295,7 +1295,7 @@ export default [
1295
1295
  fileName: "Label.vue",
1296
1296
  dirPath: "components/UI",
1297
1297
  fileContent:
1298
- '<template>\n <Label :class="styles({ class: props.class })" v-bind="forwarded">\n <slot />\n <slot name="hint">\n <span v-if="hint">\n {{ hint }}\n </span>\n </slot>\n </Label>\n</template>\n\n<script lang="ts" setup>\n import { Label } from "radix-vue";\n import type { LabelProps } from "radix-vue";\n\n const props = defineProps<\n LabelProps & {\n /** Custom class(es) to add to the label */\n class?: any;\n hint?: string;\n }\n >();\n\n const forwarded = reactiveOmit(props, "class", "hint");\n\n const styles = tv({\n base: "flex items-center justify-between text-[15px] font-medium leading-none hover:cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:opacity-70 sm:text-sm",\n });\n</script>\n',
1298
+ '<template>\n <Label :class="styles({ class: props.class })" v-bind="forwarded">\n <slot />\n <slot name="hint">\n <span v-if="hint" class="text-muted-foreground">\n {{ hint }}\n </span>\n </slot>\n </Label>\n</template>\n\n<script lang="ts" setup>\n import { Label } from "radix-vue";\n import type { LabelProps } from "radix-vue";\n\n const props = defineProps<\n LabelProps & {\n /** Custom class(es) to add to the label */\n class?: any;\n hint?: string;\n }\n >();\n\n const forwarded = reactiveOmit(props, "class", "hint");\n\n const styles = tv({\n base: "flex items-center justify-between text-[15px] font-medium leading-none hover:cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:opacity-70 sm:text-sm",\n });\n</script>\n',
1299
1299
  },
1300
1300
  ],
1301
1301
  utils: [],
@@ -1539,7 +1539,7 @@ export default [
1539
1539
  fileName: "Navbar.vue",
1540
1540
  dirPath: "components/UI",
1541
1541
  fileContent:
1542
- '<template>\n <Primitive :class="styles({ sticky, class: props.class })" v-bind="forwarded">\n <slot />\n </Primitive>\n</template>\n\n<script lang="ts" setup>\n import { Primitive } from "radix-vue";\n import type { PrimitiveProps } from "radix-vue";\n\n const props = withDefaults(\n defineProps<\n PrimitiveProps & {\n /** Custom class(es) to add to the parent */\n class?: any;\n /** Whether the navbar should be sticky */\n sticky?: boolean;\n }\n >(),\n {\n as: "header",\n }\n );\n\n const forwarded = reactiveOmit(props, "class", "sticky");\n\n const styles = tv({\n base: "z-20 border-b bg-background/90 backdrop-blur",\n variants: {\n sticky: {\n true: "sticky top-0",\n },\n },\n });\n</script>\n',
1542
+ '<template>\n <Primitive :class="styles({ sticky, class: props.class })" v-bind="forwarded">\n <slot />\n </Primitive>\n</template>\n\n<script lang="ts" setup>\n import { reactiveOmit } from "@vueuse/core";\n import { Primitive, useForwardProps } from "radix-vue";\n import type { PrimitiveProps } from "radix-vue";\n\n const props = withDefaults(\n defineProps<\n PrimitiveProps & {\n /** Custom class(es) to add to the parent */\n class?: any;\n /** Whether the navbar should be sticky */\n sticky?: boolean;\n }\n >(),\n {\n as: "header",\n }\n );\n\n const forwarded = useForwardProps(reactiveOmit(props, "class", "sticky"));\n\n const styles = tv({\n base: "z-20 border-b bg-background/90 backdrop-blur",\n variants: {\n sticky: {\n true: "sticky top-0",\n },\n },\n });\n</script>\n',
1543
1543
  },
1544
1544
  ],
1545
1545
  utils: [],
@@ -2667,7 +2667,7 @@ export default [
2667
2667
  fileName: "Vee/Input.vue",
2668
2668
  dirPath: "components/UI",
2669
2669
  fileContent:
2670
- '<template>\n <div class="w-full">\n <UiLabel\n v-if="label"\n :for="inputId"\n :hint="labelHint"\n :class="[disabled && \'text-muted-foreground\', errorMessage && \'text-destructive\', \'mb-2\']"\n ><span>{{ label }} <span v-if="required" class="text-destructive">*</span></span></UiLabel\n >\n <div class="relative">\n <slot name="icon">\n <span v-if="hasIcon" class="absolute inset-y-0 left-3 flex items-center justify-center">\n <Icon v-if="icon" :name="icon" class="h-4 w-4 text-muted-foreground/70" />\n </span>\n </slot>\n <UiInput\n :id="inputId"\n v-model="value"\n :type="type"\n :required="required"\n :name="name"\n :disabled="disabled"\n v-bind="$attrs"\n :class="[hasIcon && \'pl-9\']"\n :placeholder="placeholder"\n @blur="handleBlur"\n />\n </div>\n <TransitionSlide group tag="div">\n <p v-if="hint && !errorMessage" key="hint" class="mt-1.5 text-sm text-muted-foreground">\n {{ hint }}\n </p>\n\n <p v-if="errorMessage" key="errorMessage" class="mt-1.5 text-sm text-destructive">\n {{ errorMessage }}\n </p>\n </TransitionSlide>\n </div>\n</template>\n\n<script lang="ts" setup>\n const props = defineProps<{\n label?: string;\n labelHint?: string;\n icon?: string;\n hint?: string;\n disabled?: boolean;\n modelValue?: string;\n name?: string;\n id?: string;\n rules?: any;\n validateOnMount?: boolean;\n type?: string;\n placeholder?: string;\n required?: boolean;\n }>();\n\n const inputId = props.id || useId();\n\n const hasIcon = computed(() => Boolean(props.icon) || Boolean(useSlots().icon));\n\n const { errorMessage, value, handleBlur } = useField(() => props.name || inputId, props.rules, {\n initialValue: props.modelValue,\n label: props.label,\n validateOnMount: props.validateOnMount,\n syncVModel: true,\n });\n</script>\n',
2670
+ '<template>\n <div class="w-full">\n <UiLabel\n v-if="label"\n :for="inputId"\n :hint="labelHint"\n :class="[disabled && \'text-muted-foreground\', errorMessage && \'text-destructive\', \'mb-2\']"\n ><span>{{ label }} <span v-if="required" class="text-destructive">*</span></span></UiLabel\n >\n <div class="relative">\n <slot name="icon">\n <span v-if="hasIcon" class="absolute inset-y-0 left-3 flex items-center justify-center">\n <Icon v-if="icon" :name="icon" class="h-4 w-4 text-muted-foreground/70" />\n </span>\n </slot>\n <slot name="trailingIcon">\n <span\n v-if="hasTrailingIcon"\n class="absolute inset-y-0 right-3 flex items-center justify-center"\n >\n <Icon v-if="trailingIcon" :name="trailingIcon" class="h-4 w-4 text-muted-foreground/70" />\n </span>\n </slot>\n <UiInput\n :id="inputId"\n v-model="value"\n :type="type"\n :required="required"\n :name="name"\n :disabled="disabled"\n v-bind="$attrs"\n :class="[hasIcon && \'pl-9\', hasTrailingIcon && \'pr-9\']"\n :placeholder="placeholder"\n @blur="handleBlur"\n />\n </div>\n <TransitionSlide group tag="div">\n <p v-if="hint && !errorMessage" key="hint" class="mt-1.5 text-sm text-muted-foreground">\n {{ hint }}\n </p>\n\n <p v-if="errorMessage" key="errorMessage" class="mt-1.5 text-sm text-destructive">\n {{ errorMessage }}\n </p>\n </TransitionSlide>\n </div>\n</template>\n\n<script lang="ts" setup>\n const props = defineProps<{\n label?: string;\n labelHint?: string;\n icon?: string;\n trailingIcon?: string;\n hint?: string;\n disabled?: boolean;\n modelValue?: string;\n name?: string;\n id?: string;\n rules?: any;\n validateOnMount?: boolean;\n type?: string;\n placeholder?: string;\n required?: boolean;\n }>();\n\n defineOptions({ inheritAttrs: false });\n\n const inputId = props.id || useId();\n\n const hasIcon = computed(() => Boolean(props.icon) || Boolean(useSlots().icon));\n const hasTrailingIcon = computed(\n () => Boolean(props.trailingIcon) || Boolean(useSlots().trailingIcon)\n );\n\n const { errorMessage, value, handleBlur } = useField(() => props.name || inputId, props.rules, {\n initialValue: props.modelValue,\n label: props.label,\n validateOnMount: props.validateOnMount,\n syncVModel: true,\n });\n</script>\n',
2671
2671
  },
2672
2672
  ],
2673
2673
  utils: [],
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
3
 
4
+ import { version } from "../package.json";
4
5
  import { add } from "./commands/add";
5
6
  import { init } from "./commands/init";
6
7
  import { addPrettier } from "./commands/prettier";
@@ -22,7 +23,7 @@ console.log();
22
23
  program
23
24
  .name("ui-thing")
24
25
  .description("CLI for adding ui-thing components to your Nuxt 3 application")
25
- .version("0.0.1")
26
+ .version(version)
26
27
  .addCommand(init)
27
28
  .addCommand(add)
28
29
  .addCommand(theme)
@@ -1,6 +1,4 @@
1
- export const CSS_START = `@import url("https://rsms.me/inter/inter.css");
2
-
3
- @tailwind base;
1
+ export const CSS_START = `@tailwind base;
4
2
  @tailwind components;
5
3
  @tailwind utilities;
6
4
  `;
@@ -9,14 +7,14 @@ export const CSS_END = `@layer base {
9
7
  * {
10
8
  @apply border-border;
11
9
  }
10
+ html {
11
+ @apply antialiased;
12
+ }
12
13
  body {
13
14
  @apply bg-background text-foreground;
14
- font-feature-settings:
15
- "rlig" 1,
16
- "calt" 1;
15
+ font-feature-settings: "cv02", "cv03", "cv04", "cv11";
17
16
  }
18
17
  }
19
-
20
18
  `;
21
19
 
22
20
  export const createCSS = (theme: keyof typeof themes) => {
@@ -1,11 +1,11 @@
1
- export const DEFINE_SHORTCUT = `import { logicAnd, logicNot } from "@vueuse/math";
2
- import { computed, ref } from "vue";
1
+ export const DEFINE_SHORTCUT = `/* eslint-disable @typescript-eslint/no-unsafe-function-type */
2
+ import { logicAnd, logicNot } from "@vueuse/math";
3
3
  import type { ComputedRef, WatchSource } from "vue";
4
4
 
5
5
  export interface ShortcutConfig {
6
6
  handler: Function;
7
7
  usingInput?: string | boolean;
8
- whenever?: WatchSource<Boolean>[];
8
+ whenever?: WatchSource<boolean>[];
9
9
  }
10
10
 
11
11
  export interface ShortcutsConfig {
@@ -18,7 +18,7 @@ export interface ShortcutsOptions {
18
18
 
19
19
  interface Shortcut {
20
20
  handler: Function;
21
- condition: ComputedRef<Boolean>;
21
+ condition: ComputedRef<boolean>;
22
22
  chained: boolean;
23
23
  // KeyboardEvent attributes
24
24
  key: string;
@@ -157,7 +157,7 @@ export const defineShortcuts = (config: ShortcutsConfig, options: ShortcutsOptio
157
157
  }
158
158
 
159
159
  // Create shortcut computed
160
- const conditions: ComputedRef<Boolean>[] = [];
160
+ const conditions: ComputedRef<boolean>[] = [];
161
161
  if (!(shortcutConfig as ShortcutConfig).usingInput) {
162
162
  conditions.push(logicNot(usingInput));
163
163
  } else if (typeof (shortcutConfig as ShortcutConfig).usingInput === "string") {
@@ -176,12 +176,16 @@ export const defineShortcuts = (config: ShortcutsConfig, options: ShortcutsOptio
176
176
 
177
177
  useEventListener("keydown", onKeyDown);
178
178
  };
179
+
179
180
  `;
180
181
 
181
182
  export const USE_SHORTCUTS = `export const _useShortcuts = () => {
182
183
  const macOS = computed(
183
184
  () =>
184
- process.client && navigator && navigator.userAgent && navigator.userAgent.match(/Macintosh;/)
185
+ import.meta.client &&
186
+ navigator &&
187
+ navigator.userAgent &&
188
+ navigator.userAgent.match(/Macintosh;/)
185
189
  );
186
190
 
187
191
  const metaSymbol = ref(" ");
@@ -214,4 +218,5 @@ export const USE_SHORTCUTS = `export const _useShortcuts = () => {
214
218
  };
215
219
 
216
220
  export const useShortcuts = createSharedComposable(_useShortcuts);
221
+
217
222
  `;
@@ -17,7 +17,13 @@ module.exports = {
17
17
  },
18
18
  },
19
19
  fontFamily: {
20
- sans: ["Inter var", "Inter", ...fontFamily.sans],
20
+ sans: [
21
+ \`Inter, \${fontFamily.sans.join(", ")}\`,
22
+ {
23
+ fontFeatureSettings: '"cv02","cv03","cv04","cv11"',
24
+ },
25
+ ],
26
+ mono: ["'Fira Code'", ...fontFamily.mono],
21
27
  },
22
28
  borderRadius: {
23
29
  lg: "var(--radius)",
@@ -25,6 +31,27 @@ module.exports = {
25
31
  sm: "calc(var(--radius) - 4px)",
26
32
  },
27
33
  keyframes: {
34
+ shine: {
35
+ from: { backgroundPosition: "200% 0" },
36
+ to: { backgroundPosition: "-200% 0" },
37
+ },
38
+ meteor: {
39
+ "0%": { transform: "rotate(215deg) translateX(0)", opacity: 1 },
40
+ "70%": { opacity: 1 },
41
+ "100%": {
42
+ transform: "rotate(215deg) translateX(-500px)",
43
+ opacity: 0,
44
+ },
45
+ },
46
+ grid: {
47
+ "0%": { transform: "translateY(-50%)" },
48
+ "100%": { transform: "translateY(0)" },
49
+ },
50
+ "border-beam": {
51
+ "100%": {
52
+ "offset-distance": "100%",
53
+ },
54
+ },
28
55
  "accordion-down": {
29
56
  from: { height: "0px" },
30
57
  to: { height: "var(--radix-accordion-content-height)" },
@@ -51,6 +78,9 @@ module.exports = {
51
78
  },
52
79
  },
53
80
  animation: {
81
+ shine: "shine 8s ease-in-out infinite",
82
+ meteor: "meteor 5s linear infinite",
83
+ grid: "grid 15s linear infinite",
54
84
  "accordion-down": "accordion-down 0.2s ease-out",
55
85
  "accordion-up": "accordion-up 0.2s ease-out",
56
86
  fadeIn: "fadeIn 0.2s ease-out",
package/src/types.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export type UIConfig = {
2
+ nuxtVersion?: number;
2
3
  theme: string;
3
4
  tailwindCSSLocation: string;
4
5
  tailwindConfigLocation: string;
@@ -11,7 +12,7 @@ export type UIConfig = {
11
12
  packageManager: string;
12
13
  };
13
14
 
14
- export type InitOptions = { force?: boolean; yes?: boolean };
15
+ export type InitOptions = { force?: boolean; yes?: boolean; nuxtVersion?: number };
15
16
 
16
17
  export type Component = {
17
18
  name: string;
@@ -1,3 +1,4 @@
1
+ import { join } from "node:path";
1
2
  import { $ } from "execa";
2
3
  import fse from "fs-extra";
3
4
  import ora from "ora";
@@ -6,7 +7,8 @@ import prompts from "prompts";
6
7
  import { PRETTIER_CONFIG } from "../templates/prettier";
7
8
 
8
9
  export const addPrettierConfig = async (cwd = process.cwd(), format: boolean = true) => {
9
- if (fse.existsSync(`${cwd}/.prettierrc`)) {
10
+ const prettierLocation = join(cwd, ".prettierrc");
11
+ if (fse.existsSync(prettierLocation)) {
10
12
  const res = await prompts({
11
13
  name: "overwrite",
12
14
  type: "confirm",
@@ -15,7 +17,7 @@ export const addPrettierConfig = async (cwd = process.cwd(), format: boolean = t
15
17
  });
16
18
  if (!res.overwrite) return false;
17
19
  }
18
- await fse.writeFile(`${cwd}/.prettierrc`, PRETTIER_CONFIG, "utf-8");
20
+ await fse.writeFile(prettierLocation, PRETTIER_CONFIG, "utf-8");
19
21
  if (!format) return true;
20
22
  const spinner = ora("Formatting files with prettier...").start();
21
23
  await $`npx prettier --write .`;
@@ -1,12 +1,18 @@
1
+ import { join } from "node:path";
1
2
  import fse from "fs-extra";
2
3
 
3
4
  import { DEFINE_SHORTCUT, USE_SHORTCUTS } from "../templates/shortcuts";
5
+ import { UIConfig } from "../types";
6
+ import { getUIConfig } from "./config";
4
7
 
5
8
  export const addShortcutFiles = async (cwd = process.cwd()) => {
9
+ // get config
10
+ let userConfig: UIConfig = await getUIConfig();
11
+ const composablesLocation = join(cwd, userConfig.composablesLocation);
6
12
  // ensure that the composable folder exists
7
- await fse.ensureDir(`${cwd}/composables`);
13
+ await fse.ensureDir(composablesLocation);
8
14
  // write the defineShortcuts composable
9
- await fse.writeFile(`${cwd}/composables/defineShortcuts.ts`, DEFINE_SHORTCUT, "utf-8");
15
+ await fse.writeFile(join(composablesLocation, "defineShortcuts.ts"), DEFINE_SHORTCUT, "utf-8");
10
16
  // write the useShortcuts composable
11
- await fse.writeFile(`${cwd}/composables/useShortcuts.ts`, USE_SHORTCUTS, "utf-8");
17
+ await fse.writeFile(join(composablesLocation, "useShortcuts.ts"), USE_SHORTCUTS, "utf-8");
12
18
  };
@@ -9,6 +9,7 @@ export const compareUIConfig = async () => {
9
9
  // Get ui config
10
10
  let userConfig: UIConfig = await getUIConfig();
11
11
  const tempConfig: UIConfig = {
12
+ nuxtVersion: 3,
12
13
  theme: "string",
13
14
  tailwindCSSLocation: "string",
14
15
  tailwindConfigLocation: "string",
@@ -8,11 +8,12 @@ import { addNuxtModule, getDefaultExportOptions } from "magicast/helpers";
8
8
  import prompts from "prompts";
9
9
 
10
10
  import { InitOptions, UIConfig } from "../types";
11
- import { initPrompts } from "./uiConfigPrompt";
11
+ import { initPrompts, promptForNuxtVersion } from "./uiConfigPrompt";
12
12
 
13
13
  const currentDir = process.cwd();
14
14
  const uiConfigFilename = "ui-thing.config.ts";
15
- const defaultConfig = {
15
+ const defaultConfig: UIConfig = {
16
+ nuxtVersion: 3,
16
17
  theme: "zinc",
17
18
  tailwindCSSLocation: "assets/css/tailwind.css",
18
19
  tailwindConfigLocation: "tailwind.config.js",
@@ -24,6 +25,19 @@ const defaultConfig = {
24
25
  useDefaultFilename: true,
25
26
  packageManager: "npm",
26
27
  };
28
+ const defaultNuxt4Config: UIConfig = {
29
+ nuxtVersion: 4,
30
+ theme: "zinc",
31
+ tailwindCSSLocation: "app/assets/css/tailwind.css",
32
+ tailwindConfigLocation: "tailwind.config.js",
33
+ componentsLocation: "app/components/Ui",
34
+ composablesLocation: "app/composables",
35
+ pluginsLocation: "app/plugins",
36
+ utilsLocation: "app/utils",
37
+ force: true,
38
+ useDefaultFilename: true,
39
+ packageManager: "npm",
40
+ };
27
41
 
28
42
  export const getNuxtConfig = async () => {
29
43
  if (!fse.existsSync("nuxt.config.ts")) {
@@ -38,11 +52,15 @@ export const getNuxtConfig = async () => {
38
52
  export const getUIConfig = async (options?: InitOptions) => {
39
53
  const configFileExists = fse.existsSync(uiConfigFilename);
40
54
  let uiConfig: UIConfig = {} as UIConfig;
55
+ let nuxtVersion = Number(options?.nuxtVersion);
41
56
 
42
57
  if (!configFileExists || options?.force) {
58
+ if (!nuxtVersion) {
59
+ nuxtVersion = await promptForNuxtVersion();
60
+ }
43
61
  // if option yes is passed, use default values
44
62
  if (options?.yes) {
45
- uiConfig = defaultConfig;
63
+ uiConfig = Number(nuxtVersion) === 4 ? defaultNuxt4Config : defaultConfig;
46
64
  } else {
47
65
  uiConfig = await initPrompts();
48
66
  }
@@ -1,4 +1,4 @@
1
- export const INIT_DEPS = ["radix-vue", "tailwind-variants"];
1
+ export const INIT_DEPS = ["radix-vue", "tailwind-variants", "@nuxt/fonts"];
2
2
  export const INIT_DEV_DEPS = [
3
3
  "typescript",
4
4
  "tailwindcss-animate",
@@ -17,6 +17,19 @@ export const INIT_MODULES = [
17
17
  "@nuxtjs/color-mode",
18
18
  "@vueuse/nuxt",
19
19
  "@nuxt/icon",
20
+ "@nuxt/fonts",
21
+ ];
22
+
23
+ export const NUXT_VERSIONS = [
24
+ { title: "Nuxt 3", value: 3 },
25
+ { title: "Nuxt 4", value: 4 },
26
+ ];
27
+
28
+ export const PACKAGE_MANAGER_CHOICES = [
29
+ { title: "NPM", value: "npm" },
30
+ { title: "YARN", value: "yarn" },
31
+ { title: "PNPM", value: "pnpm" },
32
+ { title: "BUN", value: "bun" },
20
33
  ];
21
34
 
22
35
  export const CSS_THEME_OPTIONS = [