Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 125 additions & 108 deletions src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,26 +46,34 @@ const CollapseToggleButton = () => {
aria-label={buttonLabel}
onClick={toggleSidebar}
className={cn(
'absolute top-20 right-0 z-10 p-0.5 rounded-full transform translate-x-1/2',
'transition-colors duration-300 ease-in-out',
'border border-slate-300 dark:border-slate-200',
'text-slate-500 dark:text-slate-200 hover:text-slate-400 dark:hover:text-slate-400',
'focus:outline-none focus:ring-2 focus:ring-accent transition-transform'
"absolute top-20 right-0 z-10 p-0.5 rounded-full transform translate-x-1/2",
"transition-colors duration-300 ease-in-out",
"border border-slate-300 dark:border-slate-200",
"text-slate-500 dark:text-slate-200 hover:text-slate-400 dark:hover:text-slate-400",
"focus:outline-none focus:ring-2 focus:ring-accent transition-transform bg-background-primary",
)}
>
<CircleChevronLeft
size={24}
className={cn(
'transition-transform duration-300 ease-in-out',
isCollapsed && 'rotate-180'
"transition-transform duration-300 ease-in-out",
isCollapsed && "rotate-180",
)}
/>
</button>
);
}
};

export const Sidebar = ({ children }: SidebarProps) => {
const { hardware, getNode, getNodesLength, metadata, activePage, setActivePage, setDialogOpen } = useDevice();
const {
hardware,
getNode,
getNodesLength,
metadata,
activePage,
setActivePage,
setDialogOpen,
} = useDevice();
const { setCommandPaletteOpen } = useAppStore();
const myNode = getNode(hardware.myNodeNum);
const { isCollapsed } = useSidebar();
Expand All @@ -86,19 +94,18 @@ export const Sidebar = ({ children }: SidebarProps) => {
return (
<div
className={cn(
'relative border-slate-300 dark:border-slate-700',
'transition-all duration-300 ease-in-out flex-shrink-0',
isCollapsed ? 'w-24' : 'w-46 lg:w-64'
"relative border-slate-300 dark:border-slate-700",
"transition-all duration-300 ease-in-out flex-shrink-0",
isCollapsed ? "w-24" : "w-46 lg:w-64",
)}
>
<CollapseToggleButton />

<div
className={cn(
'h-14 flex mt-2 gap-2 items-center flex-shrink-0 transition-all duration-300 ease-in-out',
'border-b-[0.5px] border-slate-300 dark:border-slate-700',
isCollapsed && 'justify-center px-0'

"h-14 flex mt-2 gap-2 items-center flex-shrink-0 transition-all duration-300 ease-in-out",
"border-b-[0.5px] border-slate-300 dark:border-slate-700",
isCollapsed && "justify-center px-0",
)}
>
<img
Expand All @@ -108,11 +115,11 @@ export const Sidebar = ({ children }: SidebarProps) => {
/>
<h2
className={cn(
'text-xl font-semibold text-gray-800 dark:text-gray-100 whitespace-nowrap',
'transition-all duration-300 ease-in-out',
"text-xl font-semibold text-gray-800 dark:text-gray-100 whitespace-nowrap",
"transition-all duration-300 ease-in-out",
isCollapsed
? 'opacity-0 max-w-0 invisible ml-0'
: 'opacity-100 max-w-xs visible ml-2'
? "opacity-0 max-w-0 invisible ml-0"
: "opacity-100 max-w-xs visible ml-2",
)}
>
Meshtastic
Expand All @@ -136,106 +143,116 @@ export const Sidebar = ({ children }: SidebarProps) => {
))}
</SidebarSection>

<div className={cn(
'flex-1 min-h-0',
isCollapsed && 'overflow-hidden'
)}
<div
className={cn(
"flex-1 min-h-0",
isCollapsed && "overflow-hidden",
)}
>
{children}
</div>

<div className="pt-4 border-t-[0.5px] bg-background-primary border-slate-300 dark:border-slate-700 flex-shrink-0">
{myNode === undefined ? (
<div className="flex flex-col items-center justify-center py-6">
<Spinner />
<Subtle
className={cn(
'mt-4 transition-opacity duration-300',
isCollapsed ? 'opacity-0 invisible' : 'opacity-100 visible'
)}
>
Loading...
</Subtle>
</div>
) : (
<>
<div
className={cn(
'flex place-items-center gap-2',
isCollapsed && 'justify-center'
)}
>
<Avatar
text={myNode.user?.shortName ?? myNode.num.toString()}
className={cn("flex-shrink-0 ml-2",
isCollapsed && "ml-0",
)}
size="sm"
/>
<p
{myNode === undefined
? (
<div className="flex flex-col items-center justify-center py-6">
<Spinner />
<Subtle
className={cn(
'max-w-[20ch] text-wrap text-sm font-medium',
'transition-all duration-300 ease-in-out overflow-hidden',
isCollapsed
? 'opacity-0 max-w-0 invisible'
: 'opacity-100 max-w-full visible'
"mt-4 transition-opacity duration-300",
isCollapsed ? "opacity-0 invisible" : "opacity-100 visible",
)}
>
{myNode.user?.longName}
</p>
Loading...
</Subtle>
</div>

<div
className={cn(
'flex flex-col gap-0.5 ml-2 mt-2',
'transition-all duration-300 ease-in-out',
isCollapsed
? 'opacity-0 max-w-0 h-0 invisible'
: 'opacity-100 max-w-xs h-auto visible'
)}
>
<div className="inline-flex gap-2">
<BatteryStatus deviceMetrics={myNode.deviceMetrics} />
</div>
<div className="inline-flex gap-2">
<ZapIcon size={18} className="text-gray-500 dark:text-gray-400 w-4 flex-shrink-0" />
<Subtle>{myNode.deviceMetrics?.voltage?.toPrecision(3) ?? "UNK"} volts</Subtle>
</div>
<div className="inline-flex gap-2">
<CpuIcon size={18} className="text-gray-500 dark:text-gray-400 w-4 flex-shrink-0" />
<Subtle>v{myMetadata?.firmwareVersion ?? "UNK"}</Subtle>
)
: (
<>
<div
className={cn(
"flex place-items-center gap-2",
isCollapsed && "justify-center",
)}
>
<Avatar
text={myNode.user?.shortName ?? myNode.num.toString()}
className={cn("flex-shrink-0 ml-2", isCollapsed && "ml-0")}
size="sm"
/>
<p
className={cn(
"max-w-[20ch] text-wrap text-sm font-medium",
"transition-all duration-300 ease-in-out overflow-hidden",
isCollapsed
? "opacity-0 max-w-0 invisible"
: "opacity-100 max-w-full visible",
)}
>
{myNode.user?.longName}
</p>
</div>
</div>
<div
className={cn(
'flex items-center flex-shrink-0 ml-2',
'transition-all duration-300 ease-in-out',
isCollapsed
? 'opacity-0 max-w-0 invisible pointer-events-none'
: 'opacity-100 max-w-xs visible'
)}
>
<button
type="button"
aria-label="Edit device name"
className="p-1 rounded transition-colors hover:text-accent"
onClick={() => setDialogOpen("deviceName", true)}

<div
className={cn(
"flex flex-col gap-0.5 ml-2 mt-2",
"transition-all duration-300 ease-in-out",
isCollapsed
? "opacity-0 max-w-0 h-0 invisible"
: "opacity-100 max-w-xs h-auto visible",
)}
>
<PenLine size={22} />
</button>
<ThemeSwitcher />
<button
type="button"
className="transition-all hover:text-accent"
onClick={() => setCommandPaletteOpen(true)}
<div className="inline-flex gap-2">
<BatteryStatus deviceMetrics={myNode.deviceMetrics} />
</div>
<div className="inline-flex gap-2">
<ZapIcon
size={18}
className="text-gray-500 dark:text-gray-400 w-4 flex-shrink-0"
/>
<Subtle>
{myNode.deviceMetrics?.voltage?.toPrecision(3) ?? "UNK"}
{" "}
volts
</Subtle>
</div>
<div className="inline-flex gap-2">
<CpuIcon
size={18}
className="text-gray-500 dark:text-gray-400 w-4 flex-shrink-0"
/>
<Subtle>v{myMetadata?.firmwareVersion ?? "UNK"}</Subtle>
</div>
</div>
<div
className={cn(
"flex items-center flex-shrink-0 ml-2",
"transition-all duration-300 ease-in-out",
isCollapsed
? "opacity-0 max-w-0 invisible pointer-events-none"
: "opacity-100 max-w-xs visible",
)}
>
<SearchIcon />
</button>
</div>

</>
)}
<button
type="button"
aria-label="Edit device name"
className="p-1 rounded transition-colors hover:text-accent"
onClick={() => setDialogOpen("deviceName", true)}
>
<PenLine size={22} />
</button>
<ThemeSwitcher />
<button
type="button"
className="transition-all hover:text-accent"
onClick={() => setCommandPaletteOpen(true)}
>
<SearchIcon />
</button>
</div>
</>
)}
</div>
</div>
);
};
};
14 changes: 8 additions & 6 deletions src/components/UI/Checkbox/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect, useId } from "react";
import { useEffect, useId, useState } from "react";
import { Check } from "lucide-react";
import { Label } from "@components/UI/Label.tsx";
import { cn } from "@core/utils/cn.ts";
Expand Down Expand Up @@ -65,13 +65,15 @@ export function Checkbox({
role="presentation"
className={cn(
"w-6 h-6 border-2 border-gray-500 rounded-md flex items-center justify-center",
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
isChecked ? "" : ""
disabled
? "opacity-50 cursor-not-allowed"
: "cursor-pointer focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
isChecked ? "" : "",
)}
>
{isChecked && (
<div className="animate-fade-in scale-100 opacity-100">
<Check className="w-4 h-4 text-slate-900 dark:text-slate-200" />
<Check className="w-4 h-4" />
</div>
)}
</div>
Expand All @@ -85,7 +87,7 @@ export function Checkbox({
className={cn(
"text-gray-900 dark:text-gray-900",
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
labelClassName
labelClassName,
)}
>
{children}
Expand All @@ -95,4 +97,4 @@ export function Checkbox({
</div>
</div>
);
}
}
38 changes: 21 additions & 17 deletions src/components/UI/Sidebar/SidebarSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,31 @@ export const SidebarSection = ({
}: SidebarSectionProps) => {
const { isCollapsed } = useSidebar();
return (
<div className={cn(
"py-2",
isCollapsed ? 'px-0' : 'px-4',
className,
)}>

<Heading as="h3" className={cn(
'mb-2',
'uppercase tracking-wider text-md',
'transition-all duration-300 ease-in-out',
'whitespace-nowrap overflow-hidden',
isCollapsed
? 'opacity-0 max-w-0 h-0 invisible px-0 mb-0'
: 'opacity-100 max-w-xs h-auto visible px-1 mb-1'
)}>
<div
className={cn(
"py-2",
isCollapsed ? "px-0" : "px-4",
className,
)}
>
<Heading
as="h3"
className={cn(
"mb-2",
"uppercase tracking-wider text-md",
"transition-all duration-300 ease-in-out",
"whitespace-nowrap overflow-hidden",
isCollapsed
? "opacity-0 max-w-0 h-0 invisible px-0 mb-0"
: "opacity-100 max-w-xs h-auto visible px-1 mb-1",
)}
>
{label}
</Heading>

<div className="space-y-0.5">
<div className="space-y-1">
{children}
</div>
</div>
);
};
};