ui-thing 0.1.27 → 0.1.29

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.27",
3
+ "version": "0.1.29",
4
4
  "description": "CLI used to add Nuxt 3 components to a project",
5
5
  "keywords": [
6
6
  "cli",
@@ -44,7 +44,7 @@
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",
@@ -58,14 +58,14 @@
58
58
  "@ianvs/prettier-plugin-sort-imports": "^4.3.1",
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.8.6",
63
63
  "@types/prompts": "^2.4.9",
64
- "@vitest/coverage-v8": "^2.1.3",
64
+ "@vitest/coverage-v8": "^2.1.4",
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.4"
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: [
@@ -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: [],
@@ -833,7 +833,7 @@ export default [
833
833
  fileName: "Dialog/Content.vue",
834
834
  dirPath: "components/UI",
835
835
  fileContent:
836
- '<template>\n <UiDialogPortal :to="to">\n <UiDialogOverlay />\n <DialogContent :class="styles({ class: props.class })" v-bind="{ ...forwarded, ...$attrs }">\n <slot>\n <slot name="header">\n <UiDialogHeader>\n <slot name="title">\n <UiDialogTitle v-if="title" :title="title" />\n </slot>\n <slot name="description">\n <UiDialogDescription v-if="description" :description="description" />\n </slot>\n </UiDialogHeader>\n </slot>\n <slot name="content" />\n <slot name="footer" />\n </slot>\n <slot name="close">\n <UiDialogClose />\n </slot>\n <UiDialogClose\n v-if="!hideClose"\n class="absolute right-4 top-2 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"\n >\n <Icon name="lucide:x" class="h-4 w-4" />\n <span class="sr-only">Close</span>\n </UiDialogClose>\n </DialogContent>\n </UiDialogPortal>\n</template>\n\n<script lang="ts" setup>\n import { DialogContent, useForwardPropsEmits } from "radix-vue";\n import type { DialogContentEmits, DialogContentProps } from "radix-vue";\n\n defineOptions({ inheritAttrs: false });\n const props = defineProps<\n DialogContentProps & {\n /** Icon to display in the close button */\n icon?: string;\n /** Title text */\n title?: string;\n /** Description text */\n description?: string;\n /** Custom class(es) to add to the parent */\n class?: any;\n /** Whether to hide the close button */\n hideClose?: boolean;\n /** Where to render the dialog */\n to?: string | HTMLElement;\n }\n >();\n const emits = defineEmits<DialogContentEmits>();\n const forwarded = useForwardPropsEmits(\n reactiveOmit(props, "icon", "title", "description", "class", "hideClose", "to"),\n emits\n );\n\n const styles = tv({\n base: "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",\n });\n</script>\n',
836
+ '<template>\n <UiDialogPortal :to="to">\n <UiDialogOverlay />\n <DialogContent :class="styles({ class: props.class })" v-bind="{ ...forwarded, ...$attrs }">\n <slot>\n <slot name="header">\n <UiDialogHeader>\n <slot name="title">\n <UiDialogTitle v-if="title" :title="title" />\n </slot>\n <slot name="description">\n <UiDialogDescription v-if="description" :description="description" />\n </slot>\n </UiDialogHeader>\n </slot>\n <slot name="content" />\n <slot name="footer" />\n </slot>\n <slot name="close">\n <UiDialogClose />\n </slot>\n <UiDialogClose\n v-if="!hideClose"\n class="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"\n >\n <Icon name="lucide:x" class="h-4 w-4" mode="svg" />\n <span class="sr-only">Close</span>\n </UiDialogClose>\n </DialogContent>\n </UiDialogPortal>\n</template>\n\n<script lang="ts" setup>\n import { DialogContent, useForwardPropsEmits } from "radix-vue";\n import type { DialogContentEmits, DialogContentProps } from "radix-vue";\n\n defineOptions({ inheritAttrs: false });\n const props = defineProps<\n DialogContentProps & {\n /** Icon to display in the close button */\n icon?: string;\n /** Title text */\n title?: string;\n /** Description text */\n description?: string;\n /** Custom class(es) to add to the parent */\n class?: any;\n /** Whether to hide the close button */\n hideClose?: boolean;\n /** Where to render the dialog */\n to?: string | HTMLElement;\n }\n >();\n const emits = defineEmits<DialogContentEmits>();\n const forwarded = useForwardPropsEmits(\n reactiveOmit(props, "icon", "title", "description", "class", "hideClose", "to"),\n emits\n );\n\n const styles = tv({\n base: "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",\n });\n</script>\n',
837
837
  },
838
838
  {
839
839
  fileName: "Dialog/Description.vue",
@@ -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: [],
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 = [
@@ -1,10 +1,32 @@
1
1
  import kleur from "kleur";
2
2
  import prompts from "prompts";
3
3
 
4
- import { CSS_THEME_OPTIONS } from "./constants";
4
+ import { CSS_THEME_OPTIONS, NUXT_VERSIONS, PACKAGE_MANAGER_CHOICES } from "./constants";
5
+
6
+ export const promptForNuxtVersion = async () => {
7
+ const response = await prompts([
8
+ {
9
+ name: "nuxtVersion",
10
+ type: "select",
11
+ message: "Which Nuxt version are you using?",
12
+ choices: NUXT_VERSIONS,
13
+ },
14
+ ]);
15
+ if (!response.nuxtVersion) {
16
+ console.log(kleur.red("No Nuxt version selected. Exiting..."));
17
+ return process.exit(0);
18
+ }
19
+ return response.nuxtVersion;
20
+ };
5
21
 
6
22
  export const initPrompts = async () => {
7
23
  const response = await prompts([
24
+ {
25
+ name: "nuxtVersion",
26
+ type: "select",
27
+ message: "Which Nuxt version are you using?",
28
+ choices: NUXT_VERSIONS,
29
+ },
8
30
  {
9
31
  name: "theme",
10
32
  type: "autocomplete",
@@ -15,7 +37,8 @@ export const initPrompts = async () => {
15
37
  name: "tailwindCSSLocation",
16
38
  type: "text",
17
39
  message: "Where is your tailwind.css file located?",
18
- initial: "assets/css/tailwind.css",
40
+ initial: (_, v) =>
41
+ v.nuxtVersion == 3 ? "assets/css/tailwind.css" : "app/assets/css/tailwind.css",
19
42
  },
20
43
  {
21
44
  name: "tailwindConfigLocation",
@@ -27,25 +50,25 @@ export const initPrompts = async () => {
27
50
  name: "componentsLocation",
28
51
  type: "text",
29
52
  message: "Where should your components be stored?",
30
- initial: "components/Ui",
53
+ initial: (_, v) => (v.nuxtVersion == 3 ? "components/Ui" : "app/components/Ui"),
31
54
  },
32
55
  {
33
56
  name: "composablesLocation",
34
57
  type: "text",
35
58
  message: "Where should your composables be stored?",
36
- initial: "composables",
59
+ initial: (_, v) => (v.nuxtVersion == 3 ? "composables" : "app/composables"),
37
60
  },
38
61
  {
39
62
  name: "pluginsLocation",
40
63
  type: "text",
41
64
  message: "Where should your plugins be stored?",
42
- initial: "plugins",
65
+ initial: (_, v) => (v.nuxtVersion == 3 ? "plugins" : "app/plugins"),
43
66
  },
44
67
  {
45
68
  name: "utilsLocation",
46
69
  type: "text",
47
70
  message: "Where should your utils be stored?",
48
- initial: "utils",
71
+ initial: (_, v) => (v.nuxtVersion == 3 ? "utils" : "app/utils"),
49
72
  },
50
73
  {
51
74
  name: "force",
@@ -63,15 +86,11 @@ export const initPrompts = async () => {
63
86
  name: "packageManager",
64
87
  type: "select",
65
88
  message: "Which package manager do you use?",
66
- choices: [
67
- { title: "NPM", value: "npm" },
68
- { title: "Yarn", value: "yarn" },
69
- { title: "PNPM", value: "pnpm" },
70
- { title: "Bun", value: "bun" },
71
- ],
89
+ choices: PACKAGE_MANAGER_CHOICES,
72
90
  },
73
91
  ]);
74
- if (!response || Object.keys(response).length < 9) {
92
+
93
+ if (!response || Object.keys(response).length < 10) {
75
94
  console.log(kleur.red("Incomplete configuration submitted. Exiting..."));
76
95
  return process.exit(0);
77
96
  }