diff --git a/app/boot-skeleton.css b/app/boot-skeleton.css index 87664f686..7ce12389c 100644 --- a/app/boot-skeleton.css +++ b/app/boot-skeleton.css @@ -44,6 +44,7 @@ @custom-variant boot-no-calendar (&:where([data-boot-rail]:not([data-boot-rail~="calendar"]) *)); @custom-variant boot-no-contacts (&:where([data-boot-rail]:not([data-boot-rail~="contacts"]) *)); @custom-variant boot-no-files (&:where([data-boot-rail]:not([data-boot-rail~="files"]) *)); +@custom-variant boot-rail-add-app (&:where([data-boot-rail~="add-app"] *)); @custom-variant boot-rail-admin (&:where([data-boot-rail~="admin"] *)); @custom-variant boot-rail-a11y (&:where([data-boot-rail~="a11y"] *)); @custom-variant boot-rail-quota (&:where([data-boot-rail~="quota"] *)); diff --git a/components/boot-skeleton/shell.tsx b/components/boot-skeleton/shell.tsx index 806f9e57b..af71f393c 100644 --- a/components/boot-skeleton/shell.tsx +++ b/components/boot-skeleton/shell.tsx @@ -16,6 +16,7 @@ import { Keyboard, Mail, Menu, + Plus, Settings, Shield, Users, @@ -66,6 +67,9 @@ export function RailSkeleton({ active, className }: { active?: RailApp; classNam ))} + + +
diff --git a/components/boot-skeleton/use-skeleton-labels.ts b/components/boot-skeleton/use-skeleton-labels.ts new file mode 100644 index 000000000..5c828640c --- /dev/null +++ b/components/boot-skeleton/use-skeleton-labels.ts @@ -0,0 +1,24 @@ +"use client"; + +import { useTranslations } from "next-intl"; +import type { RailLabels } from "./shell"; + +/** + * The few strings a page skeleton shows: the status message and the phone + * bottom bar's labels. For the gates of client pages, which have the message + * catalog; the skeleton components themselves stay free of next-intl. + */ +export function useSkeletonLabels(): { label: string; labels: RailLabels } { + const t = useTranslations(); + return { + label: t("common.loading"), + labels: { + mail: t("sidebar.mail"), + calendar: t("sidebar.calendar"), + contacts: t("sidebar.contacts"), + files: t("sidebar.files"), + settings: t("sidebar.settings"), + admin: t("sidebar.admin"), + }, + }; +} diff --git a/components/layout/navigation-rail.tsx b/components/layout/navigation-rail.tsx index 71653b1d8..1ebaed59c 100644 --- a/components/layout/navigation-rail.tsx +++ b/components/layout/navigation-rail.tsx @@ -357,13 +357,14 @@ export function NavigationRail({ if (!client) return; const items: RailHint[] = []; for (const item of navItems) if (item.id !== 'mail' && !item.hidden) items.push(item.id as RailHint); + if (sidebarAppsEnabled && onManageApps) items.push('add-app'); if (isStalwartAdmin) items.push('admin'); if (showAccessibilityButton) items.push('a11y'); if (quotaShown) items.push('quota'); writeRailHints(items); // navItems is rebuilt every render; its inputs are listed instead. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [client, supportsCalendar, calendarEnabled, supportsContacts, contactsEnabled, supportsFiles, filesEnabled, isStalwartAdmin, showAccessibilityButton, quotaShown]); + }, [client, supportsCalendar, calendarEnabled, supportsContacts, contactsEnabled, supportsFiles, filesEnabled, sidebarAppsEnabled, !!onManageApps, isStalwartAdmin, showAccessibilityButton, quotaShown]); const getIsActive = (href: string, itemId: string) => { if (activeAppId) return false; diff --git a/lib/boot-skeleton/hints.ts b/lib/boot-skeleton/hints.ts index cc93fdac9..e5125777f 100644 --- a/lib/boot-skeleton/hints.ts +++ b/lib/boot-skeleton/hints.ts @@ -6,7 +6,7 @@ import { BOOT_HINTS_KEY } from './keys'; * admin rights, quota), which nothing else keeps across a reload. The boot * script turns it into `data-boot-rail` (lib/boot-skeleton/boot-script.ts). */ -export type RailHint = 'calendar' | 'contacts' | 'files' | 'admin' | 'a11y' | 'quota'; +export type RailHint = 'calendar' | 'contacts' | 'files' | 'add-app' | 'admin' | 'a11y' | 'quota'; let lastWritten: string | null = null;