Revamp mobile UI (#10103)

* Simplify nav components

* Allow ability to choose live layout on mobile

* Combine event views

* Undo vite

* Fix autoplay

* Remove import

* Show filters on mobile view

* Spacing

* Don't separate properties
This commit is contained in:
Nicolas Mowen
2024-02-27 14:39:05 -07:00
committed by GitHub
parent 622e9741c0
commit fd24007618
12 changed files with 230 additions and 485 deletions

View File

@@ -1,63 +0,0 @@
import { Link } from "react-router-dom";
import Logo from "@/components/Logo";
import { LuMenu } from "react-icons/lu";
import { Button } from "@/components/ui/button";
import { ENV } from "@/env";
import { NavLink } from "react-router-dom";
import { navbarLinks } from "@/pages/site-navigation";
import SettingsNavItems from "./settings/SettingsNavItems";
type HeaderProps = {
onToggleNavbar: () => void;
};
function HeaderNavigation() {
return (
<div className="hidden md:flex">
{navbarLinks.map((item) => {
let shouldRender = item.dev ? ENV !== "production" : true;
return (
shouldRender && (
<NavLink
key={item.id}
to={item.url}
className={({ isActive }) =>
`my-2 py-3 px-4 text-muted-foreground flex flex-row items-center text-center rounded-lg gap-2 hover:bg-border ${
isActive ? "font-bold bg-popover text-popover-foreground" : ""
}`
}
>
<div className="text-sm">{item.title}</div>
</NavLink>
)
);
})}
</div>
);
}
function Header({ onToggleNavbar }: HeaderProps) {
return (
<div className="flex gap-10 lg:gap-20 justify-between pt-2 mb-2 border-b-[1px] px-4 items-center md:hidden">
<div className="flex gap-4 items-center flex-shrink-0 m-1">
<Button
variant="ghost"
size="icon"
className="md:hidden"
onClick={onToggleNavbar}
>
<LuMenu />
</Button>
<Link to="/">
<div className="flex flex-row items-center">
<Logo className="w-10 mr-5" />
</div>
</Link>
<HeaderNavigation />
</div>
<SettingsNavItems className="flex flex-shrink-0 md:gap-2" />
</div>
);
}
export default Header;

View File

@@ -1,95 +0,0 @@
import { IconType } from "react-icons";
import { NavLink } from "react-router-dom";
import { Sheet, SheetContent } from "@/components/ui/sheet";
import Logo from "./Logo";
import { ENV } from "@/env";
import { navbarLinks } from "@/pages/site-navigation";
import SettingsNavItems from "./settings/SettingsNavItems";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
function Sidebar({
sheetOpen,
setSheetOpen,
}: {
sheetOpen: boolean;
setSheetOpen: (open: boolean) => void;
}) {
const sidebar = (
<aside className="w-[52px] z-10 h-screen sticky top-0 overflow-y-auto scrollbar-hidden py-4 flex flex-col justify-between">
<span tabIndex={0} className="sr-only" />
<div className="w-full flex flex-col gap-0 items-center">
<Logo className="w-8 h-8 mb-6" />
{navbarLinks.map((item) => (
<SidebarItem
key={item.id}
Icon={item.icon}
title={item.title}
url={item.url}
dev={item.dev}
onClick={() => setSheetOpen(false)}
/>
))}
</div>
<SettingsNavItems className="hidden md:flex flex-col items-center mb-8" />
</aside>
);
return (
<>
<div className="hidden md:block">{sidebar}</div>
<Sheet
open={sheetOpen}
modal={false}
onOpenChange={() => setSheetOpen(false)}
>
<SheetContent side="left" className="w-[90px]">
<div className="w-full flex flex-row justify-center"></div>
{sidebar}
</SheetContent>
</Sheet>
</>
);
}
type SidebarItemProps = {
Icon: IconType;
title: string;
url: string;
dev?: boolean;
onClick?: () => void;
};
function SidebarItem({ Icon, title, url, dev, onClick }: SidebarItemProps) {
const shouldRender = dev ? ENV !== "production" : true;
return (
shouldRender && (
<Tooltip>
<NavLink
to={url}
onClick={onClick}
className={({ isActive }) =>
`mx-[10px] mb-6 flex flex-col justify-center items-center rounded-lg ${
isActive
? "font-bold text-primary-foreground bg-primary"
: "text-muted-foreground bg-muted"
}`
}
>
<TooltipTrigger>
<Icon className="w-5 h-5 m-[6px]" />
</TooltipTrigger>
</NavLink>
<TooltipContent side="right">
<p>{title}</p>
</TooltipContent>
</Tooltip>
)
);
}
export default Sidebar;

View File

@@ -15,6 +15,7 @@ import { Calendar } from "../ui/calendar";
import { ReviewFilter } from "@/types/review";
import { getEndOfDayTimestamp } from "@/utils/dateUtil";
import { useFormattedTimestamp } from "@/hooks/use-date-utils";
import { isMobile } from "react-device-detect";
const ATTRIBUTES = ["amazon", "face", "fedex", "license_plate", "ups"];
@@ -122,11 +123,17 @@ function CamerasFilterButton({
}}
>
<DropdownMenuTrigger asChild>
<Button className="mx-1 capitalize" variant="secondary">
<LuVideo className=" mr-[10px]" />
{selectedCameras == undefined
? "All Cameras"
: `${selectedCameras.length} Cameras`}
<Button
size={isMobile ? "sm" : "default"}
className="mx-1 capitalize"
variant="secondary"
>
<LuVideo className="md:mr-[10px]" />
<div className="hidden md:block">
{selectedCameras == undefined
? "All Cameras"
: `${selectedCameras.length} Cameras`}
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
@@ -205,9 +212,15 @@ function CalendarFilterButton({
}}
>
<PopoverTrigger asChild>
<Button className="mx-1" variant="secondary">
<LuCalendar className=" mr-[10px]" />
{day == undefined ? "Last 24 Hours" : selectedDate}
<Button
size={isMobile ? "sm" : "default"}
className="mx-1"
variant="secondary"
>
<LuCalendar className="md:mr-[10px]" />
<div className="hidden md:block">
{day == undefined ? "Last 24 Hours" : selectedDate}
</div>
</Button>
</PopoverTrigger>
<PopoverContent>
@@ -241,9 +254,13 @@ function GeneralFilterButton({
return (
<Popover>
<PopoverTrigger asChild>
<Button className="mx-1" variant="secondary">
<LuFilter className=" mr-[10px]" />
Filter
<Button
size={isMobile ? "sm" : "default"}
className="mx-1"
variant="secondary"
>
<LuFilter className="md:mr-[10px]" />
<div className="hidden md:block">Filter</div>
</Button>
</PopoverTrigger>
<PopoverContent side="left" asChild>

View File

@@ -0,0 +1,26 @@
import { navbarLinks } from "@/pages/site-navigation";
import NavItem from "./NavItem";
import SettingsNavItems from "../settings/SettingsNavItems";
function Bottombar() {
return (
<div className="absolute h-16 left-4 bottom-0 right-4 flex flex-row items-center justify-between">
{navbarLinks.map((item) => (
<NavItem
className=""
variant="secondary"
key={item.id}
Icon={item.icon}
title={item.title}
url={item.url}
dev={item.dev}
/>
))}
<SettingsNavItems className="flex flex-shrink-0 justify-between gap-4" />
</div>
);
}
//
export default Bottombar;

View File

@@ -0,0 +1,64 @@
import { IconType } from "react-icons";
import { NavLink } from "react-router-dom";
import { ENV } from "@/env";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
const variants = {
primary: {
active: "font-bold text-primary-foreground bg-primary",
inactive: "text-muted-foreground bg-muted",
},
secondary: {
active: "font-bold text-primary",
inactive: "text-muted-foreground",
},
};
type NavItemProps = {
className: string;
variant?: "primary" | "secondary";
Icon: IconType;
title: string;
url: string;
dev?: boolean;
onClick?: () => void;
};
export default function NavItem({
className,
variant = "primary",
Icon,
title,
url,
dev,
onClick,
}: NavItemProps) {
const shouldRender = dev ? ENV !== "production" : true;
return (
shouldRender && (
<Tooltip>
<NavLink
to={url}
onClick={onClick}
className={({ isActive }) =>
`${className} flex flex-col justify-center items-center rounded-lg ${
variants[variant][isActive ? "active" : "inactive"]
}`
}
>
<TooltipTrigger>
<Icon className="w-5 h-5 m-[6px]" />
</TooltipTrigger>
</NavLink>
<TooltipContent side="right">
<p>{title}</p>
</TooltipContent>
</Tooltip>
)
);
}

View File

@@ -0,0 +1,28 @@
import Logo from "../Logo";
import { navbarLinks } from "@/pages/site-navigation";
import SettingsNavItems from "../settings/SettingsNavItems";
import NavItem from "./NavItem";
function Sidebar() {
return (
<aside className="w-[52px] z-10 h-screen sticky top-0 overflow-y-auto scrollbar-hidden py-4 flex flex-col justify-between">
<span tabIndex={0} className="sr-only" />
<div className="w-full flex flex-col gap-0 items-center">
<Logo className="w-8 h-8 mb-6" />
{navbarLinks.map((item) => (
<NavItem
className="mx-[10px] mb-6"
key={item.id}
Icon={item.icon}
title={item.title}
url={item.url}
dev={item.dev}
/>
))}
</div>
<SettingsNavItems className="hidden md:flex flex-col items-center mb-8" />
</aside>
);
}
export default Sidebar;

View File

@@ -12,6 +12,7 @@ import useCameraActivity from "@/hooks/use-camera-activity";
import { useRecordingsState } from "@/api/ws";
import { LivePlayerMode } from "@/types/live";
import useCameraLiveMode from "@/hooks/use-camera-live-mode";
import { isDesktop } from "react-device-detect";
type LivePlayerProps = {
className?: string;
@@ -153,7 +154,7 @@ export default function LivePlayer({
className={`bg-gradient-to-br from-gray-400 to-gray-500 bg-gray-500`}
>
<MdLeakAdd className="w-4 h-4 text-motion" />
<div className="ml-1 text-white text-xs">Motion</div>
<div className="hidden md:block ml-1 text-white text-xs">Motion</div>
</Chip>
{cameraConfig.audio.enabled_in_config && (
@@ -162,19 +163,21 @@ export default function LivePlayer({
className={`bg-gradient-to-br from-gray-400 to-gray-500 bg-gray-500`}
>
<BsSoundwave className="w-4 h-4 text-audio" />
<div className="ml-1 text-white text-xs">Sound</div>
<div className="hidden md:block ml-1 text-white text-xs">Sound</div>
</Chip>
)}
</div>
<Chip className="absolute right-2 top-2 bg-gradient-to-br from-gray-400 to-gray-500 bg-gray-500">
{recording == "ON" && (
<MdCircle className="w-2 h-2 drop-shadow-md shadow-danger text-danger" />
)}
<div className="ml-1 capitalize text-white text-xs">
{cameraConfig.name.replaceAll("_", " ")}
</div>
</Chip>
{isDesktop && (
<Chip className="absolute right-2 top-2 bg-gradient-to-br from-gray-400 to-gray-500 bg-gray-500">
{recording == "ON" && (
<MdCircle className="w-2 h-2 drop-shadow-md shadow-danger text-danger" />
)}
<div className="ml-1 capitalize text-white text-xs">
{cameraConfig.name.replaceAll("_", " ")}
</div>
</Chip>
)}
</div>
);
}