Add tooltips to camera group items on desktop

This commit is contained in:
Nicolas Mowen 2024-03-03 21:51:20 -07:00
parent 6e3e1f6314
commit c68e770c55
3 changed files with 110 additions and 43 deletions

View File

@ -6,11 +6,37 @@ import { FaCar, FaCat, FaCircle, FaDog, FaLeaf } from "react-icons/fa";
import useOverlayState from "@/hooks/use-overlay-state"; import useOverlayState from "@/hooks/use-overlay-state";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useMemo } from "react"; import { useCallback, useMemo, useState } from "react";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
export function CameraGroupSelector() { type CameraGroupSelectorProps = {
className?: string;
};
export function CameraGroupSelector({ className }: CameraGroupSelectorProps) {
const { data: config } = useSWR<FrigateConfig>("config"); const { data: config } = useSWR<FrigateConfig>("config");
const navigate = useNavigate(); const navigate = useNavigate();
// tooltip
const [tooltip, setTooltip] = useState<string>();
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
const showTooltip = useCallback(
(newTooltip: string | undefined) => {
if (!newTooltip) {
setTooltip(newTooltip);
if (timeoutId) {
clearTimeout(timeoutId);
}
} else {
setTimeoutId(setTimeout(() => setTooltip(newTooltip), 500));
}
},
[timeoutId],
);
// groups
const [group, setGroup] = useOverlayState("cameraGroup"); const [group, setGroup] = useOverlayState("cameraGroup");
const groups = useMemo(() => { const groups = useMemo(() => {
@ -25,8 +51,10 @@ export function CameraGroupSelector() {
return ( return (
<div <div
className={`flex items-center justify-start gap-2 ${isDesktop ? "flex-col mb-4" : ""}`} className={`flex items-center justify-start gap-2 ${className ?? ""} ${isDesktop ? "flex-col" : ""}`}
> >
<Tooltip open={tooltip == "home"}>
<TooltipTrigger asChild>
<Button <Button
className={ className={
group == undefined group == undefined
@ -35,13 +63,21 @@ export function CameraGroupSelector() {
} }
size="xs" size="xs"
onClick={() => navigate(-1)} onClick={() => navigate(-1)}
onMouseEnter={() => (isDesktop ? showTooltip("home") : null)}
onMouseLeave={() => (isDesktop ? showTooltip(undefined) : null)}
> >
<MdHome className="size-4" /> <MdHome className="size-4" />
</Button> </Button>
</TooltipTrigger>
<TooltipContent className="capitalize" side="right">
Home
</TooltipContent>
</Tooltip>
{groups.map(([name, config]) => { {groups.map(([name, config]) => {
return ( return (
<Tooltip key={name} open={tooltip == name}>
<TooltipTrigger asChild>
<Button <Button
key={name}
className={ className={
group == name group == name
? "text-selected bg-blue-900 focus:bg-blue-900 bg-opacity-60 focus:bg-opacity-60" ? "text-selected bg-blue-900 focus:bg-blue-900 bg-opacity-60 focus:bg-opacity-60"
@ -49,9 +85,16 @@ export function CameraGroupSelector() {
} }
size="xs" size="xs"
onClick={() => setGroup(name, group != undefined)} onClick={() => setGroup(name, group != undefined)}
onMouseEnter={() => (isDesktop ? showTooltip(name) : null)}
onMouseLeave={() => (isDesktop ? showTooltip(undefined) : null)}
> >
{getGroupIcon(config.icon)} {getGroupIcon(config.icon)}
</Button> </Button>
</TooltipTrigger>
<TooltipContent className="capitalize" side="right">
{name}
</TooltipContent>
</Tooltip>
); );
})} })}
</div> </div>

View File

@ -6,9 +6,10 @@ import {
TooltipContent, TooltipContent,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { useState } from "react"; import { useCallback, useState } from "react";
import { isDesktop } from "react-device-detect"; import { isDesktop } from "react-device-detect";
import { TooltipPortal } from "@radix-ui/react-tooltip"; import { TooltipPortal } from "@radix-ui/react-tooltip";
import { CameraGroupSelector } from "../filter/CameraGroupSelector";
const variants = { const variants = {
primary: { primary: {
@ -43,6 +44,21 @@ export default function NavItem({
const shouldRender = dev ? ENV !== "production" : true; const shouldRender = dev ? ENV !== "production" : true;
const [showTooltip, setShowTooltip] = useState(false); const [showTooltip, setShowTooltip] = useState(false);
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout>();
const showTooltipTimer = useCallback(
(showTooltip: boolean) => {
if (!showTooltip) {
setShowTooltip(showTooltip);
if (timeoutId) {
clearTimeout(timeoutId);
}
} else {
setTimeoutId(setTimeout(() => setShowTooltip(showTooltip), 500));
}
},
[timeoutId],
);
return ( return (
shouldRender && ( shouldRender && (
@ -50,17 +66,28 @@ export default function NavItem({
<NavLink <NavLink
to={url} to={url}
onClick={onClick} onClick={onClick}
className={({ isActive }) => className={`${className} flex flex-col justify-center items-center rounded-lg`}
`${className} flex flex-col justify-center items-center rounded-lg ${
variants[variant][isActive ? "active" : "inactive"]
}`
}
onMouseEnter={() => (isDesktop ? setShowTooltip(true) : null)}
onMouseLeave={() => (isDesktop ? setShowTooltip(false) : null)}
> >
<TooltipTrigger> {({ isActive }) => (
<Icon className="size-5 md:m-[6px]" /> <>
<TooltipTrigger
className={`rounded-lg ${variants[variant][isActive ? "active" : "inactive"]}`}
>
<Icon
className="size-5 md:m-[6px]"
onMouseEnter={() =>
isDesktop ? showTooltipTimer(true) : null
}
onMouseLeave={() =>
isDesktop ? showTooltipTimer(false) : null
}
/>
</TooltipTrigger> </TooltipTrigger>
{isDesktop && title == "Live" && isActive && (
<CameraGroupSelector className="mt-2" />
)}
</>
)}
</NavLink> </NavLink>
<TooltipPortal> <TooltipPortal>
<TooltipContent side="right"> <TooltipContent side="right">

View File

@ -2,7 +2,6 @@ import Logo from "../Logo";
import { navbarLinks } from "@/pages/site-navigation"; import { navbarLinks } from "@/pages/site-navigation";
import SettingsNavItems from "../settings/SettingsNavItems"; import SettingsNavItems from "../settings/SettingsNavItems";
import NavItem from "./NavItem"; import NavItem from "./NavItem";
import { CameraGroupSelector } from "../filter/CameraGroupSelector";
function Sidebar() { function Sidebar() {
return ( return (
@ -11,16 +10,14 @@ function Sidebar() {
<div className="w-full flex flex-col gap-0 items-center"> <div className="w-full flex flex-col gap-0 items-center">
<Logo className="w-8 h-8 mb-6" /> <Logo className="w-8 h-8 mb-6" />
{navbarLinks.map((item) => ( {navbarLinks.map((item) => (
<div key={item.id}>
<NavItem <NavItem
className={`mx-[10px] ${item.id == 1 ? "mb-2" : "mb-6"}`} className="mx-[10px] mb-6"
key={item.id}
Icon={item.icon} Icon={item.icon}
title={item.title} title={item.title}
url={item.url} url={item.url}
dev={item.dev} dev={item.dev}
/> />
{item.id == 1 && <CameraGroupSelector />}
</div>
))} ))}
</div> </div>
<SettingsNavItems className="hidden md:flex flex-col items-center mb-8" /> <SettingsNavItems className="hidden md:flex flex-col items-center mb-8" />