183 lines
5.1 KiB
TypeScript
183 lines
5.1 KiB
TypeScript
"use client";
|
|
|
|
import type { ReactNode } from "react";
|
|
import { Loader2, type LucideIcon } from "lucide-react";
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipProvider,
|
|
TooltipTrigger,
|
|
} from "@/components/ui/tooltip";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type FloatingSidebarShellProps = {
|
|
children: ReactNode;
|
|
className?: string;
|
|
flowBoardControl?: boolean;
|
|
};
|
|
|
|
export function FloatingSidebarShell({
|
|
children,
|
|
className,
|
|
flowBoardControl = false,
|
|
}: FloatingSidebarShellProps) {
|
|
return (
|
|
<aside
|
|
className={cn(
|
|
"pointer-events-none flex flex-col items-center gap-2",
|
|
className
|
|
)}
|
|
data-flow-board-shell-control={flowBoardControl ? "true" : undefined}
|
|
>
|
|
<TooltipProvider delayDuration={120}>{children}</TooltipProvider>
|
|
</aside>
|
|
);
|
|
}
|
|
|
|
type FloatingLogoPillProps = {
|
|
siteName: string;
|
|
siteLogo?: string | null;
|
|
tooltip?: string | null;
|
|
onClick?: () => void;
|
|
variant?: "pill" | "plain";
|
|
};
|
|
|
|
export function FloatingLogoPill({
|
|
siteName,
|
|
siteLogo,
|
|
tooltip = siteName,
|
|
onClick,
|
|
variant = "pill",
|
|
}: FloatingLogoPillProps) {
|
|
const isPlain = variant === "plain";
|
|
const trigger = (
|
|
<button
|
|
aria-label={tooltip || siteName}
|
|
className={cn(
|
|
"pointer-events-auto flex items-center gap-2 text-left transition",
|
|
isPlain
|
|
? "h-8 hover:opacity-70"
|
|
: "h-[52px] w-[52px] justify-center rounded-full border border-black/10 bg-white px-2.5 shadow hover:bg-black/[0.03] hover:shadow-md dark:border-white/10 dark:bg-[#141416] dark:hover:bg-white/[0.04] lg:w-[164px] lg:justify-start lg:px-4"
|
|
)}
|
|
onClick={onClick}
|
|
type="button"
|
|
>
|
|
{siteLogo ? (
|
|
<img
|
|
alt={siteName}
|
|
className="h-8 w-8 shrink-0 rounded-full object-cover"
|
|
src={siteLogo}
|
|
/>
|
|
) : (
|
|
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-black font-bold text-white text-sm dark:bg-white dark:text-black">
|
|
{siteName.slice(0, 1).toUpperCase()}
|
|
</div>
|
|
)}
|
|
<span
|
|
className={cn(
|
|
"min-w-0 truncate font-semibold text-[#111827] leading-none tracking-tight dark:text-white",
|
|
isPlain ? "text-[20px]" : "hidden text-[15px] lg:block"
|
|
)}
|
|
>
|
|
{siteName}
|
|
</span>
|
|
</button>
|
|
);
|
|
|
|
if (!tooltip) {
|
|
return trigger;
|
|
}
|
|
|
|
return (
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>{trigger}</TooltipTrigger>
|
|
<TooltipContent side={isPlain ? "bottom" : "right"}>
|
|
{tooltip}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
);
|
|
}
|
|
|
|
type FloatingSidebarRailProps = {
|
|
children: ReactNode;
|
|
className?: string;
|
|
orientation?: "vertical" | "horizontal";
|
|
tone?: "dark" | "light";
|
|
};
|
|
|
|
export function FloatingSidebarRail({
|
|
children,
|
|
className,
|
|
orientation = "vertical",
|
|
tone = "dark",
|
|
}: FloatingSidebarRailProps) {
|
|
const isHorizontal = orientation === "horizontal";
|
|
const isLight = tone === "light";
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"pointer-events-auto flex items-center gap-1 backdrop-blur-2xl",
|
|
isLight
|
|
? "border border-black/8 bg-white/96 text-[#111827] shadow-[0_20px_46px_rgba(15,23,42,0.12)]"
|
|
: "border border-white/8 bg-[#17181d]/94 text-white shadow-[0_22px_60px_rgba(6,8,14,0.34),inset_0_1px_0_rgba(255,255,255,0.05)]",
|
|
isHorizontal
|
|
? "h-[56px] w-auto flex-row overflow-hidden rounded-[22px] px-3 py-2"
|
|
: "w-[56px] max-w-[56px] min-w-0 flex-col overflow-x-hidden overflow-y-auto overscroll-contain rounded-[24px] px-2 py-2 [overflow-x:clip] [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
|
className
|
|
)}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
type FloatingSidebarActionProps = {
|
|
label: string;
|
|
icon: LucideIcon;
|
|
active?: boolean;
|
|
disabled?: boolean;
|
|
loading?: boolean;
|
|
onClick?: () => void;
|
|
};
|
|
|
|
export function FloatingSidebarAction({
|
|
label,
|
|
icon: Icon,
|
|
active = false,
|
|
disabled = false,
|
|
loading = false,
|
|
onClick,
|
|
}: FloatingSidebarActionProps) {
|
|
return (
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<button
|
|
className={cn(
|
|
"flex h-10 w-10 shrink-0 items-center justify-center rounded-xl text-[#4f566b] transition-colors hover:bg-black/5 hover:text-[#111827] disabled:cursor-not-allowed disabled:opacity-45 dark:text-white/60 dark:hover:bg-white/8 dark:hover:text-white",
|
|
active &&
|
|
"bg-[#111827] text-white shadow-sm hover:bg-[#1f2937] hover:text-white"
|
|
)}
|
|
disabled={disabled || loading}
|
|
onMouseDown={(event) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}}
|
|
onPointerDown={(event) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}}
|
|
onClick={onClick}
|
|
type="button"
|
|
>
|
|
{loading ? (
|
|
<Loader2 className="h-[18px] w-[18px] animate-spin" />
|
|
) : (
|
|
<Icon className="h-[18px] w-[18px]" />
|
|
)}
|
|
</button>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="right">{label}</TooltipContent>
|
|
</Tooltip>
|
|
);
|
|
}
|