feat: complete UI/UX improvements, vuesax icons, media server, and section management
This commit is contained in:
@@ -46,4 +46,31 @@ defineProps<{ lockBody?: boolean }>();
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.ap {
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.ap__head {
|
||||
position: sticky;
|
||||
top: 52px;
|
||||
z-index: 20;
|
||||
padding: 10px 12px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 1px 3px rgba(15, 23, 42, 0.03);
|
||||
}
|
||||
|
||||
.ap__body {
|
||||
padding: 12px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.ap__body--locked {
|
||||
overflow: visible;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { ICON_NAMES, type IconName } from "@imen/shared";
|
||||
import { computed } from "vue";
|
||||
import { ICONS } from "./icons";
|
||||
import { RECOMMENDED_ICONS } from "@imen/shared";
|
||||
import VuesaxIcon from "./VuesaxIcon.vue";
|
||||
|
||||
/** Picks one of the Lucide icons the website knows how to render */
|
||||
const value = defineModel<string>({ required: true });
|
||||
defineProps<{ allowClear?: boolean }>();
|
||||
|
||||
const options = ICON_NAMES.map((name) => ({ value: name, label: name }));
|
||||
const current = computed(() => ICONS[value.value as IconName]);
|
||||
const options = RECOMMENDED_ICONS.map((name) => ({ value: name, label: name }));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="flex size-8 shrink-0 items-center justify-center rounded-md border border-gray-200 text-[#ff5a00]">
|
||||
<component :is="current" v-if="current" class="size-4" />
|
||||
<span class="flex size-9 shrink-0 items-center justify-center rounded-lg border border-gray-200 bg-gray-50 text-[#ff5a00]">
|
||||
<VuesaxIcon v-if="value" :name="value" :size="20" />
|
||||
<span v-else class="text-xs text-gray-400">---</span>
|
||||
</span>
|
||||
<a-select
|
||||
v-model:value="value"
|
||||
@@ -25,9 +23,9 @@ const current = computed(() => ICONS[value.value as IconName]);
|
||||
placeholder="انتخاب آیکون"
|
||||
>
|
||||
<template #option="{ value: name }">
|
||||
<span class="flex items-center gap-2" dir="ltr">
|
||||
<component :is="ICONS[name as IconName]" class="size-4" />
|
||||
{{ name }}
|
||||
<span class="flex items-center gap-2.5 py-0.5" dir="ltr">
|
||||
<VuesaxIcon :name="name" :size="18" class="text-orange-600" />
|
||||
<span class="text-xs font-mono">{{ name }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</a-select>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import VuesaxIcon from "@/components/VuesaxIcon.vue";
|
||||
|
||||
defineProps<{ title: string; subtitle?: string; back?: string }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-header">
|
||||
<div class="page-header__main">
|
||||
<RouterLink v-if="back" :to="back" class="page-header__back" title="بازگشت">→</RouterLink>
|
||||
<RouterLink v-if="back" :to="back" class="page-header__back" title="بازگشت">
|
||||
<VuesaxIcon name="arrow-right" :size="18" />
|
||||
</RouterLink>
|
||||
<div class="min-w-0">
|
||||
<h1 class="page-header__title">{{ title }}</h1>
|
||||
<p v-if="subtitle" class="page-header__subtitle">{{ subtitle }}</p>
|
||||
@@ -19,42 +23,61 @@ defineProps<{ title: string; subtitle?: string; back?: string }>();
|
||||
|
||||
<style scoped>
|
||||
.page-header {
|
||||
flex: 0 0 auto !important;
|
||||
flex-shrink: 0 !important;
|
||||
flex-grow: 0 !important;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.page-header__main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.page-header__back {
|
||||
color: #94a3b8;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 8px;
|
||||
background: #f1f5f9;
|
||||
color: #475569;
|
||||
text-decoration: none;
|
||||
transition: color 0.15s ease;
|
||||
transition: all 0.15s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.page-header__back:hover {
|
||||
color: #334155;
|
||||
background: #e2e8f0;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.page-header__title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.page-header__title {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header__subtitle {
|
||||
margin: 2px 0 0;
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.page-header__actions {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { resolveVuesaxIcon } from "@imen/shared";
|
||||
import icons from "@/icons/vuesax-linear.json";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** Short name (`add`) or full key (`vuesax-linear:add`) */
|
||||
/** Short name (`add`), alias (`ShieldCheck`, `Phone`), or full key (`vuesax-linear:add`) */
|
||||
name: string;
|
||||
size?: number | string;
|
||||
}>(),
|
||||
@@ -14,10 +15,9 @@ const props = withDefaults(
|
||||
const pack = icons as Record<string, string>;
|
||||
|
||||
const svgHtml = computed(() => {
|
||||
const key = props.name.startsWith("vuesax-linear:")
|
||||
? props.name
|
||||
: `vuesax-linear:${props.name}`;
|
||||
const raw = pack[key];
|
||||
const resolved = resolveVuesaxIcon(props.name || "");
|
||||
const key = resolved.includes(":") ? resolved : `vuesax-linear:${resolved}`;
|
||||
const raw = pack[key] || pack[`vuesax-linear:${resolved.toLowerCase()}`] || pack["vuesax-linear:shield-tick"];
|
||||
if (!raw) return "";
|
||||
return raw
|
||||
.replace(/\swidth="24"/, "")
|
||||
@@ -33,7 +33,7 @@ const box = computed(() =>
|
||||
|
||||
<template>
|
||||
<span
|
||||
class="vx-icon"
|
||||
class="anticon vx-icon"
|
||||
:style="{ width: box, height: box }"
|
||||
:aria-hidden="true"
|
||||
v-html="svgHtml"
|
||||
@@ -48,6 +48,7 @@ const box = computed(() =>
|
||||
justify-content: center;
|
||||
line-height: 0;
|
||||
color: inherit;
|
||||
vertical-align: -0.15em;
|
||||
}
|
||||
|
||||
.vx-icon :deep(svg) {
|
||||
|
||||
@@ -1,104 +1,2 @@
|
||||
import type { IconName } from "@imen/shared";
|
||||
import {
|
||||
Award,
|
||||
BadgeCheck,
|
||||
Building2,
|
||||
ClipboardCheck,
|
||||
Clock,
|
||||
Construction,
|
||||
Ear,
|
||||
Factory,
|
||||
FileCheck,
|
||||
FileSearch,
|
||||
Flame,
|
||||
Footprints,
|
||||
Fuel,
|
||||
Gauge,
|
||||
Glasses,
|
||||
Globe,
|
||||
GraduationCap,
|
||||
Hand,
|
||||
Handshake,
|
||||
HardHat,
|
||||
Headset,
|
||||
HeartPulse,
|
||||
Leaf,
|
||||
Lock,
|
||||
Mail,
|
||||
MapPin,
|
||||
Medal,
|
||||
Microscope,
|
||||
Package,
|
||||
PackageCheck,
|
||||
Phone,
|
||||
Pickaxe,
|
||||
ShieldAlert,
|
||||
ShieldCheck,
|
||||
ShieldPlus,
|
||||
Shirt,
|
||||
Siren,
|
||||
Star,
|
||||
Target,
|
||||
ThumbsUp,
|
||||
TrafficCone,
|
||||
TriangleAlert,
|
||||
Truck,
|
||||
Users,
|
||||
Warehouse,
|
||||
Wind,
|
||||
Wrench,
|
||||
Zap,
|
||||
} from "lucide-vue-next";
|
||||
import type { Component } from "vue";
|
||||
|
||||
/** Same icon set as the website's DynamicIcon (ICON_NAMES in @imen/shared) */
|
||||
export const ICONS: Record<IconName, Component> = {
|
||||
ShieldCheck,
|
||||
ShieldPlus,
|
||||
ShieldAlert,
|
||||
BadgeCheck,
|
||||
Award,
|
||||
Medal,
|
||||
PackageCheck,
|
||||
Package,
|
||||
Truck,
|
||||
Warehouse,
|
||||
Globe,
|
||||
Users,
|
||||
Handshake,
|
||||
Headset,
|
||||
ClipboardCheck,
|
||||
FileCheck,
|
||||
FileSearch,
|
||||
GraduationCap,
|
||||
Gauge,
|
||||
Microscope,
|
||||
Wrench,
|
||||
HardHat,
|
||||
Shirt,
|
||||
Footprints,
|
||||
Hand,
|
||||
Glasses,
|
||||
Ear,
|
||||
Wind,
|
||||
TriangleAlert,
|
||||
Siren,
|
||||
TrafficCone,
|
||||
Construction,
|
||||
Factory,
|
||||
Building2,
|
||||
Fuel,
|
||||
Pickaxe,
|
||||
Flame,
|
||||
Zap,
|
||||
Leaf,
|
||||
HeartPulse,
|
||||
Clock,
|
||||
Phone,
|
||||
Mail,
|
||||
MapPin,
|
||||
Star,
|
||||
ThumbsUp,
|
||||
Target,
|
||||
Lock,
|
||||
};
|
||||
export { ICON_ALIASES, ICON_NAMES, RECOMMENDED_ICONS, resolveVuesaxIcon } from "@imen/shared";
|
||||
export type { IconName } from "@imen/shared";
|
||||
|
||||
Reference in New Issue
Block a user