Replace emoji icons with Lucide icons

This commit is contained in:
2026-05-25 16:30:12 +03:30
parent 25639ea921
commit 750c306b32
12 changed files with 161 additions and 32 deletions
+10
View File
@@ -8,6 +8,7 @@
"name": "montime-adm",
"version": "0.0.1",
"dependencies": {
"@lucide/vue": "^1.16.0",
"@vueuse/core": "^12.8.2",
"pinia": "^2.3.1",
"vue": "^3.5.13",
@@ -562,6 +563,15 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@lucide/vue": {
"version": "1.16.0",
"resolved": "https://mirror-npm.runflare.com/@lucide/vue/-/vue-1.16.0.tgz",
"integrity": "sha512-kiHx0fe49DMjQuvNaEOu9Jou6u9FwdqQ8xbZIKlD47sXrOI8g59kIeeQL42NBLI4AvO+UJsbaPX0hEK/4rHSBQ==",
"license": "ISC",
"peerDependencies": {
"vue": ">=3.0.1"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://mirror-npm.runflare.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+1
View File
@@ -10,6 +10,7 @@
"typecheck": "vue-tsc --noEmit -p tsconfig.app.json"
},
"dependencies": {
"@lucide/vue": "^1.16.0",
"@vueuse/core": "^12.8.2",
"pinia": "^2.3.1",
"vue": "^3.5.13",
+2 -1
View File
@@ -47,7 +47,7 @@
<label
class="flex cursor-pointer flex-col items-center justify-center rounded-xl border border-dashed border-border bg-surface-3 px-4 py-6 text-sm text-muted transition hover:border-brand-500/50 hover:bg-brand-500/5"
>
<span class="text-2xl">📷</span>
<AppIcon name="camera" size="lg" class="text-muted" />
<span class="mt-2">افزودن تصویر</span>
<input
type="file"
@@ -63,6 +63,7 @@
<script setup lang="ts">
import { onBeforeUnmount, ref, watch } from 'vue';
import type { BusinessImage } from '@/api/types';
import AppIcon from '@/components/ui/AppIcon.vue';
import { uploadUrl } from '@/utils/media';
const props = defineProps<{
+3 -2
View File
@@ -36,9 +36,9 @@
/>
<div
v-else
class="flex h-full w-full items-center justify-center text-xl text-muted/50"
class="flex h-full w-full items-center justify-center text-muted/40"
>
🏪
<AppIcon name="store" size="lg" />
</div>
</div>
</article>
@@ -48,6 +48,7 @@
import { computed } from 'vue';
import type { Business } from '@/api/types';
import AppBadge from '@/components/ui/AppBadge.vue';
import AppIcon from '@/components/ui/AppIcon.vue';
import { activeLabel } from '@/utils/format';
import { businessCoverUrl } from '@/utils/media';
+5 -3
View File
@@ -13,10 +13,10 @@
<p class="mt-2 text-3xl font-bold tracking-tight text-text">{{ value }}</p>
</div>
<span
class="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl text-lg"
class="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl"
:class="iconWrapClass"
>
{{ icon }}
<AppIcon :name="icon" size="lg" />
</span>
</div>
</RouterLink>
@@ -24,11 +24,13 @@
<script setup lang="ts">
import { computed } from 'vue';
import AppIcon from '@/components/ui/AppIcon.vue';
import type { AppIconName } from '@/types/icons';
const props = defineProps<{
label: string;
value: number | string;
icon: string;
icon: AppIconName;
to: string;
tone?: 'amber' | 'emerald' | 'sky' | 'violet' | 'rose';
}>();
+72
View File
@@ -0,0 +1,72 @@
<template>
<component
:is="iconComponent"
:class="sizeClass"
:stroke-width="strokeWidth"
aria-hidden="true"
/>
</template>
<script setup lang="ts">
import type { Component } from 'vue';
import { computed } from 'vue';
import {
ArrowRight,
Calendar,
Camera,
CircleCheck,
ClipboardList,
FolderOpen,
Home,
LayoutDashboard,
MapPin,
Moon,
Plus,
Search,
Store,
Sun,
User,
Users,
X,
} from '@lucide/vue';
import type { AppIconName } from '@/types/icons';
const iconMap: Record<AppIconName, Component> = {
home: Home,
calendar: Calendar,
user: User,
store: Store,
plus: Plus,
'layout-dashboard': LayoutDashboard,
folder: FolderOpen,
'clipboard-list': ClipboardList,
users: Users,
'map-pin': MapPin,
sun: Sun,
moon: Moon,
'arrow-right': ArrowRight,
x: X,
camera: Camera,
'circle-check': CircleCheck,
search: Search,
};
const props = withDefaults(
defineProps<{
name: AppIconName;
size?: 'sm' | 'md' | 'lg';
strokeWidth?: number;
}>(),
{
size: 'md',
strokeWidth: 2,
},
);
const sizeClass = computed(() => {
const map = { sm: 'h-4 w-4', md: 'h-5 w-5', lg: 'h-6 w-6' } as const;
return map[props.size];
});
const iconComponent = computed(() => iconMap[props.name]);
</script>
+3 -1
View File
@@ -20,7 +20,7 @@
aria-label="بستن"
@click="emit('close')"
>
✕
<AppIcon name="x" size="md" />
</button>
</div>
<slot />
@@ -30,6 +30,8 @@
</template>
<script setup lang="ts">
import AppIcon from '@/components/ui/AppIcon.vue';
defineProps<{
open: boolean;
title?: string;
+22 -10
View File
@@ -1,17 +1,29 @@
<template>
<AppInput
:id="id"
v-model="model"
type="search"
:label="label"
:placeholder="placeholder"
autocomplete="off"
enterkeyhint="search"
/>
<div class="space-y-1.5">
<label v-if="label" :for="id" class="block text-sm font-medium text-muted">
{{ label }}
</label>
<div class="relative">
<AppIcon
name="search"
size="sm"
class="pointer-events-none absolute start-3 top-1/2 -translate-y-1/2 text-muted"
/>
<input
:id="id"
v-model="model"
type="search"
:placeholder="placeholder"
autocomplete="off"
enterkeyhint="search"
class="w-full rounded-xl border border-border bg-surface-2 py-3 pe-4 ps-10 text-sm text-text outline-none placeholder:text-muted focus:border-brand-500 focus:ring-2 focus:ring-brand-500/30"
/>
</div>
</div>
</template>
<script setup lang="ts">
import AppInput from '@/components/ui/AppInput.vue';
import AppIcon from '@/components/ui/AppIcon.vue';
const model = defineModel<string>({ default: '' });
+3 -2
View File
@@ -5,13 +5,14 @@
:aria-label="isDark ? 'حالت روشن' : 'حالت تاریک'"
@click="toggle"
>
<span v-if="isDark" aria-hidden="true">☀️</span>
<span v-else aria-hidden="true">🌙</span>
<AppIcon v-if="isDark" name="sun" />
<AppIcon v-else name="moon" />
</button>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import AppIcon from '@/components/ui/AppIcon.vue';
import { useTheme } from '@/composables/useTheme';
const { isDark: mode, toggle } = useTheme();
+10 -8
View File
@@ -45,7 +45,7 @@
: 'text-muted hover:bg-surface-3'
"
>
<span class="text-base sm:text-lg">{{ item.icon }}</span>
<AppIcon :name="item.icon" size="lg" />
<span class="w-full truncate text-center">{{ item.label }}</span>
</RouterLink>
</div>
@@ -56,20 +56,22 @@
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router';
import AppShell from '@/components/shell/AppShell.vue';
import AppIcon from '@/components/ui/AppIcon.vue';
import ThemeToggle from '@/components/ui/ThemeToggle.vue';
import type { AdminNavItem } from '@/types/icons';
import { useAuthStore } from '@/stores/auth';
const route = useRoute();
const router = useRouter();
const auth = useAuthStore();
const items = [
{ to: '/', name: 'dashboard', label: 'داشبورد', icon: '📊' },
{ to: '/categories', name: 'categories', label: 'دسته', icon: '📁' },
{ to: '/businesses', name: 'businesses', label: 'کسب‌وکار', icon: '🏪' },
{ to: '/business-requests', name: 'business-requests', label: 'درخواست', icon: '📋' },
{ to: '/bookings', name: 'bookings', label: 'رزرو', icon: '📅' },
{ to: '/users', name: 'users', label: 'کاربران', icon: '👥' },
const items: AdminNavItem[] = [
{ to: '/', name: 'dashboard', label: 'داشبورد', icon: 'layout-dashboard' },
{ to: '/categories', name: 'categories', label: 'دسته', icon: 'folder' },
{ to: '/businesses', name: 'businesses', label: 'کسب‌وکار', icon: 'store' },
{ to: '/business-requests', name: 'business-requests', label: 'درخواست', icon: 'clipboard-list' },
{ to: '/bookings', name: 'bookings', label: 'رزرو', icon: 'calendar' },
{ to: '/users', name: 'users', label: 'کاربران', icon: 'users' },
];
function onLogout() {
+25
View File
@@ -0,0 +1,25 @@
export type AppIconName =
| 'home'
| 'calendar'
| 'user'
| 'store'
| 'plus'
| 'layout-dashboard'
| 'folder'
| 'clipboard-list'
| 'users'
| 'map-pin'
| 'sun'
| 'moon'
| 'arrow-right'
| 'x'
| 'camera'
| 'circle-check'
| 'search';
export type AdminNavItem = {
to: string;
name: string;
label: string;
icon: AppIconName;
};
+5 -5
View File
@@ -88,11 +88,11 @@ const counts = ref({
});
const statCards = computed(() => [
{ label: 'کسب‌وکار', value: counts.value.businesses, icon: '🏪', to: '/businesses', tone: 'amber' as const },
{ label: 'فعال', value: counts.value.activeBusinesses, icon: '✓', to: '/businesses', tone: 'emerald' as const },
{ label: 'رزرو', value: counts.value.bookings, icon: '📅', to: '/bookings', tone: 'sky' as const },
{ label: 'کاربر', value: counts.value.users, icon: '👥', to: '/users', tone: 'violet' as const },
{ label: 'دسته', value: counts.value.categories, icon: '📁', to: '/categories', tone: 'rose' as const },
{ label: 'کسب‌وکار', value: counts.value.businesses, icon: 'store' as const, to: '/businesses', tone: 'amber' as const },
{ label: 'فعال', value: counts.value.activeBusinesses, icon: 'circle-check' as const, to: '/businesses', tone: 'emerald' as const },
{ label: 'رزرو', value: counts.value.bookings, icon: 'calendar' as const, to: '/bookings', tone: 'sky' as const },
{ label: 'کاربر', value: counts.value.users, icon: 'users' as const, to: '/users', tone: 'violet' as const },
{ label: 'دسته', value: counts.value.categories, icon: 'folder' as const, to: '/categories', tone: 'rose' as const },
]);
const recentBusinesses = computed(() => businesses.value.slice(0, 6));