diff --git a/packages/web/components.json b/packages/web/components.json
new file mode 100644
index 00000000..8e3c1f8a
--- /dev/null
+++ b/packages/web/components.json
@@ -0,0 +1,22 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": false,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "src/index.css",
+ "baseColor": "slate",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "iconLibrary": "lucide",
+ "aliases": {
+ "components": "@app/components",
+ "utils": "@app/lib/utils",
+ "ui": "@app/components/ui",
+ "lib": "@app/lib",
+ "hooks": "@app/hooks"
+ },
+ "registries": {}
+}
diff --git a/packages/web/package.json b/packages/web/package.json
index 7d082b11..0150238b 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -45,6 +45,7 @@
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slider": "^1.3.5",
+ "@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-toast": "^1.2.14",
@@ -108,6 +109,7 @@
"tailwindcss-animate": "^1.0.7",
"tar": "^7.4.3",
"testing-library": "^0.0.2",
+ "tw-animate-css": "^1.3.8",
"typescript": "^5.8.3",
"vitest": "^3.2.4"
}
diff --git a/packages/web/public/i18n/locales/en/ui.json b/packages/web/public/i18n/locales/en/ui.json
index 694e8982..394317a1 100644
--- a/packages/web/public/i18n/locales/en/ui.json
+++ b/packages/web/public/i18n/locales/en/ui.json
@@ -22,6 +22,13 @@
}
},
"deviceInfo": {
+ "connectionStatus": {
+ "title": "Connection Status",
+ "connected": "Connected",
+ "disconnected": "Disconnected",
+ "connecting": "Connecting...",
+ "error": "Connection Error"
+ },
"volts": "{{voltage}} volts",
"firmware": {
"title": "Firmware",
diff --git a/packages/web/src/App.tsx b/packages/web/src/App.tsx
index d0eb9ebe..800dd3a7 100644
--- a/packages/web/src/App.tsx
+++ b/packages/web/src/App.tsx
@@ -6,13 +6,13 @@ import { KeyBackupReminder } from "@components/KeyBackupReminder.tsx";
import { Toaster } from "@components/Toaster.tsx";
import { ErrorPage } from "@components/UI/ErrorPage.tsx";
import Footer from "@components/UI/Footer.tsx";
-import { useTheme } from "@core/hooks/useTheme.ts";
import { SidebarProvider, useAppStore, useDeviceStore } from "@core/stores";
import { Dashboard } from "@pages/Dashboard/index.tsx";
import { Outlet } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import { ErrorBoundary } from "react-error-boundary";
import { MapProvider } from "react-map-gl/maplibre";
+import { ThemeProvider } from "./components/theme-provider.tsx";
export function App() {
const { getDevice } = useDeviceStore();
@@ -21,45 +21,44 @@ export function App() {
const device = getDevice(selectedDeviceId);
- // Sets up light/dark mode based on user preferences or system settings
- useTheme();
-
return (
-
- {
- setConnectDialogOpen(open);
- }}
- />
-
-
-
-
-
-
- {device ? (
-
-
-
-
-
-
-
-
- ) : (
- <>
-
-
- >
- )}
-
-
-
-
-
+
+
+ {
+ setConnectDialogOpen(open);
+ }}
+ />
+
+
+
+
+
+
+ {device ? (
+
+
+
+
+
+
+
+
+ ) : (
+ <>
+
+
+ >
+ )}
+
+
+
+
+
+
);
}
diff --git a/packages/web/src/components/ConnectionStatus.tsx b/packages/web/src/components/ConnectionStatus.tsx
new file mode 100644
index 00000000..0bcfbaa3
--- /dev/null
+++ b/packages/web/src/components/ConnectionStatus.tsx
@@ -0,0 +1,8 @@
+import { useDevice } from "@app/core/stores";
+
+export function ConnectionStatus() {
+ const { status } = useDevice();
+ console.log(status);
+
+ return
Connection Status Component
;
+}
diff --git a/packages/web/src/components/DeviceInfoPanel.tsx b/packages/web/src/components/DeviceInfoPanel.tsx
index 6480cb39..03558e62 100644
--- a/packages/web/src/components/DeviceInfoPanel.tsx
+++ b/packages/web/src/components/DeviceInfoPanel.tsx
@@ -1,5 +1,6 @@
import { cn } from "@core/utils/cn.ts";
import {
+ CableIcon,
CpuIcon,
Languages,
type LucideIcon,
@@ -8,17 +9,25 @@ import {
Search as SearchIcon,
ZapIcon,
} from "lucide-react";
+import type { DeviceStatusEnum } from "node_modules/@meshtastic/core/src/types";
import type React from "react";
import { Fragment } from "react";
import { useTranslation } from "react-i18next";
import BatteryStatus from "./BatteryStatus.tsx";
import LanguageSwitcher from "./LanguageSwitcher.tsx";
import ThemeSwitcher from "./ThemeSwitcher.tsx";
-import type { DeviceMetrics } from "./types.ts";
+import { ThemeModeToggle } from "./theme-mode-toggle.tsx";
import { Avatar } from "./UI/Avatar.tsx";
import { Button } from "./UI/Button.tsx";
+import { Separator } from "./UI/Separator.tsx";
import { Subtle } from "./UI/Typography/Subtle.tsx";
+export type DeviceMetrics = {
+ connectionStatus: DeviceStatusEnum;
+ batteryLevel?: number | null;
+ voltage?: number | null;
+};
+
interface DeviceInfoPanelProps {
isCollapsed: boolean;
deviceMetrics: DeviceMetrics;
@@ -61,6 +70,12 @@ export const DeviceInfoPanel = ({
const { batteryLevel, voltage } = deviceMetrics;
const deviceInfoItems: InfoDisplayItem[] = [
+ {
+ id: "deviceConnectionStatus",
+ label: t("sidebar.deviceInfo.connectionStatus.title"),
+ icon: CableIcon,
+ value: deviceMetrics.connectionStatus || t("unknown.notAvailable", "N/A"),
+ },
{
id: "battery",
label: t("batteryStatus.title"),
@@ -89,7 +104,7 @@ export const DeviceInfoPanel = ({
id: "theme",
label: t("theme.changeTheme"),
icon: Palette,
- render: () => ,
+ render: () => ,
},
{
id: "changeName",
@@ -137,9 +152,7 @@ export const DeviceInfoPanel = ({
)}
- {!isCollapsed && (
-
- )}
+ {!isCollapsed && }
{IconComponent && (
-
+
)}
{item.customComponent}
{item.id !== "battery" && (
-
+
{item.label}: {item.value}
)}
@@ -171,9 +181,7 @@ export const DeviceInfoPanel = ({
})}
- {!isCollapsed && (
-
- )}
+ {!isCollapsed && }
{buttonItem.label}
diff --git a/packages/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx b/packages/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx
index e2eaa0a7..af5dacc5 100644
--- a/packages/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx
+++ b/packages/web/src/components/Dialog/NodeDetailsDialog/NodeDetailsDialog.tsx
@@ -1,3 +1,10 @@
+import {
+ Tooltip,
+ TooltipArrow,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@app/components/UI/tooltip";
import { DeviceImage } from "@components/generic/DeviceImage.tsx";
import { TimeAgo } from "@components/generic/TimeAgo.tsx";
import { Uptime } from "@components/generic/Uptime.tsx";
@@ -17,13 +24,6 @@ import {
DialogTitle,
} from "@components/UI/Dialog.tsx";
import { Separator } from "@components/UI/Separator.tsx";
-import {
- Tooltip,
- TooltipArrow,
- TooltipContent,
- TooltipProvider,
- TooltipTrigger,
-} from "@components/UI/Tooltip.tsx";
import { useFavoriteNode } from "@core/hooks/useFavoriteNode.ts";
import { useIgnoreNode } from "@core/hooks/useIgnoreNode.ts";
import { toast } from "@core/hooks/useToast.ts";
diff --git a/packages/web/src/components/Map.tsx b/packages/web/src/components/Map.tsx
index c0ced881..2d1f8d6e 100644
--- a/packages/web/src/components/Map.tsx
+++ b/packages/web/src/components/Map.tsx
@@ -1,9 +1,6 @@
-import { useTheme } from "@core/hooks/useTheme.ts";
-import { useEffect, useMemo, useRef } from "react";
-import { useTranslation } from "react-i18next";
+import { useEffect, useRef } from "react";
import MapGl, {
AttributionControl,
- type MapLayerMouseEvent,
type MapRef,
NavigationControl,
ScaleControl,
@@ -12,22 +9,9 @@ import MapGl, {
interface MapProps {
children?: React.ReactNode;
onLoad?: (map: MapRef) => void;
- onMouseMove?: (event: MapLayerMouseEvent) => void;
- onClick?: (event: MapLayerMouseEvent) => void;
- interactiveLayerIds?: string[];
}
-export const BaseMap = ({
- children,
- onLoad,
- onClick,
- onMouseMove,
- interactiveLayerIds,
-}: MapProps) => {
- const { theme } = useTheme();
- const { t } = useTranslation("map");
-
- const darkMode = theme === "dark";
+export const MeshMap = ({ children, onLoad }: MapProps) => {
const mapRef = useRef
(null);
useEffect(() => {
@@ -37,27 +21,6 @@ export const BaseMap = ({
}
}, [onLoad]);
- const locale = useMemo(() => {
- return {
- "GeolocateControl.FindMyLocation": t(
- "maplibre.GeolocateControl.FindMyLocation",
- ),
- "NavigationControl.ZoomIn": t("maplibre.NavigationControl.ZoomIn"),
- "NavigationControl.ZoomOut": t("maplibre.NavigationControl.ZoomOut"),
- "ScaleControl.Meters": t("unit.meter.suffix"),
- "ScaleControl.Kilometers": t("unit.kilometer.suffix"),
- "CooperativeGesturesHandler.WindowsHelpText": t(
- "maplibre.CooperativeGesturesHandler.WindowsHelpText",
- ),
- "CooperativeGesturesHandler.MacHelpText": t(
- "maplibre.CooperativeGesturesHandler.MacHelpText",
- ),
- "CooperativeGesturesHandler.MobileHelpText": t(
- "maplibre.CooperativeGesturesHandler.MobileHelpText",
- ),
- };
- }, [t]);
-
return (
-
+
{/* { Disabled for now until we can use i18n for the geolocate control} */}
{/* (
-
-
+
+
+
+ {label}
+
+
);
@@ -125,7 +123,7 @@ export const ChannelChat = ({ messages = [] }: ChannelChatProps) => {
}
return (
-
+
{groups.map(({ dayKey, label, items }) => (
{/* Render messages first, then delimiter — with flex-col-reverse this shows the delimiter above that day's messages */}
diff --git a/packages/web/src/components/PageComponents/Messages/MessageActionsMenu.tsx b/packages/web/src/components/PageComponents/Messages/MessageActionsMenu.tsx
index 2bdf10b7..23036cdf 100644
--- a/packages/web/src/components/PageComponents/Messages/MessageActionsMenu.tsx
+++ b/packages/web/src/components/PageComponents/Messages/MessageActionsMenu.tsx
@@ -4,7 +4,7 @@ import {
TooltipContent,
TooltipProvider,
TooltipTrigger,
-} from "@components/UI/Tooltip.tsx";
+} from "@app/components/UI/tooltip";
import { cn } from "@core/utils/cn.ts";
import { Reply, SmilePlus } from "lucide-react";
import { useTranslation } from "react-i18next";
diff --git a/packages/web/src/components/PageComponents/Messages/MessageInput.tsx b/packages/web/src/components/PageComponents/Messages/MessageInput.tsx
index 4a78d068..398265e7 100644
--- a/packages/web/src/components/PageComponents/Messages/MessageInput.tsx
+++ b/packages/web/src/components/PageComponents/Messages/MessageInput.tsx
@@ -75,6 +75,7 @@ export const MessageInput = ({ onSend, to, maxBytes }: MessageInputProps) => {
diff --git a/packages/web/src/components/PageComponents/Messages/MessageItem.tsx b/packages/web/src/components/PageComponents/Messages/MessageItem.tsx
index acf89a63..663881c9 100644
--- a/packages/web/src/components/PageComponents/Messages/MessageItem.tsx
+++ b/packages/web/src/components/PageComponents/Messages/MessageItem.tsx
@@ -1,11 +1,11 @@
-import { Avatar } from "@components/UI/Avatar.tsx";
import {
Tooltip,
TooltipArrow,
TooltipContent,
TooltipProvider,
TooltipTrigger,
-} from "@components/UI/Tooltip.tsx";
+} from "@app/components/UI/tooltip";
+import { Avatar } from "@components/UI/Avatar.tsx";
import { MessageState, useDevice, useNodeDB } from "@core/stores";
import type { Message } from "@core/stores/messageStore/types.ts";
import { cn } from "@core/utils/cn.ts";
diff --git a/packages/web/src/components/PageLayout.tsx b/packages/web/src/components/PageLayout.tsx
index b2bc76c2..bdca287a 100644
--- a/packages/web/src/components/PageLayout.tsx
+++ b/packages/web/src/components/PageLayout.tsx
@@ -45,12 +45,12 @@ export const PageLayout = ({
}: PageLayoutProps) => {
return (
-
+
{/* Left Sidebar */}
{leftBar && (