- Updated .env.example to include configuration for OnlyOffice Document Server. - Modified the workspace configuration to remove the drive-suite path. - Adjusted TypeScript environment imports for consistency. - Enhanced Next.js configuration to disable canvas in Webpack. - Updated package.json to include new dependencies for OnlyOffice and PDF.js. - Added global styles for OnlyOffice theme integration in the CSS. - Created new layout and page components for the Drive feature, including public sharing and editing functionalities. - Updated metadata handling across various layouts to reflect the new app structure.
164 lines
4.3 KiB
TypeScript
164 lines
4.3 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">
|
||
<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>
|
||
)
|
||
}
|