diff --git a/components/dashboard/dashboard-layout.tsx b/components/dashboard/dashboard-layout.tsx index 4a7220e..e777912 100644 --- a/components/dashboard/dashboard-layout.tsx +++ b/components/dashboard/dashboard-layout.tsx @@ -19,7 +19,10 @@ type DashboardLayoutProps = { icon?: LucideIcon; items: RootNavItem[]; sidebarIconSrc?: string; + sidebarBrandHref?: string; + sidebarBrandHoverLabel?: string; sidebarTop?: ReactNode; + sidebarFooterItems?: RootNavItem[]; sidebarFooter?: ReactNode; headerActions?: ReactNode; children: ReactNode; @@ -31,7 +34,10 @@ export function DashboardLayout({ icon: Icon, items, sidebarIconSrc, + sidebarBrandHref, + sidebarBrandHoverLabel, sidebarTop, + sidebarFooterItems, sidebarFooter, headerActions, children, @@ -43,7 +49,10 @@ export function DashboardLayout({ @@ -64,7 +73,10 @@ export function DashboardLayout({ diff --git a/components/dashboard/sidebar.tsx b/components/dashboard/sidebar.tsx index 57bc040..361795a 100644 --- a/components/dashboard/sidebar.tsx +++ b/components/dashboard/sidebar.tsx @@ -17,11 +17,22 @@ type DashboardSidebarItem = { type DashboardSidebarProps = { items: DashboardSidebarItem[]; iconSrc?: string; + brandHref?: string; + brandHoverLabel?: string; top?: ReactNode; + footerItems?: DashboardSidebarItem[]; footer?: ReactNode; }; -export function DashboardSidebar({ items, iconSrc, top, footer }: DashboardSidebarProps) { +export function DashboardSidebar({ + items, + iconSrc, + brandHref = "/", + brandHoverLabel, + top, + footerItems = [], + footer, +}: DashboardSidebarProps) { function renderItem(item: DashboardSidebarItem, nested = false) { const Icon = item.icon; const hasChildren = Boolean(item.children?.length); @@ -50,8 +61,11 @@ export function DashboardSidebar({ items, iconSrc, top, footer }: DashboardSideb return (
-
-
+ +
{/* eslint-disable-next-line @next/next/no-img-element */}
-
-

Mohs Admin

-

Website owner workspace

+
+
+

Mohs Admin

+

Website owner workspace

+
+
+

+ {brandHoverLabel ?? "Back to Home"} +

+
-
+ - {top ?
{top}
: null} + {top ?
{top}
: null} - +
- {footer ? ( + {footer || footerItems.length ? ( <> -
{footer}
+
+ {footerItems.length ? : null} + {footerItems.length && footer ? : null} + {footer} +
) : null}
diff --git a/components/layout/locale-toggle.tsx b/components/layout/locale-toggle.tsx index 3cf41fa..7a581c1 100644 --- a/components/layout/locale-toggle.tsx +++ b/components/layout/locale-toggle.tsx @@ -19,7 +19,11 @@ type LocaleToggleProps = { showLabel?: boolean; }; -export function LocaleToggle({ locale, className, showLabel = false }: LocaleToggleProps) { +export function LocaleToggle({ + locale, + className, + showLabel = false, +}: LocaleToggleProps) { const pathname = usePathname(); const locales: AppLocale[] = ["de", "en", "ar"]; const currentPath = stripLocalePrefix(pathname); @@ -41,40 +45,44 @@ export function LocaleToggle({ locale, className, showLabel = false }: LocaleTog size={showLabel ? "sm" : "icon"} aria-label={`Locale: ${currentLocale.toUpperCase()}`} className={cn( - "group cursor-pointer rounded-pill border border-transparent text-muted-foreground hover:border-border/70 hover:bg-background/80 hover:text-foreground", - showLabel ? "gap-2 px-3" : "h-9 w-9 p-0", + "group inline-flex shrink-0 cursor-pointer items-center justify-center overflow-hidden rounded-full border border-transparent text-muted-foreground hover:border-border/70 hover:bg-background/80 hover:text-foreground", + showLabel ? "h-9 w-9 p-0" : "h-9 w-9 p-0", className, )} > {localeFlags[currentLocale].alt} - + {availableLocales.map((targetLocale) => ( {localeFlags[targetLocale].alt} diff --git a/components/layout/site-header.tsx b/components/layout/site-header.tsx index 86e7503..abdba1a 100644 --- a/components/layout/site-header.tsx +++ b/components/layout/site-header.tsx @@ -95,6 +95,12 @@ export function SiteHeader({ const locale = useLocale(); const t = useTranslations("navigation"); const isArabic = locale === "ar"; + const desktopControlRailClassName = + "inline-flex items-center gap-1 rounded-[var(--radius-pill)] border border-black/6 bg-white/24 p-1 shadow-[0_14px_34px_-24px_rgba(15,23,42,0.18)] backdrop-blur-[18px] dark:border-white/10 dark:bg-white/4 dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)]"; + const desktopControlButtonClassName = + "h-9 w-9 rounded-full border border-transparent p-0 text-[hsl(var(--hero-ink)/0.76)] hover:border-black/5 hover:bg-white/36 hover:text-[hsl(var(--hero-ink))] dark:text-foreground dark:hover:border-white/10 dark:hover:bg-white/10"; + const mobileControlButtonClassName = + "h-10 w-10 rounded-full border border-black/6 bg-white/32 p-0 text-[hsl(var(--hero-ink)/0.8)] hover:bg-white/42 hover:text-[hsl(var(--hero-ink))] dark:border-white/10 dark:bg-white/6 dark:text-foreground dark:hover:bg-white/10"; return (
@@ -134,25 +140,24 @@ export function SiteHeader({ initial={{ opacity: 0, y: -8 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.28, ease: "easeOut", delay: 0.03 }} - className="inline-flex items-center gap-1 rounded-[var(--radius-pill)] border border-black/6 bg-white/24 p-1 shadow-[0_14px_34px_-24px_rgba(15,23,42,0.18)] backdrop-blur-[18px] dark:border-white/10 dark:bg-white/4 dark:shadow-[0_20px_48px_-28px_rgba(0,0,0,0.58)]" + className={desktopControlRailClassName} > + {isAdmin ? ( - ) : null} -
@@ -255,22 +260,21 @@ export function SiteHeader({
+ {isAdmin ? ( - ) : null} -
diff --git a/components/root/root-dashboard-shell.tsx b/components/root/root-dashboard-shell.tsx index ba7bad6..b2babc3 100644 --- a/components/root/root-dashboard-shell.tsx +++ b/components/root/root-dashboard-shell.tsx @@ -1,6 +1,5 @@ import type { ReactNode } from "react"; import { - ArrowLeft, FolderKanban, Globe2, ImageIcon, @@ -20,7 +19,6 @@ import { FormSaveButton } from "@/components/root/form-save-button"; import { SidebarMaintenanceControl } from "@/components/root/sidebar-maintenance-control"; import { ThemeToggle } from "@/components/theme-toggle"; import { Button } from "@/components/ui/button"; -import { Separator } from "@/components/ui/separator"; import { getMaintenanceMode, getSiteSettingsMediaBindings } from "@/lib/app-config"; import { getLocalizedPath } from "@/lib/locale"; import { getRootNavigation } from "@/lib/root-navigation"; @@ -82,9 +80,17 @@ export async function RootDashboardShell({ getSiteSettingsMediaBindings(), getMaintenanceMode(), ]); - const normalizedSidebarItems = getRootNavigation(copy, active, smtpChild, portfolioChild).filter( - (item) => item.href !== "/root/maintenance" && item.href !== "/root/ui-kit", + const sidebarItems = getRootNavigation(copy, active, smtpChild, portfolioChild); + const normalizedSidebarItems = sidebarItems.filter( + (item) => + item.href !== "/root/maintenance" && + item.href !== "/root/ui-kit" && + item.href !== "/root/smtp" && + item.href !== "/root/marquee", ); + const footerSidebarItems = ["/root/marquee", "/root/smtp"] + .map((href) => sidebarItems.find((item) => item.href === href)) + .filter((item): item is NonNullable => Boolean(item)); const headerIcon = active === "overview" ? LayoutDashboard @@ -124,17 +130,10 @@ export async function RootDashboardShell({ icon={headerIcon} items={normalizedSidebarItems} sidebarIconSrc={mediaBindings.favicon?.url} - sidebarTop={ -
- - {sidebarTopContent} -
- } + sidebarBrandHref={getLocalizedPath("de")} + sidebarBrandHoverLabel={copy.backToSite} + sidebarTop={sidebarTopContent} + sidebarFooterItems={footerSidebarItems} sidebarFooter={ <> - } headerActions={