|
|
|
@ -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(); |
|
|
|
@ -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 |
|
|
|
@ -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 |
|
|
|
@ -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> |
|
|
|
); |
|
|
|
}; |
|
|
|
}; |
|
|
|
|