fix(admin): polish mobile layout and actions
This commit is contained in:
@@ -11,6 +11,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { formatJalali, resolveErrorMessage } from "@/lib/utils";
|
import { formatJalali, resolveErrorMessage } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -91,37 +92,41 @@ export default function AdminBlog() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6" dir="ltr">
|
<div className="space-y-6" dir="rtl">
|
||||||
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||||
<Button asChild>
|
|
||||||
<Link to="/admin/blog/new/edit">
|
|
||||||
<Plus className="ml-2 h-4 w-4" />
|
|
||||||
نوشته جدید
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<h2 className="text-2xl font-bold">مدیریت بلاگ</h2>
|
<h2 className="text-2xl font-bold">مدیریت بلاگ</h2>
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
پیشنویسها، صف بررسی، انتشار و اصلاح نوشتهها.
|
پیشنویسها، صف بررسی، انتشار و اصلاح نوشتهها.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<Button asChild>
|
||||||
|
<Link to="/admin/blog/new/edit">
|
||||||
|
<Plus className="ml-2 h-4 w-4" />
|
||||||
|
نوشته جدید
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-4 md:grid-cols-4">
|
<div className="grid gap-4 md:grid-cols-4">
|
||||||
<Card><CardContent className="flex items-center flex-row-reverse gap-2 p-4"><BookOpenText className="h-5 w-5 text-primary" /><span>کل: {posts.length}</span></CardContent></Card>
|
<Card><CardContent className="flex items-center flex-row gap-2 p-4"><BookOpenText className="h-5 w-5 text-primary" /><span>کل: {posts.length}</span></CardContent></Card>
|
||||||
<Card><CardContent className="flex items-center flex-row-reverse gap-2 p-4"><Clock3 className="h-5 w-5 text-amber-600" /><span>بررسی: {stats.submitted ?? 0}</span></CardContent></Card>
|
<Card><CardContent className="flex items-center flex-row gap-2 p-4"><Clock3 className="h-5 w-5 text-amber-600" /><span>بررسی: {stats.submitted ?? 0}</span></CardContent></Card>
|
||||||
<Card><CardContent className="flex items-center flex-row-reverse gap-2 p-4"><CheckCircle2 className="h-5 w-5 text-emerald-600" /><span>منتشر: {stats.published ?? 0}</span></CardContent></Card>
|
<Card><CardContent className="flex items-center flex-row gap-2 p-4"><CheckCircle2 className="h-5 w-5 text-emerald-600" /><span>منتشر: {stats.published ?? 0}</span></CardContent></Card>
|
||||||
<Card><CardContent className="flex items-center flex-row-reverse gap-2 p-4"><XCircle className="h-5 w-5 text-rose-600" /><span>اصلاح: {stats.changes_requested ?? 0}</span></CardContent></Card>
|
<Card><CardContent className="flex items-center flex-row gap-2 p-4"><XCircle className="h-5 w-5 text-rose-600" /><span>اصلاح: {stats.changes_requested ?? 0}</span></CardContent></Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
||||||
<div className="flex gap-2">
|
<div className="text-right">
|
||||||
|
<CardTitle>نوشتهها</CardTitle>
|
||||||
|
<CardDescription>دسترسی نویسندهها به نوشتههای خودشان محدود میشود.</CardDescription>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2 sm:flex-row">
|
||||||
<Button variant="outline" onClick={loadPosts}>جستجو</Button>
|
<Button variant="outline" onClick={loadPosts}>جستجو</Button>
|
||||||
<Input value={search} onChange={(event) => setSearch(event.target.value)} placeholder="جستجو..." className="w-64 text-right" />
|
<Input value={search} onChange={(event) => setSearch(event.target.value)} placeholder="جستجو..." className="w-full text-right sm:w-64" />
|
||||||
<Select value={status} onValueChange={setStatus}>
|
<Select value={status} onValueChange={setStatus}>
|
||||||
<SelectTrigger className="w-48"><SelectValue /></SelectTrigger>
|
<SelectTrigger className="w-full sm:w-48"><SelectValue /></SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">همه وضعیتها</SelectItem>
|
<SelectItem value="all">همه وضعیتها</SelectItem>
|
||||||
<SelectItem value="draft">پیشنویس</SelectItem>
|
<SelectItem value="draft">پیشنویس</SelectItem>
|
||||||
@@ -132,10 +137,6 @@ export default function AdminBlog() {
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
|
||||||
<CardTitle>نوشتهها</CardTitle>
|
|
||||||
<CardDescription>دسترسی نویسندهها به نوشتههای خودشان محدود میشود.</CardDescription>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
@@ -143,35 +144,87 @@ export default function AdminBlog() {
|
|||||||
<div className="flex justify-center py-10"><Loader2 className="h-5 w-5 animate-spin" /></div>
|
<div className="flex justify-center py-10"><Loader2 className="h-5 w-5 animate-spin" /></div>
|
||||||
) : posts.length ? (
|
) : posts.length ? (
|
||||||
posts.map((post) => (
|
posts.map((post) => (
|
||||||
<div key={post.id} className="flex flex-col gap-3 rounded-2xl border p-4 md:flex-row md:items-center md:justify-between">
|
<div key={post.id} className="flex gap-3 rounded-2xl border p-4 flex-row items-center justify-between">
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
<Button variant="outline" size="sm" asChild>
|
|
||||||
<Link to={`/admin/blog/${post.id}/preview`}><Eye className="h-4 w-4" /></Link>
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" size="sm" asChild>
|
|
||||||
<Link to={`/admin/blog/${post.id}/edit`}><Edit className="h-4 w-4" /></Link>
|
|
||||||
</Button>
|
|
||||||
{post.status === "draft" || post.status === "changes_requested" ? (
|
|
||||||
<Button size="sm" onClick={() => submitPost(post.id)} disabled={actingId === post.id}>
|
|
||||||
<Send className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
{canReview && post.status === "submitted" ? (
|
|
||||||
<>
|
|
||||||
<Button size="sm" onClick={() => reviewPost(post.id, "publish")} disabled={actingId === post.id}>انتشار</Button>
|
|
||||||
<Button size="sm" variant="outline" onClick={() => reviewPost(post.id, "request_changes")} disabled={actingId === post.id}>درخواست اصلاح</Button>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="flex flex-wrap items-center justify-end gap-2">
|
<div className="flex flex-col-reverse md:flex-row flex-wrap items-start gap-2">
|
||||||
<Badge variant={post.status === "published" ? "default" : "secondary"}>{statusLabels[post.status] ?? post.status}</Badge>
|
|
||||||
<h3 className="font-semibold">{post.title}</h3>
|
<h3 className="font-semibold">{post.title}</h3>
|
||||||
|
<Badge variant={post.status === "published" ? "default" : "secondary"}>{statusLabels[post.status] ?? post.status}</Badge>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
{post.updated_at ? formatJalali(post.updated_at, false) : ""}
|
{post.updated_at ? formatJalali(post.updated_at, false) : ""}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="grid grid-cols-2 grid-flow-col grid-rows-2 gap-2 md:flex md:flex-row md:flex-wrap" dir="ltr">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button variant="outline" size="sm" asChild className="w-full md:w-auto">
|
||||||
|
<Link
|
||||||
|
to={`/admin/blog/${post.id}/preview`}
|
||||||
|
aria-label="پیشنمایش"
|
||||||
|
className="flex justify-center"
|
||||||
|
>
|
||||||
|
<Eye className="h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>پیشنمایش</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button variant="secondary" size="sm" asChild className="w-full md:w-auto">
|
||||||
|
<Link
|
||||||
|
to={`/admin/blog/${post.id}/edit`}
|
||||||
|
aria-label="ویرایش"
|
||||||
|
className="flex justify-center"
|
||||||
|
>
|
||||||
|
<Edit className="h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>ویرایش</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{post.status === "draft" || post.status === "changes_requested" ? (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => submitPost(post.id)}
|
||||||
|
disabled={actingId === post.id}
|
||||||
|
aria-label="ارسال برای بررسی"
|
||||||
|
className="w-full md:w-auto"
|
||||||
|
>
|
||||||
|
<Send className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>ارسال برای بررسی</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{canReview && post.status === "submitted" ? (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => reviewPost(post.id, "publish")}
|
||||||
|
disabled={actingId === post.id}
|
||||||
|
className="w-full md:w-auto"
|
||||||
|
>
|
||||||
|
انتشار
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => reviewPost(post.id, "request_changes")}
|
||||||
|
disabled={actingId === post.id}
|
||||||
|
className="w-full md:w-auto"
|
||||||
|
>
|
||||||
|
درخواست اصلاح
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ export default function AdminBlogCategories() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const categories = useMemo(() => categoriesQuery.data ?? [], [categoriesQuery.data]);
|
const categories = useMemo(() => categoriesQuery.data ?? [], [categoriesQuery.data]);
|
||||||
|
const rootCategories = useMemo(() => categories.filter((category) => !category.parent_id), [categories]);
|
||||||
|
const editingHasChildren = useMemo(
|
||||||
|
() => Boolean(editing && categories.some((category) => category.parent_id === editing.id)),
|
||||||
|
[categories, editing],
|
||||||
|
);
|
||||||
const visibleCategories = useMemo(() => {
|
const visibleCategories = useMemo(() => {
|
||||||
const needle = search.trim().toLowerCase();
|
const needle = search.trim().toLowerCase();
|
||||||
if (!needle) return categories;
|
if (!needle) return categories;
|
||||||
@@ -166,7 +171,7 @@ export default function AdminBlogCategories() {
|
|||||||
<Loader2 className="h-5 w-5 animate-spin" />
|
<Loader2 className="h-5 w-5 animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
) : visibleCategories.length ? (
|
) : visibleCategories.length ? (
|
||||||
<div className="overflow-hidden rounded-2xl border">
|
<div className="overflow-x-auto rounded-2xl border">
|
||||||
<table className="w-full min-w-[760px] text-sm">
|
<table className="w-full min-w-[760px] text-sm">
|
||||||
<thead className="bg-muted/40 text-muted-foreground">
|
<thead className="bg-muted/40 text-muted-foreground">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -217,7 +222,7 @@ export default function AdminBlogCategories() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{deletedQuery.data?.length ? (
|
{deletedQuery.data?.length ? (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col gap-3">
|
||||||
{deletedQuery.data.map((category) => (
|
{deletedQuery.data.map((category) => (
|
||||||
<div key={category.id} className="flex items-center justify-between rounded-2xl border p-3">
|
<div key={category.id} className="flex items-center justify-between rounded-2xl border p-3">
|
||||||
<span className="font-medium">{category.name}</span>
|
<span className="font-medium">{category.name}</span>
|
||||||
@@ -251,15 +256,24 @@ export default function AdminBlogCategories() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label className="mb-2 block">والد</Label>
|
<Label className="mb-2 block">والد</Label>
|
||||||
<Select value={form.parent_id} onValueChange={(value) => setForm((prev) => ({ ...prev, parent_id: value }))}>
|
<Select
|
||||||
|
value={form.parent_id}
|
||||||
|
onValueChange={(value) => setForm((prev) => ({ ...prev, parent_id: value }))}
|
||||||
|
disabled={editingHasChildren}
|
||||||
|
>
|
||||||
<SelectTrigger><SelectValue /></SelectTrigger>
|
<SelectTrigger><SelectValue /></SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="none">بدون والد</SelectItem>
|
<SelectItem value="none">بدون والد</SelectItem>
|
||||||
{categories.filter((item) => item.id !== editing?.id).map((category) => (
|
{rootCategories.filter((item) => item.id !== editing?.id).map((category) => (
|
||||||
<SelectItem key={category.id} value={String(category.id)}>{category.name}</SelectItem>
|
<SelectItem key={category.id} value={String(category.id)}>{category.name}</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
{editingHasChildren ? (
|
||||||
|
<p className="mt-2 text-xs text-muted-foreground">
|
||||||
|
دستهبندیهایی که زیرمجموعه دارند باید در سطح ریشه باقی بمانند.
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label className="mb-2 block">توضیحات</Label>
|
<Label className="mb-2 block">توضیحات</Label>
|
||||||
|
|||||||
@@ -226,9 +226,9 @@ export default function AdminBlogEditor({ postId }: Props) {
|
|||||||
<div className="rounded-3xl border bg-background/90 p-4 shadow-sm">
|
<div className="rounded-3xl border bg-background/90 p-4 shadow-sm">
|
||||||
<div className="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
|
<div className="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<div className="flex flex-wrap items-center justify-end gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<Badge variant="outline">{form.status || "draft"}</Badge>
|
|
||||||
<h2 className="text-2xl font-bold">{isNew ? "نوشته جدید" : "ویرایش نوشته"}</h2>
|
<h2 className="text-2xl font-bold">{isNew ? "نوشته جدید" : "ویرایش نوشته"}</h2>
|
||||||
|
<Badge variant="outline">{form.status || "draft"}</Badge>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
نوشتن مارکداون، پیشنمایش زنده و تنظیمات انتشار در یک محیط مینیمال.
|
نوشتن مارکداون، پیشنمایش زنده و تنظیمات انتشار در یک محیط مینیمال.
|
||||||
@@ -311,7 +311,7 @@ export default function AdminBlogEditor({ postId }: Props) {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label className="mb-2 block text-right">برچسبها</Label>
|
<Label className="mb-2 block text-right">برچسبها</Label>
|
||||||
<div className="flex flex-wrap justify-end gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{tags.map((tag) => {
|
{tags.map((tag) => {
|
||||||
const selected = selectedTagIds.includes(tag.id);
|
const selected = selectedTagIds.includes(tag.id);
|
||||||
return (
|
return (
|
||||||
@@ -332,7 +332,7 @@ export default function AdminBlogEditor({ postId }: Props) {
|
|||||||
{canAssignWriters ? (
|
{canAssignWriters ? (
|
||||||
<div>
|
<div>
|
||||||
<Label className="mb-2 block text-right">نویسندگان</Label>
|
<Label className="mb-2 block text-right">نویسندگان</Label>
|
||||||
<div className="flex flex-wrap justify-end gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{users.map((writer) => {
|
{users.map((writer) => {
|
||||||
const selected = selectedWriterIds.includes(writer.id);
|
const selected = selectedWriterIds.includes(writer.id);
|
||||||
const fullName = [writer.first_name, writer.last_name].filter(Boolean).join(" ") || writer.username;
|
const fullName = [writer.first_name, writer.last_name].filter(Boolean).join(" ") || writer.username;
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ export default function AdminBlogTags() {
|
|||||||
<Loader2 className="h-5 w-5 animate-spin" />
|
<Loader2 className="h-5 w-5 animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
) : visibleTags.length ? (
|
) : visibleTags.length ? (
|
||||||
<div className="overflow-hidden rounded-2xl border">
|
<div className="overflow-x-auto rounded-2xl border">
|
||||||
<table className="w-full min-w-[620px] text-sm">
|
<table className="w-full min-w-[620px] text-sm">
|
||||||
<thead className="bg-muted/40 text-muted-foreground">
|
<thead className="bg-muted/40 text-muted-foreground">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -197,7 +197,7 @@ export default function AdminBlogTags() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{deletedQuery.data?.length ? (
|
{deletedQuery.data?.length ? (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col gap-3">
|
||||||
{deletedQuery.data.map((tag) => (
|
{deletedQuery.data.map((tag) => (
|
||||||
<div key={tag.id} className="flex items-center justify-between rounded-2xl border p-3">
|
<div key={tag.id} className="flex items-center justify-between rounded-2xl border p-3">
|
||||||
<span className="font-medium">{tag.name}</span>
|
<span className="font-medium">{tag.name}</span>
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export default function AdminLayout({ children }: { children: ReactNode }) {
|
|||||||
<div className="flex min-h-screen">
|
<div className="flex min-h-screen">
|
||||||
<aside
|
<aside
|
||||||
className={cn(
|
className={cn(
|
||||||
"sticky top-0 hidden h-screen shrink-0 border-l bg-background/95 shadow-sm backdrop-blur transition-[width] duration-300 ease-in-out md:flex md:flex-col",
|
"sticky top-0 hidden h-screen shrink-0 border-l bg-background/95 shadow-sm backdrop-blur transition-[width] duration-300 ease-in-out lg:flex lg:flex-col",
|
||||||
collapsed ? "w-20" : "w-72",
|
collapsed ? "w-20" : "w-72",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -121,38 +121,48 @@ export default function AdminLayout({ children }: { children: ReactNode }) {
|
|||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="border-b bg-background/90 md:hidden">
|
<div className="border-b bg-background/90 lg:hidden">
|
||||||
<div className="flex items-center justify-between px-4 py-3">
|
<div className="px-4 py-3 text-right">
|
||||||
<h1 className="text-lg font-bold">پنل مدیریت</h1>
|
<h1 className="text-lg font-bold">پنل مدیریت</h1>
|
||||||
<div className="flex gap-2 overflow-x-auto">
|
<p className="text-xs text-muted-foreground">مدیریت بخشهای سامانه</p>
|
||||||
{visibleNavItems.map((item) => {
|
|
||||||
const Icon = item.icon;
|
|
||||||
return (
|
|
||||||
<NavLink
|
|
||||||
key={item.to}
|
|
||||||
to={item.to}
|
|
||||||
className={({ isActive }) =>
|
|
||||||
cn(
|
|
||||||
"flex shrink-0 items-center gap-1 rounded-full px-3 py-2 text-xs",
|
|
||||||
isActive || isItemActive(item.to)
|
|
||||||
? "bg-primary text-primary-foreground"
|
|
||||||
: "bg-muted text-muted-foreground",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Icon className="h-4 w-4" />
|
|
||||||
{item.label}
|
|
||||||
</NavLink>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="container mx-auto px-4 py-6">
|
<div className="container mx-auto min-w-0 px-3 pb-28 pt-4 sm:px-4 lg:py-6">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="fixed inset-x-0 z-50 px-4 lg:hidden"
|
||||||
|
style={{ bottom: "calc(env(safe-area-inset-bottom) + 0.9rem)" }}
|
||||||
|
>
|
||||||
|
<nav
|
||||||
|
aria-label="Admin mobile navigation"
|
||||||
|
className="mx-auto flex w-full max-w-sm items-center justify-between rounded-[1.75rem] border border-white/20 bg-background/70 px-2 py-2 shadow-[0_18px_60px_rgba(15,23,42,0.18)] backdrop-blur-2xl dark:border-white/10 dark:bg-slate-950/65"
|
||||||
|
dir="rtl"
|
||||||
|
>
|
||||||
|
{visibleNavItems.map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
const active = isItemActive(item.to);
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
key={item.to}
|
||||||
|
to={item.to}
|
||||||
|
className={cn(
|
||||||
|
"flex min-w-0 flex-1 flex-col items-center justify-center gap-1 rounded-2xl px-2 py-2 text-[10px] font-medium transition-all",
|
||||||
|
active
|
||||||
|
? "bg-primary text-primary-foreground shadow-sm"
|
||||||
|
: "text-muted-foreground hover:bg-white/30 hover:text-foreground dark:hover:bg-white/10",
|
||||||
|
)}
|
||||||
|
aria-current={active ? "page" : undefined}
|
||||||
|
>
|
||||||
|
<Icon className={cn("h-5 w-5", active ? "scale-105" : "")} />
|
||||||
|
<span className="max-w-full truncate">{item.label}</span>
|
||||||
|
</NavLink>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user