From 7c7af7deeb31820ca8fbe961def9d2b646da1a58 Mon Sep 17 00:00:00 2001 From: parsajiravand Date: Tue, 21 Apr 2026 00:14:53 +0330 Subject: [PATCH 1/4] Convert icon pack to solaer --- README.md | 2 +- app/components/ui/AppIcon.vue | 64 ++++++------- app/utils/app-icon-resolve.ts | 91 ++++++++++++++++++ app/utils/nav.ts | 2 +- nuxt.config.ts | 25 +++-- package-lock.json | 171 +++++++++++++++++++++++++++++++--- package.json | 9 +- 7 files changed, 302 insertions(+), 62 deletions(-) create mode 100644 app/utils/app-icon-resolve.ts diff --git a/README.md b/README.md index 5ce4f00..065956b 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ A modern, feature-rich admin dashboard built with **Nuxt.js 4**, **[god-kit](htt - **State Management:** Pinia - **Validation:** Zod - **HTTP Client:** Axios -- **Icons:** Material Design Icons +- **Icons:** [Solar](https://icon-sets.iconify.design/solar/) (via [`@nuxt/icon`](https://github.com/nuxt/icon) + `@iconify-json/solar`), with [Simple Icons](https://simpleicons.org/) for brand marks — see `app/utils/app-icon-resolve.ts` for legacy name mapping - **Persistence:** Cookies + LocalStorage ## 📦 Installation diff --git a/app/components/ui/AppIcon.vue b/app/components/ui/AppIcon.vue index 38fc57a..1c9d619 100644 --- a/app/components/ui/AppIcon.vue +++ b/app/components/ui/AppIcon.vue @@ -1,50 +1,50 @@ diff --git a/app/utils/app-icon-resolve.ts b/app/utils/app-icon-resolve.ts new file mode 100644 index 0000000..9736dea --- /dev/null +++ b/app/utils/app-icon-resolve.ts @@ -0,0 +1,91 @@ +/** + * Maps legacy MDI-style `AppIcon` names (with or without `mdi-`) to Iconify ids. + * Default pack: **Solar** (bold). Brands use **simple-icons**. + */ +const LEGACY_TO_ICONIFY: Record = { + account: 'solar:user-circle-bold', + 'account-group': 'solar:users-group-rounded-bold', + 'account-multiple': 'solar:users-group-rounded-bold', + 'account-plus': 'solar:user-plus-bold', + 'alert-circle': 'solar:danger-circle-bold', + alert: 'solar:danger-triangle-bold', + 'align-right': 'solar:align-right-bold', + 'arrow-collapse-horizontal': 'solar:round-transfer-horizontal-bold', + 'arrow-left': 'solar:arrow-left-bold', + 'autofit-width': 'solar:round-transfer-horizontal-bold', + bell: 'solar:bell-bing-bold', + 'bell-alert': 'solar:bell-bing-bold', + 'book-open-page-variant': 'solar:book-2-bold', + 'book-open-variant': 'solar:book-bold', + bug: 'solar:bug-minimalistic-bold', + 'cart-plus': 'solar:cart-plus-bold', + cart: 'solar:cart-large-2-bold', + 'chart-areaspline': 'solar:graph-new-bold', + 'chart-line': 'solar:chart-2-bold', + 'check-circle': 'solar:check-circle-bold', + 'chevron-down': 'solar:alt-arrow-down-bold', + 'chevron-left': 'solar:alt-arrow-left-bold', + 'chevron-right': 'solar:alt-arrow-right-bold', + 'chevron-up': 'solar:alt-arrow-up-bold', + 'circle-small': 'solar:record-bold', + circle: 'solar:record-circle-bold', + 'clock-outline': 'solar:clock-circle-bold', + close: 'solar:close-circle-bold', + 'code-braces': 'solar:code-bold', + cog: 'solar:settings-bold', + contrast: 'solar:filters-bold', + 'contrast-circle': 'solar:filters-bold', + 'credit-card-off': 'solar:card-bold', + discord: 'simple-icons:discord', + 'document-text': 'solar:document-text-bold', + 'exit-run': 'solar:exit-bold', + eye: 'solar:eye-bold', + 'eye-off': 'solar:eye-closed-bold', + 'file-question': 'solar:document-text-bold', + 'format-align-right': 'solar:align-right-bold', + 'format-font': 'solar:text-bold-bold', + 'fullscreen-exit': 'solar:minimize-square-3-bold', + fullscreen: 'solar:maximize-square-3-bold', + github: 'simple-icons:github', + 'help-circle': 'solar:question-circle-bold', + home: 'solar:home-2-bold', + information: 'solar:info-circle-bold', + 'information-outline': 'solar:info-circle-bold', + linkedin: 'simple-icons:linkedin', + loading: 'solar:refresh-bold', + logout: 'solar:logout-3-bold', + magnify: 'solar:magnifer-bold', + menu: 'solar:hamburger-menu-bold', + monitor: 'solar:monitor-bold', + moon: 'solar:moon-bold', + 'numeric-5-circle': 'solar:star-circle-bold', + 'numeric-6-circle': 'solar:bookmark-circle-bold', + 'package-variant': 'solar:box-minimalistic-bold', + palette: 'solar:palette-bold', + 'palette-outline': 'solar:palette-bold', + 'pallete-2': 'solar:pallete-2-bold', + refresh: 'solar:refresh-bold', + restart: 'solar:restart-bold', + 'trending-down': 'solar:graph-down-bold', + 'trending-up': 'solar:graph-up-bold', + twitter: 'simple-icons:x', + 'view-dashboard-outline': 'solar:widget-4-bold', + 'view-dashboard': 'solar:widget-5-bold', + 'weather-night': 'solar:moon-bold', + 'weather-sunny': 'solar:sun-2-bold', + cellphone: 'solar:smartphone-bold', + tablet: 'solar:tablet-bold', + 'folder-multiple': 'solar:folder-2-bold', +} + +export function resolveAppIconifyIcon(raw: string): string { + const trimmed = raw.trim() + if (trimmed.includes(':')) return trimmed + + const key = trimmed.replace(/^mdi-/i, '').toLowerCase() + return LEGACY_TO_ICONIFY[key] ?? 'solar:record-circle-bold' +} + +export function appIconNormalizedKey(raw: string): string { + return raw.trim().replace(/^mdi-/i, '').toLowerCase() +} diff --git a/app/utils/nav.ts b/app/utils/nav.ts index c2b1d7a..fcbeced 100644 --- a/app/utils/nav.ts +++ b/app/utils/nav.ts @@ -1,4 +1,4 @@ -/** Strip MDI prefix for AppIcon / webfont names */ +/** Strip `mdi-` prefix so `AppIcon` resolves the same key as short names */ export function navIconName(icon: string): string { return icon.replace(/^mdi-/, '') } diff --git a/nuxt.config.ts b/nuxt.config.ts index b2f8eb5..436e523 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -12,9 +12,17 @@ export default defineNuxtConfig({ '@nuxtjs/tailwindcss', '@pinia/nuxt', '@nuxtjs/color-mode', - "@nuxtjs/i18n", + '@nuxtjs/i18n', + '@nuxt/icon', ], + /** Solar + brands: keep JSON external so server bundle stays lean */ + icon: { + serverBundle: { + externalizeIconsJson: true, + }, + }, + // Auto-imports for better DX imports: { autoImport: true @@ -51,8 +59,7 @@ export default defineNuxtConfig({ // CSS — god-kit tokens + component styles before app Tailwind layers - // MDI webfont: large payload (~400KB+ woff2). Future path: tree-shake with @mdi/js + inline SVG - // or a small sprite for icons used in AppIcon; measure LCP before/after. + // Icons: `@nuxt/icon` + Iconify (`@iconify-json/solar`, `simple-icons`) via `AppIcon`. css: [ 'god-kit/tokens.css', 'god-kit/vue.css', @@ -60,7 +67,6 @@ export default defineNuxtConfig({ // After main/Tailwind so `.dn-*` nav rules are reliably bundled (avoid @import in main.css) '~/assets/css/dashboard-nav.css', '~/assets/css/panel-shell.css', - '@mdi/font/css/materialdesignicons.min.css', ], @@ -86,12 +92,11 @@ export default defineNuxtConfig({ output: { manualChunks: { vendor: ['vue', 'vue-router'], - ui: ['@mdi/js'], - utils: ['axios', 'clsx'] - } - } - } - } + utils: ['axios', 'clsx'], + }, + }, + }, + }, }, // Experimental features diff --git a/package-lock.json b/package-lock.json index fbac4dc..3a189d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,9 @@ "hasInstallScript": true, "license": "GNU AFFERO GENERAL PUBLIC LICENSE", "dependencies": { - "@mdi/font": "^7.4.47", - "@mdi/js": "^7.4.47", + "@iconify-json/simple-icons": "^1.2.79", + "@iconify-json/solar": "^1.2.5", + "@nuxt/icon": "^2.2.1", "@nuxtjs/color-mode": "^3.5.2", "@nuxtjs/i18n": "^10.1.1", "@nuxtjs/tailwindcss": "^6.14.0", @@ -45,6 +46,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -1220,6 +1234,65 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.79", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.79.tgz", + "integrity": "sha512-aNyO7Fd1qej9oQfIyohYFRv0lhQLaZ+6UkK1c1qwax0MDPUOZOdq65MlU500kow97pD/W+b2u1And3e25eE24Q==", + "license": "CC0-1.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify-json/solar": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@iconify-json/solar/-/solar-1.2.5.tgz", + "integrity": "sha512-WMAiNwchU8zhfrySww6KQBRIBbsQ6SvgIu2yA+CHGyMima/0KQwT5MXogrZPJGoQF+1Ye3Qj6K+1CiyNn3YkoA==", + "license": "CC-BY-4.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/collections": { + "version": "1.0.674", + "resolved": "https://registry.npmjs.org/@iconify/collections/-/collections-1.0.674.tgz", + "integrity": "sha512-YLEpD2DufdlvG6keWnfyBL+FrIOxm2+2aypAVns9vZCBMvWSU4dn/gQEz9CV7k5QPM1uE9OQqfHAaBMR+kf1kg==", + "license": "MIT", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz", + "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "mlly": "^1.8.0" + } + }, + "node_modules/@iconify/vue": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@iconify/vue/-/vue-5.0.0.tgz", + "integrity": "sha512-C+KuEWIF5nSBrobFJhT//JS87OZ++QDORB6f2q2Wm6fl2mueSTpFBeBsveK0KW9hWiZ4mNiPjsh6Zs4jjdROSg==", + "license": "MIT", + "dependencies": { + "@iconify/types": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/cyberalien" + }, + "peerDependencies": { + "vue": ">=3" + } + }, "node_modules/@intlify/bundle-utils": { "version": "11.0.7", "resolved": "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-11.0.7.tgz", @@ -1709,18 +1782,6 @@ "node": ">=18" } }, - "node_modules/@mdi/font": { - "version": "7.4.47", - "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.4.47.tgz", - "integrity": "sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw==", - "license": "Apache-2.0" - }, - "node_modules/@mdi/js": { - "version": "7.4.47", - "resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz", - "integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==", - "license": "Apache-2.0" - }, "node_modules/@miyaneee/rollup-plugin-json5": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@miyaneee/rollup-plugin-json5/-/rollup-plugin-json5-1.2.0.tgz", @@ -1966,6 +2027,82 @@ "pathe": "^2.0.3" } }, + "node_modules/@nuxt/icon": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@nuxt/icon/-/icon-2.2.1.tgz", + "integrity": "sha512-GI840yYGuvHI0BGDQ63d6rAxGzG96jQcWrnaWIQKlyQo/7sx9PjXkSHckXUXyX1MCr9zY6U25Td6OatfY6Hklw==", + "license": "MIT", + "dependencies": { + "@iconify/collections": "^1.0.641", + "@iconify/types": "^2.0.0", + "@iconify/utils": "^3.1.0", + "@iconify/vue": "^5.0.0", + "@nuxt/devtools-kit": "^3.1.1", + "@nuxt/kit": "^4.2.2", + "consola": "^3.4.2", + "local-pkg": "^1.1.2", + "mlly": "^1.8.0", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinyglobby": "^0.2.15" + } + }, + "node_modules/@nuxt/icon/node_modules/@nuxt/kit": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.4.2.tgz", + "integrity": "sha512-5+IPRNX2CjkBhuWUwz0hBuLqiaJPRoKzQ+SvcdrQDbAyE+VDeFt74VpSFr5/R0ujrK4b+XnSHUJWdS72w6hsog==", + "license": "MIT", + "dependencies": { + "c12": "^3.3.3", + "consola": "^3.4.2", + "defu": "^6.1.4", + "destr": "^2.0.5", + "errx": "^0.1.0", + "exsolve": "^1.0.8", + "ignore": "^7.0.5", + "jiti": "^2.6.1", + "klona": "^2.0.6", + "mlly": "^1.8.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "pkg-types": "^2.3.0", + "rc9": "^3.0.0", + "scule": "^1.3.0", + "semver": "^7.7.4", + "tinyglobby": "^0.2.15", + "ufo": "^1.6.3", + "unctx": "^2.5.0", + "untyped": "^2.0.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@nuxt/icon/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/@nuxt/icon/node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/@nuxt/icon/node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "license": "MIT" + }, "node_modules/@nuxt/kit": { "version": "3.21.2", "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.21.2.tgz", @@ -12429,6 +12566,12 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", diff --git a/package.json b/package.json index 6eca11d..10f12a6 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,9 @@ "typecheck": "nuxt typecheck" }, "dependencies": { - "@mdi/font": "^7.4.47", - "@mdi/js": "^7.4.47", + "@iconify-json/simple-icons": "^1.2.79", + "@iconify-json/solar": "^1.2.5", + "@nuxt/icon": "^2.2.1", "@nuxtjs/color-mode": "^3.5.2", "@nuxtjs/i18n": "^10.1.1", "@nuxtjs/tailwindcss": "^6.14.0", @@ -31,10 +32,10 @@ "nuxt": "^4.1.3", "pinia": "^3.0.3", "tailwind-merge": "^3.3.1", - "zod": "^3.24.0", "vue": "^3.5.22", "vue-router": "^4.5.1", - "vue-tsc": "^3.1.1" + "vue-tsc": "^3.1.1", + "zod": "^3.24.0" }, "devDependencies": { "sass": "^1.93.3", From cb1869e0ce58f8a6eb8da3737d8245a55ef09bea Mon Sep 17 00:00:00 2001 From: parsajiravand Date: Tue, 21 Apr 2026 00:17:41 +0330 Subject: [PATCH 2/4] Setup Linear pack or solid option for icons --- .../settings/drawer/SettingsDrawer.vue | 22 +++++++++++++++- app/components/theme/ThemeSettings.vue | 25 +++++++++++++++++++ app/components/ui/AppIcon.vue | 7 +++++- app/stores/settings.ts | 3 ++- app/types/validation.ts | 4 ++- app/utils/app-icon-resolve.ts | 22 +++++++++++++--- i18n/locales/en.json | 8 ++++++ i18n/locales/fa.json | 8 ++++++ package.json | 2 +- 9 files changed, 92 insertions(+), 9 deletions(-) diff --git a/app/components/settings/drawer/SettingsDrawer.vue b/app/components/settings/drawer/SettingsDrawer.vue index 19ca782..86de59f 100644 --- a/app/components/settings/drawer/SettingsDrawer.vue +++ b/app/components/settings/drawer/SettingsDrawer.vue @@ -84,6 +84,24 @@ :selected="settingsStore.settings.compactLayout" @click="toggleCompactLayout" /> + + + + (), { @@ -156,7 +175,8 @@ const props = withDefaults(defineProps(), { hideContrast: false, hideNavLayout: false, hideDirection: false, - hideColorScheme: false + hideColorScheme: false, + hideIconStyle: false, }) const settingsStore = useSettingsStore() diff --git a/app/components/theme/ThemeSettings.vue b/app/components/theme/ThemeSettings.vue index 7d91870..b4cecc4 100644 --- a/app/components/theme/ThemeSettings.vue +++ b/app/components/theme/ThemeSettings.vue @@ -83,6 +83,31 @@ /> +
+ +
+ + + {{ t('settingsDrawer.iconStyleLinearShort') }} + + + + {{ t('settingsDrawer.iconStyleSolidShort') }} + +
+

+ {{ t('settingsDrawer.iconStyleHelp') }} +

+
+