Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Introduced turbopack alias for canvas in next.config.mjs. - Updated package.json scripts for development and branding tasks. - Added new dependencies for Tiptap extensions. - Implemented new demo layouts for agenda, contacts, drive, and mail applications. - Enhanced globals.css for improved theming and splash screen animations. - Added OAuth callback handling for drive mounts. - Updated layout components to integrate new demo shells and improve structure.
211 lines
7.6 KiB
TypeScript
211 lines
7.6 KiB
TypeScript
"use client"
|
|
|
|
import { useMemo, useState } from "react"
|
|
import Link from "next/link"
|
|
import { useParams, usePathname } from "next/navigation"
|
|
import { Icon } from "@iconify/react"
|
|
import { Clock, Star, Trash2 } from "lucide-react"
|
|
import { cn } from "@/lib/utils"
|
|
import {
|
|
DRIVE_SIDEBAR_CARET_SLOT_CLASS,
|
|
DRIVE_SIDEBAR_ROW_BODY_CLASS,
|
|
DRIVE_SIDEBAR_ROW_CLASS,
|
|
} from "@/lib/drive/drive-chrome-classes"
|
|
import { mailNavRowClass } from "@/lib/mail-chrome-classes"
|
|
import { DriveQuotaBar } from "@/components/drive/quota-bar"
|
|
import { DriveNewMenu } from "@/components/drive/new-menu"
|
|
import { DriveSidebarFolderTree } from "@/components/drive/sidebar-folder-tree"
|
|
import { DriveSidebarOrgFolders } from "@/components/drive/drive-sidebar-org-folders"
|
|
import { DriveSidebarMounts, DriveConnectMountAction } from "@/components/drive/drive-sidebar-mounts"
|
|
import { AccountAvatar } from "@/components/suite/account-avatar"
|
|
import { AccountSwitcherSheet } from "@/components/suite/account-switcher-sheet"
|
|
import { Button } from "@/components/ui/button"
|
|
import { useIsXs } from "@/hooks/use-xs"
|
|
import { useDriveRouteRoot } from "@/lib/drive/drive-route-context"
|
|
import { driveRouteBase, folderPathFromSegments, parseDriveSegments } from "@/lib/drive/drive-url"
|
|
import { useChromeIdentity } from "@/lib/hooks/use-chrome-identity"
|
|
import { useDriveUIStore } from "@/lib/stores/drive-ui-store"
|
|
import {
|
|
SUITE_APP_LOGO_LOCKUP_CLASS,
|
|
SUITE_APP_LOGO_MARK_CLASS,
|
|
SUITE_APP_LOGO_TEXT_CLASS,
|
|
} from "@/lib/suite/suite-chrome-classes"
|
|
|
|
export function DriveSidebar({
|
|
overlay = false,
|
|
open = true,
|
|
}: {
|
|
overlay?: boolean
|
|
open?: boolean
|
|
}) {
|
|
const pathname = usePathname()
|
|
const params = useParams()
|
|
const routeRoot = useDriveRouteRoot()
|
|
const driveBase = driveRouteBase(routeRoot)
|
|
const otherNav = [
|
|
{ href: `${driveBase}/recent`, label: "Récents", icon: Clock },
|
|
{ href: `${driveBase}/starred`, label: "Favoris", icon: Star },
|
|
{ href: `${driveBase}/trash`, label: "Corbeille", icon: Trash2 },
|
|
]
|
|
const isXs = useIsXs()
|
|
const identity = useChromeIdentity()
|
|
const [accountMenuOpen, setAccountMenuOpen] = useState(false)
|
|
const setSidebarCollapsed = useDriveUIStore((s) => s.setSidebarCollapsed)
|
|
const route = useMemo(
|
|
() => parseDriveSegments(params.segments as string[] | undefined),
|
|
[params.segments]
|
|
)
|
|
const parentPath = folderPathFromSegments(route.pathSegments)
|
|
const filesSegments = route.view === "files" ? route.pathSegments : []
|
|
const sharedSegments = route.view === "shared" ? route.pathSegments : []
|
|
const orgSegments = route.view === "org" ? route.pathSegments : []
|
|
const mountSegments = route.view === "mount" ? route.pathSegments : []
|
|
|
|
const closeSidebar = () => setSidebarCollapsed(true)
|
|
const displayName = identity?.name ?? "Utilisateur"
|
|
|
|
return (
|
|
<aside
|
|
className={cn(
|
|
"flex h-full w-56 shrink-0 flex-col bg-app-canvas text-foreground",
|
|
overlay
|
|
? cn(
|
|
"fixed inset-y-0 left-0 z-50 shadow-xl transition-transform duration-200 ease-linear",
|
|
open ? "translate-x-0" : "-translate-x-full pointer-events-none"
|
|
)
|
|
: "relative"
|
|
)}
|
|
aria-hidden={overlay && !open}
|
|
>
|
|
<div className="flex shrink-0 items-center justify-between gap-2 px-4 py-4">
|
|
<div className={cn(SUITE_APP_LOGO_LOCKUP_CLASS, "min-w-0")}>
|
|
<img
|
|
src="/drive/ultidrive-mark.svg"
|
|
alt=""
|
|
className={SUITE_APP_LOGO_MARK_CLASS}
|
|
onError={(e) => {
|
|
;(e.target as HTMLImageElement).style.display = "none"
|
|
}}
|
|
/>
|
|
<span className={SUITE_APP_LOGO_TEXT_CLASS}>UltiDrive</span>
|
|
</div>
|
|
{isXs ? (
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="size-9 shrink-0 rounded-full text-gray-600 dark:text-muted-foreground"
|
|
aria-label="Réglages"
|
|
asChild
|
|
>
|
|
<Link href="/mail/settings">
|
|
<Icon icon="mdi:cog" className="size-5 shrink-0" aria-hidden />
|
|
</Link>
|
|
</Button>
|
|
) : null}
|
|
</div>
|
|
<div className="flex shrink-0 px-3 pb-3">
|
|
<DriveNewMenu parentPath={parentPath} />
|
|
</div>
|
|
<nav className="flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-2">
|
|
<div className="pb-1">
|
|
<DriveSidebarFolderTree
|
|
view="files"
|
|
pathSegments={filesSegments}
|
|
active={route.view === "files"}
|
|
/>
|
|
<DriveSidebarFolderTree
|
|
view="shared"
|
|
pathSegments={sharedSegments}
|
|
active={route.view === "shared"}
|
|
/>
|
|
<DriveSidebarMounts
|
|
active={route.view === "mount"}
|
|
pathSegments={mountSegments}
|
|
rootId={route.rootId}
|
|
/>
|
|
</div>
|
|
<DriveSidebarOrgFolders
|
|
active={route.view === "org"}
|
|
pathSegments={orgSegments}
|
|
rootId={route.rootId}
|
|
/>
|
|
{otherNav.map(({ href, label, icon: Icon }) => {
|
|
const active = pathname.startsWith(href)
|
|
return (
|
|
<Link
|
|
key={href}
|
|
href={href}
|
|
onClick={() => {
|
|
if (overlay) closeSidebar()
|
|
}}
|
|
className={cn(
|
|
DRIVE_SIDEBAR_ROW_CLASS,
|
|
"cursor-pointer",
|
|
mailNavRowClass({ isSelected: active })
|
|
)}
|
|
>
|
|
<span className={DRIVE_SIDEBAR_CARET_SLOT_CLASS} aria-hidden />
|
|
<span className={DRIVE_SIDEBAR_ROW_BODY_CLASS}>
|
|
<Icon className="h-4 w-4 shrink-0" />
|
|
<span className="truncate">{label}</span>
|
|
</span>
|
|
</Link>
|
|
)
|
|
})}
|
|
</nav>
|
|
<div
|
|
className={cn(
|
|
"sticky bottom-0 shrink-0 border-t border-border bg-app-canvas",
|
|
isXs && "pb-[calc(4rem+env(safe-area-inset-bottom))]",
|
|
)}
|
|
>
|
|
<div className={cn(isXs ? "px-3 pt-2 pb-0" : "px-3 pt-2")}>
|
|
<DriveConnectMountAction />
|
|
</div>
|
|
<div className={cn(isXs ? "px-3 pt-1.5 pb-0" : "p-3 pt-2")}>
|
|
<DriveQuotaBar />
|
|
</div>
|
|
{isXs ? (
|
|
<>
|
|
<button
|
|
type="button"
|
|
className="flex w-full min-w-0 items-center gap-2.5 px-4 py-0.5 text-left transition-colors hover:bg-mail-nav-hover"
|
|
aria-label={`Compte : ${identity?.email ?? displayName}`}
|
|
aria-expanded={accountMenuOpen}
|
|
aria-haspopup="dialog"
|
|
onClick={() => setAccountMenuOpen(true)}
|
|
>
|
|
{identity ? (
|
|
<AccountAvatar
|
|
account={{
|
|
name: identity.name,
|
|
email: identity.email,
|
|
avatarUrl: identity.avatarUrl,
|
|
}}
|
|
size="sm"
|
|
/>
|
|
) : (
|
|
<span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm font-medium text-muted-foreground">
|
|
?
|
|
</span>
|
|
)}
|
|
<span className="min-w-0 flex-1">
|
|
<span className="block truncate text-xs text-muted-foreground">
|
|
Connecté en tant que
|
|
</span>
|
|
<span className="block truncate text-sm font-medium">
|
|
{displayName}
|
|
</span>
|
|
</span>
|
|
</button>
|
|
<AccountSwitcherSheet
|
|
open={accountMenuOpen}
|
|
onOpenChange={setAccountMenuOpen}
|
|
/>
|
|
</>
|
|
) : null}
|
|
</div>
|
|
</aside>
|
|
)
|
|
}
|