diff --git a/.eslintrc.json b/.eslintrc.json index 65f045b8..e8e7c98a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -87,6 +87,7 @@ ] }, "settings": { + "import/core-modules": ["#app"], "vue-i18n": { "localeDir": "./src/locales/*.{json,json5,yaml,yml}" } diff --git a/components/DropdownMenu.vue b/components/DropdownMenu.vue index 4ae79fb1..593b4116 100644 --- a/components/DropdownMenu.vue +++ b/components/DropdownMenu.vue @@ -1,9 +1,10 @@ @@ -23,12 +21,7 @@ withDefaults( active-class="bg-tint" class="group flex gap-2 rounded-xl px-4 py-2" > - - + - - + diff --git a/modules/icons/generator/index.ts b/modules/icons/generator/index.ts new file mode 100644 index 00000000..69cf343a --- /dev/null +++ b/modules/icons/generator/index.ts @@ -0,0 +1,60 @@ +/* eslint-disable import/prefer-default-export */ +/* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Nuxt } from "@nuxt/schema"; +import chalk from "chalk"; +import { saveGeneratedFile } from "./output"; +import { constructIconNames } from "./parser"; + +type CreateTypedIconsArgs = { + nuxt: Nuxt; + location: string | null; + fileExtension: string; + isHookCall?: boolean; +}; + +export async function CreateTypedIcons({ + nuxt, + location, + fileExtension, + isHookCall = false, +}: CreateTypedIconsArgs): Promise { + try { + if (!isHookCall) { + if (location) { + nuxt.hook("builder:watch", (_, watchedPath) => { + if (watchedPath.startsWith(location)) { + CreateTypedIcons({ + nuxt, + location, + fileExtension, + isHookCall: true, + }); + } + }); + } + + nuxt.hook("modules:done", () => { + CreateTypedIcons({ nuxt, location, fileExtension, isHookCall: true }); + }); + + return; + } + + if (location) { + const iconNames = await constructIconNames( + nuxt.options.rootDir, + location, + fileExtension + ); + await saveGeneratedFile(nuxt, iconNames); + } else { + await saveGeneratedFile(nuxt, null); + } + } catch (e) { + console.error( + chalk.red("Error while generating icons definition model"), + `\n${e}` + ); + } +} diff --git a/modules/icons/generator/output.ts b/modules/icons/generator/output.ts new file mode 100644 index 00000000..ad2d26f2 --- /dev/null +++ b/modules/icons/generator/output.ts @@ -0,0 +1,27 @@ +/* eslint-disable import/prefer-default-export */ +/* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */ +// eslint-disable-next-line import/no-extraneous-dependencies +import { Nuxt } from "@nuxt/schema"; +import { addTemplate } from "@nuxt/kit"; + +export const saveGeneratedFile = (_: Nuxt, iconNames: string[] | null) => { + addTemplate({ + filename: "nuxt-icons.d.ts", + getContents: () => `// @ts-nocheck +// eslint-disable +/** + * --------------------------------------------------- + * 🚗 Generated by nuxt-icons. Do not modify ! + * --------------------------------------------------- + * */ + +declare module '#app' { + type NuxtIconName = ${ + iconNames === null + ? "string" + : iconNames.map((p) => `"${p.replace(/[\\"]/g, "\\$&")}"`).join(" | ") + } +} +`, + }); +}; diff --git a/modules/icons/generator/parser.ts b/modules/icons/generator/parser.ts new file mode 100644 index 00000000..a1f02715 --- /dev/null +++ b/modules/icons/generator/parser.ts @@ -0,0 +1,12 @@ +/* eslint-disable import/prefer-default-export */ +/* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */ +import { glob } from "glob"; + +export const constructIconNames = async ( + path: string, + location: string, + fileExtension: string +) => + (await glob(`${location}/**/**.${fileExtension}`, { cwd: path })) + .map((p) => p.substring(location.length + 1)) + .map((p) => p.substring(0, p.length - 4)); diff --git a/modules/icons/module.ts b/modules/icons/module.ts new file mode 100644 index 00000000..c9c6f9fa --- /dev/null +++ b/modules/icons/module.ts @@ -0,0 +1,52 @@ +// This component is a copy of the nuxt-icon component from nuxt-icons (https://nuxt.com/modules/icons) +/* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */ +import { defineNuxtModule, createResolver, addComponent } from "@nuxt/kit"; +import { CreateTypedIcons } from "./generator"; + +export interface ModuleOptions { + // dir: string + /** + * Enables path autocomplete and path validity for programmatic validation + * + * @default true + */ + pathCheck?: boolean; +} + +export default defineNuxtModule({ + meta: { + name: "nuxt-icons", + configKey: "nuxtIcons", + compatibility: { + nuxt: "^3.0.0", + }, + }, + defaults: { + pathCheck: true, + }, + setup(options, nuxt) { + const { resolve } = createResolver(import.meta.url); + addComponent({ + name: "nuxt-icon", + global: true, + filePath: resolve("./runtime/components/nuxt-icon.vue"), + }); + + // Force register of type declaration + nuxt.hook("prepare:types", ({ references }) => { + references.push({ + path: resolve(nuxt.options.buildDir, "nuxt-icons.d.ts"), + }); + }); + + if (options.pathCheck) { + CreateTypedIcons({ + nuxt, + location: "assets/icons", + fileExtension: "svg", + }); + } else { + CreateTypedIcons({ nuxt, location: null, fileExtension: "" }); + } + }, +}); diff --git a/components/IconComponent.vue b/modules/icons/runtime/components/nuxt-icon.vue similarity index 87% rename from components/IconComponent.vue rename to modules/icons/runtime/components/nuxt-icon.vue index db7a7639..48e8d19e 100644 --- a/components/IconComponent.vue +++ b/modules/icons/runtime/components/nuxt-icon.vue @@ -1,11 +1,10 @@