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.
187 lines
5.0 KiB
TypeScript
187 lines
5.0 KiB
TypeScript
"use client"
|
||
|
||
import { useEffect, useRef, useState } from "react"
|
||
import Link from "next/link"
|
||
import { LayoutGrid, Pencil } from "lucide-react"
|
||
import { Button } from "@/components/ui/button"
|
||
import {
|
||
SUITE_FAVORITE_APPS,
|
||
type FavoriteApp,
|
||
} from "@/lib/suite/favorite-apps"
|
||
import { SUITE_HEADER_DROPDOWN_CLASS, SUITE_ICON_BTN } from "@/lib/suite/suite-chrome-classes"
|
||
import { cn } from "@/lib/utils"
|
||
|
||
const FAVORITE_TILE_CLASS =
|
||
"flex flex-col items-center gap-2 rounded-lg p-3 transition-colors hover:bg-accent"
|
||
|
||
const DEFAULT_ICON_BTN_CLASS = cn(
|
||
"rounded-full",
|
||
SUITE_ICON_BTN,
|
||
"hover:text-accent-foreground",
|
||
)
|
||
|
||
function FavoriteAppTile({
|
||
app,
|
||
onNavigate,
|
||
}: {
|
||
app: FavoriteApp
|
||
onNavigate?: () => void
|
||
}) {
|
||
const content = (
|
||
<>
|
||
<div className="flex h-10 w-10 items-center justify-center">
|
||
{app.iconDark ? (
|
||
<>
|
||
<img
|
||
src={app.icon}
|
||
alt=""
|
||
className="h-10 w-10 object-contain dark:hidden"
|
||
onError={(e) => {
|
||
const target = e.target as HTMLImageElement
|
||
target.style.display = "none"
|
||
}}
|
||
/>
|
||
<img
|
||
src={app.iconDark}
|
||
alt=""
|
||
className="hidden h-10 w-10 object-contain dark:block"
|
||
onError={(e) => {
|
||
const target = e.target as HTMLImageElement
|
||
target.style.display = "none"
|
||
}}
|
||
/>
|
||
</>
|
||
) : (
|
||
<img
|
||
src={app.icon}
|
||
alt=""
|
||
className={cn(
|
||
"h-10 w-10 object-contain",
|
||
app.whiteLogoInDark && "dark:invert dark:hue-rotate-180",
|
||
)}
|
||
onError={(e) => {
|
||
const target = e.target as HTMLImageElement
|
||
target.style.display = "none"
|
||
target.parentElement!.innerHTML = `<div class="flex h-10 w-10 items-center justify-center rounded-full bg-blue-500 font-bold text-white">${app.name[0]}</div>`
|
||
}}
|
||
/>
|
||
)}
|
||
</div>
|
||
<span className="w-full text-center text-xs text-muted-foreground">
|
||
{app.name}
|
||
</span>
|
||
</>
|
||
)
|
||
|
||
if (!app.href) {
|
||
return (
|
||
<button type="button" className={FAVORITE_TILE_CLASS} disabled>
|
||
{content}
|
||
</button>
|
||
)
|
||
}
|
||
|
||
if (app.external) {
|
||
return (
|
||
<a
|
||
href={app.href}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className={FAVORITE_TILE_CLASS}
|
||
onClick={onNavigate}
|
||
>
|
||
{content}
|
||
</a>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<Link href={app.href} className={FAVORITE_TILE_CLASS} onClick={onNavigate}>
|
||
{content}
|
||
</Link>
|
||
)
|
||
}
|
||
|
||
interface SuiteFavoritesMenuProps {
|
||
className?: string
|
||
iconButtonClass?: string
|
||
dropdownClass?: string
|
||
/** Fermer un autre panneau header (ex. compte) à l’ouverture. */
|
||
onOpen?: () => void
|
||
}
|
||
|
||
export function SuiteFavoritesMenu({
|
||
className,
|
||
iconButtonClass = DEFAULT_ICON_BTN_CLASS,
|
||
dropdownClass = SUITE_HEADER_DROPDOWN_CLASS,
|
||
onOpen,
|
||
}: SuiteFavoritesMenuProps) {
|
||
const [open, setOpen] = useState(false)
|
||
const menuRef = useRef<HTMLDivElement>(null)
|
||
|
||
useEffect(() => {
|
||
function handleClickOutside(event: MouseEvent) {
|
||
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
||
setOpen(false)
|
||
}
|
||
}
|
||
document.addEventListener("mousedown", handleClickOutside)
|
||
return () => document.removeEventListener("mousedown", handleClickOutside)
|
||
}, [])
|
||
|
||
return (
|
||
<div className={cn("relative hidden sm:block", className)} ref={menuRef}>
|
||
<Button
|
||
variant="ghost"
|
||
size="icon"
|
||
className={iconButtonClass}
|
||
aria-label="Applications"
|
||
aria-expanded={open}
|
||
aria-haspopup="dialog"
|
||
onClick={() => {
|
||
const next = !open
|
||
setOpen(next)
|
||
if (next) onOpen?.()
|
||
}}
|
||
>
|
||
<LayoutGrid className="size-6 shrink-0" aria-hidden />
|
||
</Button>
|
||
|
||
{open ? (
|
||
<div
|
||
className={cn(
|
||
"absolute right-0 top-12 z-50 w-96 rounded-2xl",
|
||
dropdownClass,
|
||
)}
|
||
role="dialog"
|
||
aria-label="Vos favoris"
|
||
>
|
||
<div className="flex items-center justify-between border-b border-border p-4">
|
||
<span className="text-lg font-normal text-foreground">
|
||
Vos favoris
|
||
</span>
|
||
<Button
|
||
variant="ghost"
|
||
size="icon"
|
||
className={cn("h-8 w-8", iconButtonClass)}
|
||
aria-label="Personnaliser les favoris"
|
||
disabled
|
||
>
|
||
<Pencil className="h-4 w-4" />
|
||
</Button>
|
||
</div>
|
||
<div className="grid grid-cols-3 gap-1 p-3">
|
||
{SUITE_FAVORITE_APPS.map((app) => (
|
||
<FavoriteAppTile
|
||
key={app.name}
|
||
app={app}
|
||
onNavigate={() => setOpen(false)}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
) : null}
|
||
</div>
|
||
)
|
||
}
|