feat: updated app to tailwind 4.x
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" data-theme="system">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||||||
|
|||||||
+27
-24
@@ -19,7 +19,10 @@
|
|||||||
"pre-commit": "npm run check:fix && npm run format"
|
"pre-commit": "npm run check:fix && npm run format"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{ts,tsx}": ["npm run check:fix", "npm run format"]
|
"*.{ts,tsx}": [
|
||||||
|
"npm run check:fix",
|
||||||
|
"npm run format"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -33,20 +36,20 @@
|
|||||||
"@bufbuild/protobuf": "^1.10.0",
|
"@bufbuild/protobuf": "^1.10.0",
|
||||||
"@meshtastic/js": "2.3.7-5",
|
"@meshtastic/js": "2.3.7-5",
|
||||||
"@noble/curves": "^1.8.1",
|
"@noble/curves": "^1.8.1",
|
||||||
"@radix-ui/react-accordion": "^1.2.2",
|
"@radix-ui/react-accordion": "^1.2.3",
|
||||||
"@radix-ui/react-checkbox": "^1.1.3",
|
"@radix-ui/react-checkbox": "^1.1.4",
|
||||||
"@radix-ui/react-dialog": "^1.1.5",
|
"@radix-ui/react-dialog": "^1.1.6",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.5",
|
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
||||||
"@radix-ui/react-label": "^2.1.1",
|
"@radix-ui/react-label": "^2.1.2",
|
||||||
"@radix-ui/react-menubar": "^1.1.5",
|
"@radix-ui/react-menubar": "^1.1.6",
|
||||||
"@radix-ui/react-popover": "^1.1.5",
|
"@radix-ui/react-popover": "^1.1.6",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.2",
|
"@radix-ui/react-scroll-area": "^1.2.3",
|
||||||
"@radix-ui/react-select": "^2.1.5",
|
"@radix-ui/react-select": "^2.1.6",
|
||||||
"@radix-ui/react-separator": "^1.1.1",
|
"@radix-ui/react-separator": "^1.1.2",
|
||||||
"@radix-ui/react-switch": "^1.1.2",
|
"@radix-ui/react-switch": "^1.1.3",
|
||||||
"@radix-ui/react-tabs": "^1.1.2",
|
"@radix-ui/react-tabs": "^1.1.3",
|
||||||
"@radix-ui/react-toast": "^1.2.5",
|
"@radix-ui/react-toast": "^1.2.6",
|
||||||
"@radix-ui/react-tooltip": "^1.1.7",
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
"@turf/turf": "^7.2.0",
|
"@turf/turf": "^7.2.0",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"class-validator": "^0.14.1",
|
"class-validator": "^0.14.1",
|
||||||
@@ -56,7 +59,7 @@
|
|||||||
"crypto-random-string": "^5.0.0",
|
"crypto-random-string": "^5.0.0",
|
||||||
"immer": "^10.1.1",
|
"immer": "^10.1.1",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"lucide-react": "^0.474.0",
|
"lucide-react": "^0.475.0",
|
||||||
"mapbox-gl": "^3.9.4",
|
"mapbox-gl": "^3.9.4",
|
||||||
"maplibre-gl": "4.1.2",
|
"maplibre-gl": "4.1.2",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
@@ -70,21 +73,21 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.9.4",
|
"@biomejs/biome": "^1.9.4",
|
||||||
"@rsbuild/core": "^1.2.3",
|
"@rsbuild/core": "^1.2.8",
|
||||||
"@rsbuild/plugin-react": "^1.1.0",
|
"@rsbuild/plugin-react": "^1.1.0",
|
||||||
"@types/chrome": "^0.0.299",
|
"@tailwindcss/postcss": "^4.0.7",
|
||||||
|
"@types/chrome": "^0.0.304",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/node": "^22.12.0",
|
"@types/node": "^22.13.4",
|
||||||
"@types/react": "^19.0.8",
|
"@types/react": "^19.0.10",
|
||||||
"@types/react-dom": "^19.0.3",
|
"@types/react-dom": "^19.0.4",
|
||||||
"@types/w3c-web-serial": "^1.0.7",
|
"@types/w3c-web-serial": "^1.0.7",
|
||||||
"@types/web-bluetooth": "^0.0.20",
|
"@types/web-bluetooth": "^0.0.20",
|
||||||
"autoprefixer": "^10.4.20",
|
|
||||||
"gzipper": "^8.2.0",
|
"gzipper": "^8.2.0",
|
||||||
"postcss": "^8.5.1",
|
"postcss": "^8.5.1",
|
||||||
"simple-git-hooks": "^2.11.1",
|
"simple-git-hooks": "^2.11.1",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^3.0.1",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^4.0.7",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"tar": "^7.4.3",
|
"tar": "^7.4.3",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.7.3"
|
||||||
|
|||||||
+1
-2
@@ -1,6 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
'@tailwindcss/postcss': {},
|
||||||
autoprefixer: {},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
+7
-7
@@ -1,5 +1,6 @@
|
|||||||
import { DeviceWrapper } from "@app/DeviceWrapper.tsx";
|
import { DeviceWrapper } from "@app/DeviceWrapper.tsx";
|
||||||
import { PageRouter } from "@app/PageRouter.tsx";
|
import { PageRouter } from "@app/PageRouter.tsx";
|
||||||
|
import { ThemeProvider } from "@app/components/generic/ThemeProvider";
|
||||||
import { CommandPalette } from "@components/CommandPalette.tsx";
|
import { CommandPalette } from "@components/CommandPalette.tsx";
|
||||||
import { DeviceSelector } from "@components/DeviceSelector.tsx";
|
import { DeviceSelector } from "@components/DeviceSelector.tsx";
|
||||||
import { DialogManager } from "@components/Dialog/DialogManager";
|
import { DialogManager } from "@components/Dialog/DialogManager";
|
||||||
@@ -7,10 +8,10 @@ import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.tsx";
|
|||||||
import { KeyBackupReminder } from "@components/KeyBackupReminder";
|
import { KeyBackupReminder } from "@components/KeyBackupReminder";
|
||||||
import { Toaster } from "@components/Toaster.tsx";
|
import { Toaster } from "@components/Toaster.tsx";
|
||||||
import Footer from "@components/UI/Footer.tsx";
|
import Footer from "@components/UI/Footer.tsx";
|
||||||
import { ThemeController } from "@components/generic/ThemeController.tsx";
|
|
||||||
import { useAppStore } from "@core/stores/appStore.ts";
|
import { useAppStore } from "@core/stores/appStore.ts";
|
||||||
import { useDeviceStore } from "@core/stores/deviceStore.ts";
|
import { useDeviceStore } from "@core/stores/deviceStore.ts";
|
||||||
import { Dashboard } from "@pages/Dashboard/index.tsx";
|
import { Dashboard } from "@pages/Dashboard/index.tsx";
|
||||||
|
import type { JSX } from "react";
|
||||||
import { MapProvider } from "react-map-gl";
|
import { MapProvider } from "react-map-gl";
|
||||||
|
|
||||||
export const App = (): JSX.Element => {
|
export const App = (): JSX.Element => {
|
||||||
@@ -21,7 +22,7 @@ export const App = (): JSX.Element => {
|
|||||||
const device = getDevice(selectedDevice);
|
const device = getDevice(selectedDevice);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeController>
|
<>
|
||||||
<NewDeviceDialog
|
<NewDeviceDialog
|
||||||
open={connectDialogOpen}
|
open={connectDialogOpen}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
@@ -31,10 +32,10 @@ export const App = (): JSX.Element => {
|
|||||||
<Toaster />
|
<Toaster />
|
||||||
<MapProvider>
|
<MapProvider>
|
||||||
<DeviceWrapper device={device}>
|
<DeviceWrapper device={device}>
|
||||||
<div className="flex h-screen flex-col overflow-hidden bg-backgroundPrimary text-textPrimary">
|
<div className="flex h-screen flex-col overflow-hidden bg-background-primary text-text-primary">
|
||||||
<div className="flex flex-grow">
|
<div className="flex grow">
|
||||||
<DeviceSelector />
|
<DeviceSelector />
|
||||||
<div className="flex flex-grow flex-col">
|
<div className="flex grow flex-col">
|
||||||
{device ? (
|
{device ? (
|
||||||
<div className="flex h-screen">
|
<div className="flex h-screen">
|
||||||
<DialogManager />
|
<DialogManager />
|
||||||
@@ -45,7 +46,6 @@ export const App = (): JSX.Element => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Dashboard />
|
<Dashboard />
|
||||||
<div className="flex flex-grow" />
|
|
||||||
<Footer />
|
<Footer />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -54,6 +54,6 @@ export const App = (): JSX.Element => {
|
|||||||
</div>
|
</div>
|
||||||
</DeviceWrapper>
|
</DeviceWrapper>
|
||||||
</MapProvider>
|
</MapProvider>
|
||||||
</ThemeController>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
|
import MapPage from "@app/pages/Map/index.tsx";
|
||||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
import { useDevice } from "@core/stores/deviceStore.ts";
|
||||||
import ChannelsPage from "@pages/Channels.tsx";
|
import ChannelsPage from "@pages/Channels.tsx";
|
||||||
import ConfigPage from "@pages/Config/index.tsx";
|
import ConfigPage from "@pages/Config/index.tsx";
|
||||||
import MapPage from "@pages/Map.tsx";
|
|
||||||
import MessagesPage from "@pages/Messages.tsx";
|
import MessagesPage from "@pages/Messages.tsx";
|
||||||
import NodesPage from "@pages/Nodes.tsx";
|
import NodesPage from "@pages/Nodes.tsx";
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,10 @@ import {
|
|||||||
EraserIcon,
|
EraserIcon,
|
||||||
FactoryIcon,
|
FactoryIcon,
|
||||||
LayersIcon,
|
LayersIcon,
|
||||||
LayoutIcon,
|
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
MapIcon,
|
MapIcon,
|
||||||
MessageSquareIcon,
|
MessageSquareIcon,
|
||||||
MoonIcon,
|
|
||||||
PaletteIcon,
|
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
PowerIcon,
|
PowerIcon,
|
||||||
QrCodeIcon,
|
QrCodeIcon,
|
||||||
@@ -62,9 +59,6 @@ export const CommandPalette = () => {
|
|||||||
setSelectedDevice,
|
setSelectedDevice,
|
||||||
removeDevice,
|
removeDevice,
|
||||||
selectedDevice,
|
selectedDevice,
|
||||||
darkMode,
|
|
||||||
setDarkMode,
|
|
||||||
setAccent,
|
|
||||||
} = useAppStore();
|
} = useAppStore();
|
||||||
const { getDevices } = useDeviceStore();
|
const { getDevices } = useDeviceStore();
|
||||||
const { setDialogOpen, setActivePage, connection } = useDevice();
|
const { setDialogOpen, setActivePage, connection } = useDevice();
|
||||||
@@ -237,116 +231,6 @@ export const CommandPalette = () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "Application",
|
|
||||||
icon: LayoutIcon,
|
|
||||||
commands: [
|
|
||||||
{
|
|
||||||
label: "Toggle Dark Mode",
|
|
||||||
icon: MoonIcon,
|
|
||||||
action() {
|
|
||||||
setDarkMode(!darkMode);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Accent Color",
|
|
||||||
icon: PaletteIcon,
|
|
||||||
subItems: [
|
|
||||||
{
|
|
||||||
label: "Red",
|
|
||||||
icon: (
|
|
||||||
<span
|
|
||||||
className={`h-3 w-3 rounded-full ${
|
|
||||||
darkMode ? "bg-[#f25555]" : "bg-[#f28585]"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
action() {
|
|
||||||
setAccent("red");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Orange",
|
|
||||||
icon: (
|
|
||||||
<span
|
|
||||||
className={`h-3 w-3 rounded-full ${
|
|
||||||
darkMode ? "bg-[#e1720b]" : "bg-[#edb17a]"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
action() {
|
|
||||||
setAccent("orange");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Yellow",
|
|
||||||
icon: (
|
|
||||||
<span
|
|
||||||
className={`h-3 w-3 rounded-full ${
|
|
||||||
darkMode ? "bg-[#ac8c1a]" : "bg-[#e0cc87]"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
action() {
|
|
||||||
setAccent("yellow");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Green",
|
|
||||||
icon: (
|
|
||||||
<span
|
|
||||||
className={`h-3 w-3 rounded-full ${
|
|
||||||
darkMode ? "bg-[#27a341]" : "bg-[#8bc9c5]"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
action() {
|
|
||||||
setAccent("green");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Blue",
|
|
||||||
icon: (
|
|
||||||
<span
|
|
||||||
className={`h-3 w-3 rounded-full ${
|
|
||||||
darkMode ? "bg-[#2093fe]" : "bg-[#70afea]"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
action() {
|
|
||||||
setAccent("blue");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Purple",
|
|
||||||
icon: (
|
|
||||||
<span
|
|
||||||
className={`h-3 w-3 rounded-full ${
|
|
||||||
darkMode ? "bg-[#926bff]" : "bg-[#a09eef]"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
action() {
|
|
||||||
setAccent("purple");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Pink",
|
|
||||||
icon: (
|
|
||||||
<span
|
|
||||||
className={`h-3 w-3 rounded-full ${
|
|
||||||
darkMode ? "bg-[#e454c4]" : "bg-[#dba0c7]"
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
action() {
|
|
||||||
setAccent("pink");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -3,15 +3,9 @@ import { Separator } from "@components/UI/Seperator.tsx";
|
|||||||
import { Code } from "@components/UI/Typography/Code.tsx";
|
import { Code } from "@components/UI/Typography/Code.tsx";
|
||||||
import { useAppStore } from "@core/stores/appStore.ts";
|
import { useAppStore } from "@core/stores/appStore.ts";
|
||||||
import { useDeviceStore } from "@core/stores/deviceStore.ts";
|
import { useDeviceStore } from "@core/stores/deviceStore.ts";
|
||||||
import {
|
import { HomeIcon, PlusIcon, SearchIcon } from "lucide-react";
|
||||||
HomeIcon,
|
|
||||||
LanguagesIcon,
|
|
||||||
MoonIcon,
|
|
||||||
PlusIcon,
|
|
||||||
SearchIcon,
|
|
||||||
SunIcon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import type { JSX } from "react";
|
import type { JSX } from "react";
|
||||||
|
import ThemeSwitcher from "./ThemeSwitcher";
|
||||||
import { Avatar } from "./UI/Avatar";
|
import { Avatar } from "./UI/Avatar";
|
||||||
|
|
||||||
export const DeviceSelector = (): JSX.Element => {
|
export const DeviceSelector = (): JSX.Element => {
|
||||||
@@ -19,14 +13,12 @@ export const DeviceSelector = (): JSX.Element => {
|
|||||||
const {
|
const {
|
||||||
selectedDevice,
|
selectedDevice,
|
||||||
setSelectedDevice,
|
setSelectedDevice,
|
||||||
darkMode,
|
|
||||||
setDarkMode,
|
|
||||||
setCommandPaletteOpen,
|
setCommandPaletteOpen,
|
||||||
setConnectDialogOpen,
|
setConnectDialogOpen,
|
||||||
} = useAppStore();
|
} = useAppStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="flex flex-col justify-between border-r-[0.5px] border-slate-300 bg-transparent pt-2 dark:border-slate-700">
|
<nav className="flex flex-col justify-between border-r-[0.5px] border-slate-300 pt-2 dark:border-slate-700">
|
||||||
<div className="flex flex-col overflow-y-hidden">
|
<div className="flex flex-col overflow-y-hidden">
|
||||||
<ul className="flex w-20 grow flex-col items-center space-y-4 bg-transparent py-4 px-5">
|
<ul className="flex w-20 grow flex-col items-center space-y-4 bg-transparent py-4 px-5">
|
||||||
<DeviceSelectorButton
|
<DeviceSelectorButton
|
||||||
@@ -65,13 +57,14 @@ export const DeviceSelector = (): JSX.Element => {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-20 flex-col items-center space-y-5 bg-transparent px-5 pb-5">
|
<div className="flex w-20 flex-col items-center space-y-5 bg-transparent px-5 pb-5">
|
||||||
<button
|
<ThemeSwitcher />
|
||||||
|
{/* <button
|
||||||
type="button"
|
type="button"
|
||||||
className="transition-all hover:text-accent"
|
className="transition-all hover:text-accent"
|
||||||
onClick={() => setDarkMode(!darkMode)}
|
onClick={() => setDarkMode(!darkMode)}
|
||||||
>
|
>
|
||||||
{darkMode ? <SunIcon /> : <MoonIcon />}
|
{darkMode ? <SunIcon /> : <MoonIcon />}
|
||||||
</button>
|
</button> */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="transition-all hover:text-accent"
|
className="transition-all hover:text-accent"
|
||||||
@@ -79,9 +72,10 @@ export const DeviceSelector = (): JSX.Element => {
|
|||||||
>
|
>
|
||||||
<SearchIcon />
|
<SearchIcon />
|
||||||
</button>
|
</button>
|
||||||
<button type="button" className="transition-all hover:text-accent">
|
{/* TODO: This is being commented out until its fixed */}
|
||||||
|
{/* <button type="button" className="transition-all hover:text-accent">
|
||||||
<LanguagesIcon />
|
<LanguagesIcon />
|
||||||
</button>
|
</button> */}
|
||||||
<Separator />
|
<Separator />
|
||||||
<Code>{process.env.COMMIT_HASH}</Code>
|
<Code>{process.env.COMMIT_HASH}</Code>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { Switch } from "@components/UI/Switch.tsx";
|
|||||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
import { useDevice } from "@core/stores/deviceStore.ts";
|
||||||
import { Protobuf } from "@meshtastic/js";
|
import { Protobuf } from "@meshtastic/js";
|
||||||
import { toByteArray } from "base64-js";
|
import { toByteArray } from "base64-js";
|
||||||
import { useEffect, useState } from "react";
|
import { type JSX, useEffect, useState } from "react";
|
||||||
|
|
||||||
export interface ImportDialogProps {
|
export interface ImportDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -65,7 +65,7 @@ export const ImportDialog = ({
|
|||||||
}, [importDialogInput]);
|
}, [importDialogInput]);
|
||||||
|
|
||||||
const apply = () => {
|
const apply = () => {
|
||||||
channelSet?.settings.map((ch, index) => {
|
channelSet?.settings.map((ch: unknown, index: number) => {
|
||||||
connection?.setChannel(
|
connection?.setChannel(
|
||||||
new Protobuf.Channel.Channel({
|
new Protobuf.Channel.Channel({
|
||||||
index,
|
index,
|
||||||
@@ -134,7 +134,7 @@ export const ImportDialog = ({
|
|||||||
{renderOptions(Protobuf.Config_LoRaConfig_RegionCode)}
|
{renderOptions(Protobuf.Config_LoRaConfig_RegionCode)}
|
||||||
</Select> */}
|
</Select> */}
|
||||||
|
|
||||||
<span className="text-md block font-medium text-textPrimary">
|
<span className="text-md block font-medium text-text-primary">
|
||||||
Channels:
|
Channels:
|
||||||
</span>
|
</span>
|
||||||
<div className="flex w-40 flex-col gap-1">
|
<div className="flex w-40 flex-col gap-1">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
} from "@components/UI/Tabs.tsx";
|
} from "@components/UI/Tabs.tsx";
|
||||||
import { Subtle } from "@components/UI/Typography/Subtle.tsx";
|
import { Subtle } from "@components/UI/Typography/Subtle.tsx";
|
||||||
import { AlertCircle, InfoIcon } from "lucide-react";
|
import { AlertCircle, InfoIcon } from "lucide-react";
|
||||||
import { Fragment } from "react/jsx-runtime";
|
import { Fragment, type JSX } from "react/jsx-runtime";
|
||||||
import { Link } from "../UI/Typography/Link";
|
import { Link } from "../UI/Typography/Link";
|
||||||
|
|
||||||
export interface TabElementProps {
|
export interface TabElementProps {
|
||||||
@@ -78,9 +78,9 @@ const ErrorMessage = ({ missingFeatures }: FeatureErrorProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Subtle className="flex flex-col items-start gap-2 text-black bg-red-200/80 p-4 rounded-md">
|
<Subtle className="flex flex-col items-start gap-2 text-slate-900 bg-red-200/80 p-4 rounded-md">
|
||||||
<div className="flex items-center gap-2 w-full">
|
<div className="flex items-center gap-2 w-full">
|
||||||
<AlertCircle size={40} className="mr-2 flex-shrink-0" />
|
<AlertCircle size={40} className="mr-2 shrink-0" />
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
{browserFeatures.length > 0 && (
|
{browserFeatures.length > 0 && (
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ export const QRDialog = ({
|
|||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`border-black border-t border-l border-b rounded-l h-10 px-7 py-2 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 ${
|
className={`border-black border-t border-l border-b rounded-l h-10 px-7 py-2 text-sm font-medium focus:outline-hidden focus:ring-2 focus:ring-offset-2 ${
|
||||||
qrCodeAdd
|
qrCodeAdd
|
||||||
? "focus:ring-green-800 bg-green-800 text-white"
|
? "focus:ring-green-800 bg-green-800 text-white"
|
||||||
: "focus:ring-slate-400 bg-slate-400 hover:bg-green-600"
|
: "focus:ring-slate-400 bg-slate-400 hover:bg-green-600"
|
||||||
@@ -111,7 +111,7 @@ export const QRDialog = ({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`border-black border-t border-r border-b rounded-r h-10 px-4 py-2 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 ${
|
className={`border-black border-t border-r border-b rounded-r h-10 px-4 py-2 text-sm font-medium focus:outline-hidden focus:ring-2 focus:ring-offset-2 ${
|
||||||
!qrCodeAdd
|
!qrCodeAdd
|
||||||
? "focus:ring-green-800 bg-green-800 text-white"
|
? "focus:ring-green-800 bg-green-800 text-white"
|
||||||
: "focus:ring-slate-400 bg-slate-400 hover:bg-green-600"
|
: "focus:ring-slate-400 bg-slate-400 hover:bg-green-600"
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export function DynamicForm<T extends FieldValues>({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className="space-y-8 divide-y divide-gray-200"
|
className="space-y-8"
|
||||||
{...(submitType === "onSubmit"
|
{...(submitType === "onSubmit"
|
||||||
? { onSubmit: handleSubmit(onSubmit) }
|
? { onSubmit: handleSubmit(onSubmit) }
|
||||||
: {
|
: {
|
||||||
@@ -92,10 +92,7 @@ export function DynamicForm<T extends FieldValues>({
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{fieldGroups.map((fieldGroup) => (
|
{fieldGroups.map((fieldGroup) => (
|
||||||
<div
|
<div key={fieldGroup.label} className="space-y-8 sm:space-y-5">
|
||||||
key={fieldGroup.label}
|
|
||||||
className="space-y-8 divide-y divide-gray-200 sm:space-y-5"
|
|
||||||
>
|
|
||||||
<div>
|
<div>
|
||||||
<H4 className="font-medium">{fieldGroup.label}</H4>
|
<H4 className="font-medium">{fieldGroup.label}</H4>
|
||||||
<Subtle>{fieldGroup.description}</Subtle>
|
<Subtle>{fieldGroup.description}</Subtle>
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export const BLE = ({ closeDialog }: TabElementProps): JSX.Element => {
|
|||||||
{bleDevices.map((device) => (
|
{bleDevices.map((device) => (
|
||||||
<Button
|
<Button
|
||||||
key={device.id}
|
key={device.id}
|
||||||
|
className="dark:bg-slate-900 dark:text-white"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onConnect(device);
|
onConnect(device);
|
||||||
}}
|
}}
|
||||||
@@ -53,6 +54,7 @@ export const BLE = ({ closeDialog }: TabElementProps): JSX.Element => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
|
className="dark:bg-slate-900 dark:text-white"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await navigator.bluetooth
|
await navigator.bluetooth
|
||||||
.requestDevice({
|
.requestDevice({
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ export const HTTP = ({ closeDialog }: TabElementProps): JSX.Element => {
|
|||||||
<div className="flex h-48 flex-col gap-2">
|
<div className="flex h-48 flex-col gap-2">
|
||||||
<Label>IP Address/Hostname</Label>
|
<Label>IP Address/Hostname</Label>
|
||||||
<Input
|
<Input
|
||||||
// label="IP Address/Hostname"
|
|
||||||
prefix={tlsEnabled ? "https://" : "http://"}
|
prefix={tlsEnabled ? "https://" : "http://"}
|
||||||
placeholder="000.000.000.000 / meshtastic.local"
|
placeholder="000.000.000.000 / meshtastic.local"
|
||||||
|
className="text-black"
|
||||||
disabled={connectionInProgress}
|
disabled={connectionInProgress}
|
||||||
{...register("ip")}
|
{...register("ip")}
|
||||||
/>
|
/>
|
||||||
@@ -85,7 +85,11 @@ export const HTTP = ({ closeDialog }: TabElementProps): JSX.Element => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button type="submit" disabled={connectionInProgress}>
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={connectionInProgress}
|
||||||
|
className="dark:bg-slate-900 dark:text-white"
|
||||||
|
>
|
||||||
<span>{connectionInProgress ? "Connecting..." : "Connect"}</span>
|
<span>{connectionInProgress ? "Connecting..." : "Connect"}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => {
|
|||||||
<Button
|
<Button
|
||||||
key={`${usbVendorId ?? "UNK"}-${usbProductId ?? "UNK"}-${index}`}
|
key={`${usbVendorId ?? "UNK"}-${usbProductId ?? "UNK"}-${index}`}
|
||||||
disabled={port.readable !== null}
|
disabled={port.readable !== null}
|
||||||
|
className="dark:bg-slate-900 dark:text-white"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await onConnect(port);
|
await onConnect(port);
|
||||||
}}
|
}}
|
||||||
@@ -69,6 +70,7 @@ export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
|
className="dark:bg-slate-900 dark:text-white"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await navigator.serial.requestPort().then((port) => {
|
await navigator.serial.requestPort().then((port) => {
|
||||||
setSerialPorts(serialPorts.concat(port));
|
setSerialPorts(serialPorts.concat(port));
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
|
|||||||
Protobuf.Mesh.HardwareModel[hwModel]?.replaceAll("_", " ") ?? `${hwModel}`;
|
Protobuf.Mesh.HardwareModel[hwModel]?.replaceAll("_", " ") ?? `${hwModel}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dark:text-black p-1">
|
<div className="dark:text-slate-900 p-1">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="flex flex-col items-center gap-2 min-w-6 pt-1">
|
<div className="flex flex-col items-center gap-2 min-w-6 pt-1">
|
||||||
<Avatar text={node.user?.shortName} />
|
<Avatar text={node.user?.shortName} />
|
||||||
@@ -120,14 +120,14 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
|
|||||||
<Separator className="my-1" />
|
<Separator className="my-1" />
|
||||||
|
|
||||||
<div className="flex mt-2 text-sm">
|
<div className="flex mt-2 text-sm">
|
||||||
<div className="flex items-center flex-grow">
|
<div className="flex items-center grow">
|
||||||
<div className="border-2 border-black rounded px-0.5 mr-1">
|
<div className="border-2 border-black rounded-sm px-0.5 mr-1">
|
||||||
{Number.isNaN(node.hopsAway) ? "?" : node.hopsAway}
|
{Number.isNaN(node.hopsAway) ? "?" : node.hopsAway}
|
||||||
</div>
|
</div>
|
||||||
<div>{node.hopsAway === 1 ? "Hop" : "Hops"}</div>
|
<div>{node.hopsAway === 1 ? "Hop" : "Hops"}</div>
|
||||||
</div>
|
</div>
|
||||||
{node.position?.altitude && (
|
{node.position?.altitude && (
|
||||||
<div className="flex items-center flex-grow">
|
<div className="flex items-center grow">
|
||||||
<MountainSnow
|
<MountainSnow
|
||||||
size={15}
|
size={15}
|
||||||
className="ml-2 mr-1"
|
className="ml-2 mr-1"
|
||||||
@@ -145,7 +145,7 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
|
|||||||
|
|
||||||
<div className="flex mt-2">
|
<div className="flex mt-2">
|
||||||
{!!node.deviceMetrics?.channelUtilization && (
|
{!!node.deviceMetrics?.channelUtilization && (
|
||||||
<div className="flex-grow">
|
<div className="grow">
|
||||||
<div>Channel Util</div>
|
<div>Channel Util</div>
|
||||||
<Mono>
|
<Mono>
|
||||||
{node.deviceMetrics?.channelUtilization.toPrecision(3)}%
|
{node.deviceMetrics?.channelUtilization.toPrecision(3)}%
|
||||||
@@ -153,7 +153,7 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!!node.deviceMetrics?.airUtilTx && (
|
{!!node.deviceMetrics?.airUtilTx && (
|
||||||
<div className="flex-grow">
|
<div className="grow">
|
||||||
<div>Airtime Util</div>
|
<div>Airtime Util</div>
|
||||||
<Mono>{node.deviceMetrics?.airUtilTx.toPrecision(3)}%</Mono>
|
<Mono>{node.deviceMetrics?.airUtilTx.toPrecision(3)}%</Mono>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export const ChannelChat = ({
|
|||||||
<div className="flex-1 flex items-center justify-center">
|
<div className="flex-1 flex items-center justify-center">
|
||||||
<EmptyState />
|
<EmptyState />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-shrink-0 p-4 w-full dark:bg-gray-900">
|
<div className="shrink-0 p-4 w-full dark:bg-gray-900">
|
||||||
<MessageInput to={to} channel={channel} maxBytes={200} />
|
<MessageInput to={to} channel={channel} maxBytes={200} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -81,7 +81,7 @@ export const ChannelChat = ({
|
|||||||
<div ref={messagesEndRef} className="w-full" />
|
<div ref={messagesEndRef} className="w-full" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-shrink-0 mt-2 p-4 w-full dark:bg-gray-900">
|
<div className="shrink-0 mt-2 p-4 w-full dark:bg-gray-900">
|
||||||
<MessageInput to={to} channel={channel} maxBytes={200} />
|
<MessageInput to={to} channel={channel} maxBytes={200} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ const StatusIcon = ({ state, className, ...otherProps }: StatusIconProps) => {
|
|||||||
const isFailed = state === MESSAGE_STATES.FAILED;
|
const isFailed = state === MESSAGE_STATES.FAILED;
|
||||||
const iconClass = cn(
|
const iconClass = cn(
|
||||||
className,
|
className,
|
||||||
"text-gray-500 dark:text-gray-400 w-4 h-4 flex-shrink-0",
|
"text-gray-500 dark:text-gray-400 w-4 h-4 shrink-0",
|
||||||
);
|
);
|
||||||
|
|
||||||
const Icon = STATUS_ICON_MAP[state];
|
const Icon = STATUS_ICON_MAP[state];
|
||||||
@@ -99,8 +99,8 @@ const getMessageTextStyles = (state: MessageState) => {
|
|||||||
return cn(
|
return cn(
|
||||||
"break-words overflow-hidden",
|
"break-words overflow-hidden",
|
||||||
isAcknowledged
|
isAcknowledged
|
||||||
? "text-black dark:text-white"
|
? "text-slate-900 dark:text-white"
|
||||||
: "text-black dark:text-gray-400",
|
: "text-slate-900 dark:text-gray-400",
|
||||||
isFailed && "text-red-500 dark:text-red-500",
|
isFailed && "text-red-500 dark:text-red-500",
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -109,7 +109,7 @@ const TimeDisplay = ({
|
|||||||
date,
|
date,
|
||||||
className,
|
className,
|
||||||
}: { date: Date; className?: string }) => (
|
}: { date: Date; className?: string }) => (
|
||||||
<div className={cn("flex items-center gap-2 flex-shrink-0", className)}>
|
<div className={cn("flex items-center gap-2 shrink-0", className)}>
|
||||||
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
|
<span className="text-xs text-gray-500 dark:text-gray-400 font-mono">
|
||||||
{date.toLocaleDateString()}
|
{date.toLocaleDateString()}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export const MessageInput = ({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-grow gap-2">
|
<div className="flex grow gap-2">
|
||||||
<span className="w-full">
|
<span className="w-full">
|
||||||
<Input
|
<Input
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const TraceRoute = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ml-5 flex">
|
<div className="ml-5 flex">
|
||||||
<span className="ml-4 border-l-2 border-l-backgroundPrimary pl-2 text-textPrimary">
|
<span className="ml-4 border-l-2 border-l-background-primary pl-2 text-text-primary">
|
||||||
<p className="font-semibold">Route to destination:</p>
|
<p className="font-semibold">Route to destination:</p>
|
||||||
<p>{to?.user?.longName}</p>
|
<p>{to?.user?.longName}</p>
|
||||||
<p>↓ {snrTowards?.[0] ? snrTowards[0] : "??"}dB</p>
|
<p>↓ {snrTowards?.[0] ? snrTowards[0] : "??"}dB</p>
|
||||||
@@ -39,7 +39,7 @@ export const TraceRoute = ({
|
|||||||
{from?.user?.longName}
|
{from?.user?.longName}
|
||||||
</span>
|
</span>
|
||||||
{routeBack ? (
|
{routeBack ? (
|
||||||
<span className="ml-4 border-l-2 border-l-backgroundPrimary pl-2 text-textPrimary">
|
<span className="ml-4 border-l-2 border-l-background-primary pl-2 text-text-primary">
|
||||||
<p className="font-semibold">Route back:</p>
|
<p className="font-semibold">Route back:</p>
|
||||||
<p>{from?.user?.longName}</p>
|
<p>{from?.user?.longName}</p>
|
||||||
<p>↓ {snrBack?.[0] ? snrBack[0] : "??"}dB</p>
|
<p>↓ {snrBack?.[0] ? snrBack[0] : "??"}dB</p>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const PageLayout = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="relative flex h-full w-full flex-col">
|
<div className="relative flex h-full w-full flex-col">
|
||||||
<div className="flex h-14 shrink-0 border-b-[0.5px] border-slate-300 dark:border-slate-700 md:h-16 md:px-4">
|
<div className="flex h-14 shrink-0 border-b-[0.5px] border-slate-300 dark:border-slate-700 md:h-16 md:px-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="pl-4 transition-all hover:text-accent md:hidden"
|
className="pl-4 transition-all hover:text-accent md:hidden"
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return showSidebar ? (
|
return showSidebar ? (
|
||||||
<div className="min-w-[280px] max-w-min flex-col overflow-y-auto border-r-[0.5px] border-slate-300 bg-transparent dark:border-slate-700">
|
<div className="min-w-[280px] max-w-min flex-col overflow-y-auto border-r-[0.5px] bg-background-primary border-slate-300 dark:border-slate-700">
|
||||||
<div className="flex justify-between px-8 pt-6">
|
<div className="flex justify-between px-8 pt-6">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-lg font-medium">
|
<span className="text-lg font-medium">
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { useTheme } from "@app/core/hooks/useTheme";
|
||||||
|
import { Moon, Sun } from "lucide-react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
type Theme = "light" | "dark";
|
||||||
|
|
||||||
|
export default function ThemeSwitcher({
|
||||||
|
className = "",
|
||||||
|
}: { className?: string }) {
|
||||||
|
const currentTheme = useTheme(); // Get current theme from DOM
|
||||||
|
const [theme, setTheme] = React.useState<Theme>(currentTheme);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
|
localStorage.setItem("theme", theme);
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
const themeIcons = {
|
||||||
|
light: (
|
||||||
|
<Sun className="size-5 transition-transform duration-300 scale-100" />
|
||||||
|
),
|
||||||
|
dark: (
|
||||||
|
<Moon className="size-5 transition-transform duration-300 scale-100" />
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleTheme = () => setTheme(theme === "light" ? "dark" : "light");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`transition-all duration-300 hover:text-accent ${className}`}
|
||||||
|
onClick={toggleTheme}
|
||||||
|
aria-label={`Current theme: ${theme}. Click to change theme.`}
|
||||||
|
>
|
||||||
|
{themeIcons[theme]}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,12 +4,12 @@ import * as React from "react";
|
|||||||
import { cn } from "@core/utils/cn.ts";
|
import { cn } from "@core/utils/cn.ts";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 dark:hover:bg-slate-800 dark:hover:text-slate-100 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900 data-[state=open]:bg-slate-100 dark:data-[state=open]:bg-slate-800",
|
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-hidden focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900 cursor-pointer",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default:
|
||||||
"bg-slate-900 text-white hover:bg-slate-700 dark:bg-slate-50 dark:text-slate-900",
|
"bg-slate-900 text-white dark:bg-white dark:text-slate-900 bg-slate-900 text-white hover:bg-slate-700 hover:text-slate-50",
|
||||||
destructive:
|
destructive:
|
||||||
"bg-red-500 text-white hover:bg-red-600 dark:hover:bg-red-600",
|
"bg-red-500 text-white hover:bg-red-600 dark:hover:bg-red-600",
|
||||||
success:
|
success:
|
||||||
@@ -17,7 +17,7 @@ const buttonVariants = cva(
|
|||||||
outline:
|
outline:
|
||||||
"bg-transparent border border-slate-200 hover:bg-slate-100 dark:border-slate-400 dark:text-slate-100",
|
"bg-transparent border border-slate-200 hover:bg-slate-100 dark:border-slate-400 dark:text-slate-100",
|
||||||
subtle:
|
subtle:
|
||||||
"bg-slate-100 text-slate-900 hover:bg-slate-200 dark:bg-slate-700 dark:text-slate-100",
|
"bg-slate-100 text-slate-900 hover:bg-slate-200 dark:hover:bg-slate-800 dark:bg-slate-700 dark:text-slate-100",
|
||||||
ghost:
|
ghost:
|
||||||
"bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800 dark:text-slate-100 dark:hover:text-slate-100 data-[state=open]:bg-transparent dark:data-[state=open]:bg-transparent",
|
"bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800 dark:text-slate-100 dark:hover:text-slate-100 data-[state=open]:bg-transparent dark:data-[state=open]:bg-transparent",
|
||||||
link: "bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent",
|
link: "bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const Checkbox = React.forwardRef<
|
|||||||
<CheckboxPrimitive.Root
|
<CheckboxPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"peer h-4 w-4 shrink-0 rounded-sm border border-slate-300 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
|
"peer h-4 w-4 shrink-0 rounded-xs border border-slate-300 focus:outline-hidden focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const CommandInput = React.forwardRef<
|
|||||||
<CommandPrimitive.Input
|
<CommandPrimitive.Input
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-slate-400 disabled:cursor-not-allowed disabled:opacity-50 dark:text-slate-50",
|
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-slate-400 disabled:cursor-not-allowed disabled:opacity-50 dark:text-slate-50",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -88,7 +88,7 @@ const CommandGroup = React.forwardRef<
|
|||||||
<CommandPrimitive.Group
|
<CommandPrimitive.Group
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"overflow-hidden py-3 px-2 text-slate-700 dark:text-slate-400 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:pb-1.5 [&_[cmdk-group-heading]]:text-sm [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:text-slate-900 [&_[cmdk-group-heading]]:dark:text-slate-300",
|
"overflow-hidden py-3 px-2 text-slate-700 dark:text-slate-400 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:pb-1.5 [&_[cmdk-group-heading]]:text-sm [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:text-slate-900 dark:[&_[cmdk-group-heading]]:text-slate-300",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -116,7 +116,7 @@ const CommandItem = React.forwardRef<
|
|||||||
<CommandPrimitive.Item
|
<CommandPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-md py-1.5 px-2 text-sm font-medium outline-none aria-selected:bg-slate-100 data-[disabled='true']:pointer-events-none data-[disabled='true']:opacity-50 dark:aria-selected:bg-slate-700",
|
"relative flex cursor-default select-none items-center rounded-md py-1.5 px-2 text-sm font-medium outline-hidden aria-selected:bg-slate-100 data-[disabled='true']:pointer-events-none data-[disabled='true']:opacity-50 dark:aria-selected:bg-slate-700",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const DialogOverlay = React.forwardRef<
|
|||||||
>(({ className, children, ...props }, ref) => (
|
>(({ className, children, ...props }, ref) => (
|
||||||
<DialogPrimitive.Overlay
|
<DialogPrimitive.Overlay
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-0 z-50 bg-black/50 backdrop-blur-sm transition-opacity animate-in fade-in",
|
"fixed inset-0 z-50 bg-black/50 backdrop-blur-xs transition-opacity animate-in fade-in",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -44,14 +44,13 @@ const DialogContent = React.forwardRef<
|
|||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed z-50 grid w-full max-w-[512px] max-h-[100vh] overflow-y-scroll scale-100 gap-4 bg-white p-6 opacity-100 animate-in fade-in-90 slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 sm:slide-in-from-bottom-0",
|
"fixed z-50 grid w-full bg-white max-w-[512px] max-h-[100vh] overflow-y-auto scale-100 gap-4 p-6 opacity-100 animate-in fade-in-90 slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 sm:slide-in-from-bottom-0",
|
||||||
"dark:bg-slate-900",
|
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<DialogPrimitive.Close className="absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800">
|
<DialogPrimitive.Close className="absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800">
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
@@ -66,7 +65,7 @@ const DialogHeader = ({
|
|||||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col space-y-2 text-center sm:text-left",
|
"flex flex-col space-y-2 text-center sm:text-left ",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -94,11 +93,7 @@ const DialogTitle = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<DialogPrimitive.Title
|
<DialogPrimitive.Title
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn("text-lg font-semibold text-slate-900", className)}
|
||||||
"text-lg font-semibold text-slate-900",
|
|
||||||
"dark:text-slate-50",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.SubTrigger
|
<DropdownMenuPrimitive.SubTrigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default select-none items-center rounded-sm py-1.5 px-2 text-sm font-medium outline-none focus:bg-slate-100 data-[state=open]:bg-slate-100 dark:focus:bg-slate-700 dark:data-[state=open]:bg-slate-700",
|
"flex cursor-default select-none items-center rounded-xs py-1.5 px-2 text-sm font-medium outline-hidden focus:bg-slate-100 data-[state=open]:bg-slate-100 dark:focus:bg-slate-700 dark:data-[state=open]:bg-slate-700",
|
||||||
inset && "pl-8",
|
inset && "pl-8",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
@@ -81,7 +81,7 @@ const DropdownMenuItem = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.Item
|
<DropdownMenuPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 px-2 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700",
|
"relative flex cursor-default select-none items-center rounded-xs py-1.5 px-2 text-sm font-medium outline-hidden focus:bg-slate-100 data-disabled:pointer-events-none data-disabled:opacity-50 dark:focus:bg-slate-700",
|
||||||
inset && "pl-8",
|
inset && "pl-8",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
@@ -97,7 +97,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.CheckboxItem
|
<DropdownMenuPrimitive.CheckboxItem
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700",
|
"relative flex cursor-default select-none items-center rounded-xs py-1.5 pl-8 pr-2 text-sm font-medium outline-hidden focus:bg-slate-100 data-disabled:pointer-events-none data-disabled:opacity-50 dark:focus:bg-slate-700",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
@@ -121,7 +121,7 @@ const DropdownMenuRadioItem = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.RadioItem
|
<DropdownMenuPrimitive.RadioItem
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700",
|
"relative flex cursor-default select-none items-center rounded-xs py-1.5 pl-8 pr-2 text-sm font-medium outline-hidden focus:bg-slate-100 data-disabled:pointer-events-none data-disabled:opacity-50 dark:focus:bg-slate-700",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { cn } from "@app/core/utils/cn";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export interface FooterProps extends React.HTMLAttributes<HTMLElement> {}
|
export interface FooterProps extends React.HTMLAttributes<HTMLElement> {}
|
||||||
@@ -5,26 +6,18 @@ export interface FooterProps extends React.HTMLAttributes<HTMLElement> {}
|
|||||||
const Footer = React.forwardRef<HTMLElement, FooterProps>(
|
const Footer = React.forwardRef<HTMLElement, FooterProps>(
|
||||||
({ className, ...props }, ref) => {
|
({ className, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<footer
|
<footer className={cn("flex mt-auto justify-center p-2", className)}>
|
||||||
className={`flex mt-auto justify-center p-2 ${className}`}
|
|
||||||
style={{
|
|
||||||
backgroundColor: "var(--backgroundPrimary)",
|
|
||||||
color: "var(--textPrimary)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<p>
|
<p>
|
||||||
<a
|
<a
|
||||||
href="https://vercel.com/?utm_source=meshtastic&utm_campaign=oss"
|
href="https://vercel.com/?utm_source=meshtastic&utm_campaign=oss"
|
||||||
className="hover:underline"
|
className="hover:underline text-link"
|
||||||
style={{ color: "var(--link)" }}
|
|
||||||
>
|
>
|
||||||
Powered by ▲ Vercel
|
Powered by ▲ Vercel
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
| Meshtastic® is a registered trademark of Meshtastic LLC. |{" "}
|
| Meshtastic® is a registered trademark of Meshtastic LLC. |{" "}
|
||||||
<a
|
<a
|
||||||
href="https://meshtastic.org/docs/legal"
|
href="https://meshtastic.org/docs/legal"
|
||||||
className="hover:underline"
|
className="hover:underline text-link"
|
||||||
style={{ color: "var(--link)" }}
|
|
||||||
>
|
>
|
||||||
Legal Information
|
Legal Information
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>(
|
|||||||
onValueChange={(e) => selectChange(e)}
|
onValueChange={(e) => selectChange(e)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="!max-w-max">
|
<SelectTrigger>
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { type VariantProps, cva } from "class-variance-authority";
|
|||||||
import type { LucideIcon } from "lucide-react";
|
import type { LucideIcon } from "lucide-react";
|
||||||
|
|
||||||
const inputVariants = cva(
|
const inputVariants = cva(
|
||||||
"flex h-10 w-full rounded-md border bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
|
"flex h-10 w-full rounded-md border bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-hidden focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
@@ -35,29 +35,29 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
return (
|
return (
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
{prefix && (
|
{prefix && (
|
||||||
<span className="inline-flex items-center rounded-l-md bg-backgroundPrimary px-3 font-mono text-sm text-textSecondary brightness-hover">
|
<span className="inline-flex items-center rounded-l-md bg-gray-100/90 px-3 font-mono text-sm text-slate-600">
|
||||||
{prefix}
|
{prefix}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<input
|
<input
|
||||||
className={cn(
|
className={cn(
|
||||||
action && "pr-8",
|
action && "pr-8",
|
||||||
className,
|
|
||||||
inputVariants({ variant }),
|
inputVariants({ variant }),
|
||||||
|
className,
|
||||||
)}
|
)}
|
||||||
value={value}
|
value={value}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
{suffix && (
|
{suffix && (
|
||||||
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-9 font-mono text-textSecondary">
|
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-9 font-mono text-slate-500">
|
||||||
<span className="text-gray-500 sm:text-sm">{suffix}</span>
|
<span className="text-gray-100/40 sm:text-sm">{suffix}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{action && (
|
{action && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500 hover:text-gray-400 focus:outline-none "
|
className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500 hover:text-gray-400 focus:outline-hidden "
|
||||||
onClick={action.onClick}
|
onClick={action.onClick}
|
||||||
>
|
>
|
||||||
<action.icon size={20} />
|
<action.icon size={20} />
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const MenubarTrigger = React.forwardRef<
|
|||||||
<MenubarPrimitive.Trigger
|
<MenubarPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default select-none items-center rounded-[0.2rem] py-1.5 px-3 text-sm font-medium outline-none focus:bg-slate-100 data-[state=open]:bg-slate-100 dark:focus:bg-slate-700 dark:data-[state=open]:bg-slate-700",
|
"flex cursor-default select-none items-center rounded-[0.2rem] py-1.5 px-3 text-sm font-medium outline-hidden focus:bg-slate-100 data-[state=open]:bg-slate-100 dark:focus:bg-slate-700 dark:data-[state=open]:bg-slate-700",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -53,7 +53,7 @@ const MenubarSubTrigger = React.forwardRef<
|
|||||||
<MenubarPrimitive.SubTrigger
|
<MenubarPrimitive.SubTrigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default select-none items-center rounded-sm py-1.5 px-2 text-sm font-medium outline-none focus:bg-slate-100 data-[state=open]:bg-slate-100 dark:focus:bg-slate-700 dark:data-[state=open]:bg-slate-700",
|
"flex cursor-default select-none items-center rounded-xs py-1.5 px-2 text-sm font-medium outline-hidden focus:bg-slate-100 data-[state=open]:bg-slate-100 dark:focus:bg-slate-700 dark:data-[state=open]:bg-slate-700",
|
||||||
inset && "pl-8",
|
inset && "pl-8",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
@@ -114,7 +114,7 @@ const MenubarItem = React.forwardRef<
|
|||||||
<MenubarPrimitive.Item
|
<MenubarPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 px-2 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700",
|
"relative flex cursor-default select-none items-center rounded-xs py-1.5 px-2 text-sm font-medium outline-hidden focus:bg-slate-100 data-disabled:pointer-events-none data-disabled:opacity-50 dark:focus:bg-slate-700",
|
||||||
inset && "pl-8",
|
inset && "pl-8",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
@@ -130,7 +130,7 @@ const MenubarCheckboxItem = React.forwardRef<
|
|||||||
<MenubarPrimitive.CheckboxItem
|
<MenubarPrimitive.CheckboxItem
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700",
|
"relative flex cursor-default select-none items-center rounded-xs py-1.5 pl-8 pr-2 text-sm font-medium outline-hidden focus:bg-slate-100 data-disabled:pointer-events-none data-disabled:opacity-50 dark:focus:bg-slate-700",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
@@ -153,7 +153,7 @@ const MenubarRadioItem = React.forwardRef<
|
|||||||
<MenubarPrimitive.RadioItem
|
<MenubarPrimitive.RadioItem
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700",
|
"relative flex cursor-default select-none items-center rounded-xs py-1.5 pl-8 pr-2 text-sm font-medium outline-hidden focus:bg-slate-100 data-disabled:pointer-events-none data-disabled:opacity-50 dark:focus:bg-slate-700",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const MultiSelectItem = ({
|
|||||||
inline-flex items-center rounded-md px-3 py-2 text-sm transition-colors
|
inline-flex items-center rounded-md px-3 py-2 text-sm transition-colors
|
||||||
border border-slate-300
|
border border-slate-300
|
||||||
hover:bg-slate-100 dark:hover:bg-slate-800
|
hover:bg-slate-100 dark:hover:bg-slate-800
|
||||||
focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2
|
focus:outline-hidden focus:ring-2 focus:ring-slate-400 focus:ring-offset-2
|
||||||
data-[state=checked]:bg-slate-100 dark:data-[state=checked]:bg-slate-700`,
|
data-[state=checked]:bg-slate-100 dark:data-[state=checked]:bg-slate-700`,
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const PopoverContent = React.forwardRef<
|
|||||||
align={align}
|
align={align}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 w-72 rounded-md border border-slate-100 bg-white p-4 shadow-md outline-none animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2 dark:border-slate-800 dark:bg-slate-800",
|
"z-50 w-72 rounded-md border border-slate-100 bg-white p-4 shadow-md outline-hidden animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2 dark:border-slate-800 dark:bg-slate-800",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const SelectTrigger = React.forwardRef<
|
|||||||
<SelectPrimitive.Trigger
|
<SelectPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-10 w-full items-center justify-between rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
|
"flex h-10 w-full items-center justify-between rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-hidden focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -36,7 +36,7 @@ const SelectContent = React.forwardRef<
|
|||||||
<SelectPrimitive.Content
|
<SelectPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-100 bg-white text-slate-700 shadow-md animate-in fade-in-80 dark:border-slate-800 dark:bg-slate-800 dark:text-slate-400",
|
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border border-slate-100 bg-white text-slate-700 shadow-md animate-in fade-in-80 dark:border-slate-800 dark:text-slate-900",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -56,7 +56,7 @@ const SelectLabel = React.forwardRef<
|
|||||||
<SelectPrimitive.Label
|
<SelectPrimitive.Label
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"py-1.5 pr-2 pl-8 text-sm font-semibold text-slate-900 dark:text-slate-300",
|
"py-1.5 pr-2 pl-8 text-sm font-semibold text-slate-900 dark:text-white",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -71,7 +71,7 @@ const SelectItem = React.forwardRef<
|
|||||||
<SelectPrimitive.Item
|
<SelectPrimitive.Item
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pr-2 pl-8 text-sm font-medium outline-none focus:bg-slate-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-slate-700",
|
"relative flex cursor-default select-none items-center rounded-xs py-1.5 pr-2 pl-8 text-sm font-medium outline-hidden focus:bg-slate-200 data-disabled:pointer-events-none data-disabled:opacity-50 dark:focus:bg-slate-200",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -25,6 +25,6 @@ export const SidebarButton = ({
|
|||||||
>
|
>
|
||||||
{Icon && <Icon size={16} />}
|
{Icon && <Icon size={16} />}
|
||||||
{element && element}
|
{element && element}
|
||||||
<span className="flex flex-1 justify-start flex-shrink-0">{label}</span>
|
<span className="flex flex-1 justify-start shrink-0">{label}</span>
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const Switch = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<SwitchPrimitives.Root
|
<SwitchPrimitives.Root
|
||||||
className={cn(
|
className={cn(
|
||||||
"peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-slate-900 data-[state=unchecked]:bg-slate-200 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=checked]:bg-slate-400 dark:data-[state=unchecked]:bg-slate-700",
|
"peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus:outline-hidden focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-slate-900 data-[state=unchecked]:bg-slate-200 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=checked]:bg-slate-400 dark:data-[state=unchecked]:bg-slate-700",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const TabsList = React.forwardRef<
|
|||||||
<TabsPrimitive.List
|
<TabsPrimitive.List
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex flex-wrap items-center rounded-md bg-slate-100 p-1 mt-2 dark:bg-slate-800",
|
"inline-flex flex-wrap items-center rounded-md p-1 mt-2 bg-slate-200 dark:bg-slate-200",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -26,7 +26,7 @@ const TabsTrigger = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<TabsPrimitive.Trigger
|
<TabsPrimitive.Trigger
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex min-w-[100px] items-center justify-center rounded-[0.185rem] px-3 py-1.5 text-sm font-medium text-slate-700 transition-all disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-sm dark:text-slate-200 dark:data-[state=active]:bg-slate-900 dark:data-[state=active]:text-slate-100",
|
"inline-flex min-w-[100px] items-center justify-center rounded-[0.185rem] px-3 py-1.5 text-sm font-medium text-slate-900 transition-all disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-slate-900 data-[state=active]:shadow-xs cursor-pointer",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const ToastViewport = React.forwardRef<
|
|||||||
<ToastPrimitives.Viewport
|
<ToastPrimitives.Viewport
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-24 sm:right-6 sm:top-auto sm:flex-col md:max-w-[420px]",
|
"fixed top-0 z-100 flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-24 sm:right-6 sm:top-auto sm:flex-col md:max-w-[420px]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -23,12 +23,12 @@ const ToastViewport = React.forwardRef<
|
|||||||
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
|
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
|
||||||
|
|
||||||
const toastVariants = cva(
|
const toastVariants = cva(
|
||||||
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
|
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full sm:data-[state=open]:slide-in-from-bottom-full",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default:
|
||||||
"border bg-backgroundPrimary text-foreground dark:bg-slate-700 dark:border-slate-600 dark:text-slate-50",
|
"border bg-backgound-primary text-foreground dark:bg-white dark:border-slate-600 dark:text-slate-900",
|
||||||
destructive:
|
destructive:
|
||||||
"group destructive bg-red-600 text-white dark:border-red-900 dark:bg-red-900 dark:text-red-50",
|
"group destructive bg-red-600 text-white dark:border-red-900 dark:bg-red-900 dark:text-red-50",
|
||||||
},
|
},
|
||||||
@@ -61,7 +61,7 @@ const ToastAction = React.forwardRef<
|
|||||||
<ToastPrimitives.Action
|
<ToastPrimitives.Action
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
|
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 hover:group-[.destructive]:border-destructive/30 hover:group-[.destructive]:bg-destructive hover:group-[.destructive]:text-destructive-foreground focus:group-[.destructive]:ring-destructive",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -76,7 +76,7 @@ const ToastClose = React.forwardRef<
|
|||||||
<ToastPrimitives.Close
|
<ToastPrimitives.Close
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 dark:text-slate-400 dark:hover:text-slate-50",
|
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-hidden focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 hover:group-[.destructive]:text-red-50 focus:group-[.destructive]:ring-red-400 focus:group-[.destructive]:ring-offset-red-600 dark:text-slate-400 dark:hover:text-slate-50",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
toast-close=""
|
toast-close=""
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export interface CodeProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Code = ({ children }: CodeProps): JSX.Element => (
|
export const Code = ({ children }: CodeProps): JSX.Element => (
|
||||||
<code className="relative rounded bg-slate-100 px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold text-slate-900 dark:bg-slate-800 dark:text-slate-400">
|
<code className="relative rounded-sm bg-slate-100 px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold text-slate-900 dark:bg-slate-800 dark:text-slate-400">
|
||||||
{children}
|
{children}
|
||||||
</code>
|
</code>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ export interface PProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const P = ({ children }: PProps): JSX.Element => (
|
export const P = ({ children }: PProps): JSX.Element => (
|
||||||
<p className="leading-7 [&:not(:first-child)]:mt-6">{children}</p>
|
<p className="leading-7 not-first:mt-6">{children}</p>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const Mono = ({
|
|||||||
}: JSX.IntrinsicElements["span"]): JSX.Element => {
|
}: JSX.IntrinsicElements["span"]): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={`font-mono text-sm text-textSecondary ${className ?? ""}`}
|
className={`font-mono text-sm text-text-secondary ${className ?? ""}`}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const Table = ({ headings, rows }: TableProps): JSX.Element => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<table className="min-w-full">
|
<table className="min-w-full">
|
||||||
<thead className="bg-backgroundPrimary text-sm font-semibold text-textPrimary">
|
<thead className="bg-backgound-primary text-sm font-semibold text-text-primary">
|
||||||
<tr>
|
<tr>
|
||||||
{headings.map((heading) => (
|
{headings.map((heading) => (
|
||||||
<th
|
<th
|
||||||
@@ -92,7 +92,7 @@ export const Table = ({ headings, rows }: TableProps): JSX.Element => {
|
|||||||
{row.map((item, index) => (
|
{row.map((item, index) => (
|
||||||
<td
|
<td
|
||||||
key={item.key ?? index}
|
key={item.key ?? index}
|
||||||
className="whitespace-nowrap py-2 text-sm text-textSecondary first:pl-2"
|
className="whitespace-nowrap py-2 text-sm text-text-secondary first:pl-2"
|
||||||
>
|
>
|
||||||
{item}
|
{item}
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import { useAppStore } from "@core/stores/appStore.ts";
|
|
||||||
import type { ReactNode } from "react";
|
|
||||||
|
|
||||||
export interface ThemeControllerProps {
|
|
||||||
children: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ThemeController = ({
|
|
||||||
children,
|
|
||||||
}: ThemeControllerProps): JSX.Element => {
|
|
||||||
const { darkMode, accent } = useAppStore();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div data-theme={darkMode ? "dark" : "light"} data-accent={accent}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import type React from "react";
|
||||||
|
import { createContext, useContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
type Theme = "light" | "dark" | "system";
|
||||||
|
|
||||||
|
interface ThemeContextType {
|
||||||
|
theme: Theme;
|
||||||
|
setTheme: (theme: Theme) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const [theme, setTheme] = useState<Theme>(() => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
const savedTheme = localStorage.getItem("theme") as Theme;
|
||||||
|
return savedTheme || "system";
|
||||||
|
}
|
||||||
|
return "system";
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const root = window.document.documentElement;
|
||||||
|
root.classList.remove("light", "dark");
|
||||||
|
|
||||||
|
if (theme === "system") {
|
||||||
|
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
|
||||||
|
.matches
|
||||||
|
? "dark"
|
||||||
|
: "light";
|
||||||
|
root.classList.add(systemTheme);
|
||||||
|
} else {
|
||||||
|
root.classList.add(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem("theme", theme);
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
|
||||||
|
const handleChange = () => {
|
||||||
|
if (theme === "system") {
|
||||||
|
const root = window.document.documentElement;
|
||||||
|
root.classList.remove("light", "dark");
|
||||||
|
root.classList.add(mediaQuery.matches ? "dark" : "light");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaQuery.addEventListener("change", handleChange);
|
||||||
|
return () => mediaQuery.removeEventListener("change", handleChange);
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeContext.Provider value={{ theme, setTheme }}>
|
||||||
|
{children}
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTheme() {
|
||||||
|
const context = useContext(ThemeContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error("useTheme must be used within a ThemeProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
type Theme = "light" | "dark";
|
||||||
|
|
||||||
|
export function useTheme() {
|
||||||
|
const [theme, setTheme] = useState<Theme>(() => {
|
||||||
|
if (typeof window === "undefined") return "light";
|
||||||
|
return (
|
||||||
|
(document.documentElement.getAttribute("data-theme") as Theme) || "light"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
for (const mutation of mutations) {
|
||||||
|
if (
|
||||||
|
mutation.type === "attributes" &&
|
||||||
|
mutation.attributeName === "data-theme"
|
||||||
|
) {
|
||||||
|
const newTheme = document.documentElement.getAttribute(
|
||||||
|
"data-theme",
|
||||||
|
) as Theme;
|
||||||
|
setTheme(newTheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ["data-theme"],
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
@@ -9,15 +9,6 @@ export interface RasterSource {
|
|||||||
tileSize: number;
|
tileSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AccentColor =
|
|
||||||
| "red"
|
|
||||||
| "orange"
|
|
||||||
| "yellow"
|
|
||||||
| "green"
|
|
||||||
| "blue"
|
|
||||||
| "purple"
|
|
||||||
| "pink";
|
|
||||||
|
|
||||||
interface AppState {
|
interface AppState {
|
||||||
selectedDevice: number;
|
selectedDevice: number;
|
||||||
devices: {
|
devices: {
|
||||||
@@ -26,9 +17,7 @@ interface AppState {
|
|||||||
}[];
|
}[];
|
||||||
rasterSources: RasterSource[];
|
rasterSources: RasterSource[];
|
||||||
commandPaletteOpen: boolean;
|
commandPaletteOpen: boolean;
|
||||||
darkMode: boolean;
|
|
||||||
nodeNumToBeRemoved: number;
|
nodeNumToBeRemoved: number;
|
||||||
accent: AccentColor;
|
|
||||||
connectDialogOpen: boolean;
|
connectDialogOpen: boolean;
|
||||||
nodeNumDetails: number;
|
nodeNumDetails: number;
|
||||||
activeChat: number;
|
activeChat: number;
|
||||||
@@ -42,9 +31,7 @@ interface AppState {
|
|||||||
addDevice: (device: { id: number; num: number }) => void;
|
addDevice: (device: { id: number; num: number }) => void;
|
||||||
removeDevice: (deviceId: number) => void;
|
removeDevice: (deviceId: number) => void;
|
||||||
setCommandPaletteOpen: (open: boolean) => void;
|
setCommandPaletteOpen: (open: boolean) => void;
|
||||||
setDarkMode: (enabled: boolean) => void;
|
|
||||||
setNodeNumToBeRemoved: (nodeNum: number) => void;
|
setNodeNumToBeRemoved: (nodeNum: number) => void;
|
||||||
setAccent: (color: AccentColor) => void;
|
|
||||||
setConnectDialogOpen: (open: boolean) => void;
|
setConnectDialogOpen: (open: boolean) => void;
|
||||||
setNodeNumDetails: (nodeNum: number) => void;
|
setNodeNumDetails: (nodeNum: number) => void;
|
||||||
setActiveChat: (chat: number) => void;
|
setActiveChat: (chat: number) => void;
|
||||||
@@ -57,11 +44,6 @@ export const useAppStore = create<AppState>()((set) => ({
|
|||||||
currentPage: "messages",
|
currentPage: "messages",
|
||||||
rasterSources: [],
|
rasterSources: [],
|
||||||
commandPaletteOpen: false,
|
commandPaletteOpen: false,
|
||||||
darkMode:
|
|
||||||
localStorage.getItem("theme-dark") !== null
|
|
||||||
? localStorage.getItem("theme-dark") === "true"
|
|
||||||
: window.matchMedia("(prefers-color-scheme: dark)").matches,
|
|
||||||
accent: "orange",
|
|
||||||
connectDialogOpen: false,
|
connectDialogOpen: false,
|
||||||
nodeNumToBeRemoved: 0,
|
nodeNumToBeRemoved: 0,
|
||||||
nodeNumDetails: 0,
|
nodeNumDetails: 0,
|
||||||
@@ -108,25 +90,10 @@ export const useAppStore = create<AppState>()((set) => ({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
setDarkMode: (enabled: boolean) => {
|
|
||||||
localStorage.setItem("theme-dark", enabled.toString());
|
|
||||||
set(
|
|
||||||
produce<AppState>((draft) => {
|
|
||||||
draft.darkMode = enabled;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
setNodeNumToBeRemoved: (nodeNum) =>
|
setNodeNumToBeRemoved: (nodeNum) =>
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
nodeNumToBeRemoved: nodeNum,
|
nodeNumToBeRemoved: nodeNum,
|
||||||
})),
|
})),
|
||||||
setAccent(color) {
|
|
||||||
set(
|
|
||||||
produce<AppState>((draft) => {
|
|
||||||
draft.accent = color;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
setConnectDialogOpen: (open: boolean) => {
|
setConnectDialogOpen: (open: boolean) => {
|
||||||
set(
|
set(
|
||||||
produce<AppState>((draft) => {
|
produce<AppState>((draft) => {
|
||||||
@@ -134,6 +101,7 @@ export const useAppStore = create<AppState>()((set) => ({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
setNodeNumDetails: (nodeNum) =>
|
setNodeNumDetails: (nodeNum) =>
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
nodeNumDetails: nodeNum,
|
nodeNumDetails: nodeNum,
|
||||||
|
|||||||
+64
-70
@@ -1,101 +1,95 @@
|
|||||||
@tailwind base;
|
@import "tailwindcss";
|
||||||
@tailwind components;
|
@plugin "tailwindcss-animate";
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
:root {
|
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--font-mono: Cascadia Code, ui-monospace, SFMono-Regular, Menlo, Monaco,
|
||||||
|
Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
--font-sans: Inter var, ui-sans-serif, system-ui, sans-serif,
|
||||||
|
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
|
||||||
|
--animate-accordion-down: accordion-down 0.2s ease-out;
|
||||||
|
--animate-accordion-up: accordion-up 0.2s ease-out;
|
||||||
|
|
||||||
|
--color-background-primary: var(--backgroundPrimary);
|
||||||
|
--color-background-secondary: var(--backgroundSecondary);
|
||||||
|
--color-accent: var(--accent);
|
||||||
|
--color-accent-muted: var(--accentMuted);
|
||||||
|
--color-text-primary: var(--textPrimary);
|
||||||
|
--color-text-secondary: var(--textSecondary);
|
||||||
|
--color-link: var(--link);
|
||||||
|
|
||||||
|
--brightness-hover: var(--brightnessHover);
|
||||||
|
--brightness-press: var(--brightnessPress);
|
||||||
|
--brightness-disabled: var(--brightnessDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light"] {
|
||||||
--backgroundPrimary: #ffffff;
|
--backgroundPrimary: #ffffff;
|
||||||
--backgroundSecondary: #e6e9ed;
|
|
||||||
--textPrimary: #111132;
|
--textPrimary: #111132;
|
||||||
--textSecondary: #64748b;
|
--textSecondary: #64748b;
|
||||||
--link: #0b69bf;
|
--link: #0b69bf;
|
||||||
|
|
||||||
--brighnessHover: 0.95;
|
--brightnessHover: 0.95;
|
||||||
--brightnessPress: 1.05;
|
--brightnessPress: 1.05;
|
||||||
--brightnessDisabled: 0.75;
|
--brightnessDisabled: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
--backgroundPrimary: #0f172a;
|
--backgroundPrimary: #0f172a;
|
||||||
--backgroundSecondary: #363638;
|
|
||||||
--textPrimary: #ebebeb;
|
--textPrimary: #ebebeb;
|
||||||
--textSecondary: #bdbdbd;
|
--textSecondary: #bdbdbd;
|
||||||
--link: #8ec9ff;
|
--link: #8ec9ff;
|
||||||
|
|
||||||
--brighnessHover: 1.1;
|
--brightnessHover: 1.1;
|
||||||
--brightnessPress: 0.9;
|
--brightnessPress: 0.9;
|
||||||
--brightnessDisabled: 0.75;
|
--brightnessDisabled: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-accent="red"] {
|
/* Accordion Animations */
|
||||||
--accent: #f28585;
|
@keyframes accordion-down {
|
||||||
--accentMuted: #f4abab;
|
from {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
height: var(--radix-accordion-content-height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-accent="red"][data-theme="dark"] {
|
@keyframes accordion-up {
|
||||||
--accent: #f25555;
|
from {
|
||||||
--accentMuted: #b04749;
|
height: var(--radix-accordion-content-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-accent="orange"] {
|
@layer base {
|
||||||
--accent: #edb17a;
|
*,
|
||||||
--accentMuted: #efc7a4;
|
::after,
|
||||||
|
::before,
|
||||||
|
::backdrop,
|
||||||
|
::file-selector-button {
|
||||||
|
border-color: var(--color-gray-200, currentColor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-accent="orange"][data-theme="dark"] {
|
@layer components {
|
||||||
--accent: #e1720b;
|
.maplibregl-popup-close-button {
|
||||||
--accentMuted: #a55c17;
|
padding: 4px 10px 8px 0;
|
||||||
}
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
[data-accent="yellow"] {
|
|
||||||
--accent: #e0cc87;
|
.maplibregl-popup-close-button:hover {
|
||||||
--accentMuted: #e8daad;
|
background-color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-accent="yellow"][data-theme="dark"] {
|
|
||||||
--accent: #ac8c1a;
|
|
||||||
--accentMuted: #826c22;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-accent="green"] {
|
|
||||||
--accent: #8bc9c5;
|
|
||||||
--accentMuted: #afd7d5;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-accent="green"][data-theme="dark"] {
|
|
||||||
--accent: #27a341;
|
|
||||||
--accentMuted: #297b3b;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-accent="blue"] {
|
|
||||||
--accent: #70afea;
|
|
||||||
--accentMuted: #9cc7ee;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-accent="blue"][data-theme="dark"] {
|
|
||||||
--accent: #2093fe;
|
|
||||||
--accentMuted: #2471ba;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-accent="purple"] {
|
|
||||||
--accent: #a09eef;
|
|
||||||
--accentMuted: #bcbcf1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-accent="purple"][data-theme="dark"] {
|
|
||||||
--accent: #926bff;
|
|
||||||
--accentMuted: #7057bb;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-accent="pink"] {
|
|
||||||
--accent: #dba0c7;
|
|
||||||
--accentMuted: #e3bcd7;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-accent="pink"][data-theme="dark"] {
|
|
||||||
--accent: #e454c4;
|
|
||||||
--accentMuted: #a84892;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Prevent image dragging */
|
||||||
img {
|
img {
|
||||||
-webkit-user-drag: none;
|
-webkit-user-drag: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,9 +52,13 @@ const ChannelsPage = () => {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Tabs defaultValue="0">
|
<Tabs defaultValue="0">
|
||||||
<TabsList>
|
<TabsList className="dark:bg-slate-800 ">
|
||||||
{allChannels.map((channel) => (
|
{allChannels.map((channel) => (
|
||||||
<TabsTrigger key={channel.index} value={channel.index.toString()}>
|
<TabsTrigger
|
||||||
|
key={channel.index}
|
||||||
|
value={channel.index.toString()}
|
||||||
|
className="dark:text-white"
|
||||||
|
>
|
||||||
{getChannelName(channel)}
|
{getChannelName(channel)}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -12,11 +12,8 @@ import {
|
|||||||
TabsList,
|
TabsList,
|
||||||
TabsTrigger,
|
TabsTrigger,
|
||||||
} from "@components/UI/Tabs.tsx";
|
} from "@components/UI/Tabs.tsx";
|
||||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
|
||||||
|
|
||||||
export const DeviceConfig = (): JSX.Element => {
|
export const DeviceConfig = (): JSX.Element => {
|
||||||
const { metadata } = useDevice();
|
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
label: "Device",
|
label: "Device",
|
||||||
@@ -56,9 +53,13 @@ export const DeviceConfig = (): JSX.Element => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs defaultValue="Device">
|
<Tabs defaultValue="Device">
|
||||||
<TabsList>
|
<TabsList className="dark:bg-slate-800">
|
||||||
{tabs.map((tab) => (
|
{tabs.map((tab) => (
|
||||||
<TabsTrigger key={tab.label} value={tab.label}>
|
<TabsTrigger
|
||||||
|
key={tab.label}
|
||||||
|
value={tab.label}
|
||||||
|
className="dark:text-white"
|
||||||
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -71,9 +71,13 @@ export const ModuleConfig = (): JSX.Element => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs defaultValue="MQTT">
|
<Tabs defaultValue="MQTT">
|
||||||
<TabsList>
|
<TabsList className="dark:bg-slate-800">
|
||||||
{tabs.map((tab) => (
|
{tabs.map((tab) => (
|
||||||
<TabsTrigger key={tab.label} value={tab.label}>
|
<TabsTrigger
|
||||||
|
key={tab.label}
|
||||||
|
value={tab.label}
|
||||||
|
className="dark:text-white"
|
||||||
|
>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const Dashboard = () => {
|
|||||||
<li key={device.id}>
|
<li key={device.id}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`w-full px-4 py-4 sm:px-6 ${darkMode ? "hover:bg-slate-800 focus:bg-slate-400 active:bg-slate-600" : "hover:bg-slate-50 focus:bg-slate-50 active:bg-slate-100"}`}
|
className="w-full px-4 py-4 sm:px-6"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedDevice(device.id);
|
setSelectedDevice(device.id);
|
||||||
}}
|
}}
|
||||||
@@ -89,11 +89,12 @@ export const Dashboard = () => {
|
|||||||
</ul>
|
</ul>
|
||||||
) : (
|
) : (
|
||||||
<div className="m-auto flex flex-col gap-3 text-center">
|
<div className="m-auto flex flex-col gap-3 text-center">
|
||||||
<ListPlusIcon size={48} className="mx-auto text-textSecondary" />
|
<ListPlusIcon size={48} className="mx-auto text-text-secondary" />
|
||||||
<H3>No Devices</H3>
|
<H3>No Devices</H3>
|
||||||
<Subtle>Connect at least one device to get started</Subtle>
|
<Subtle>Connect at least one device to get started</Subtle>
|
||||||
<Button
|
<Button
|
||||||
className="gap-2"
|
className="gap-2"
|
||||||
|
variant={"default"}
|
||||||
onClick={() => setConnectDialogOpen(true)}
|
onClick={() => setConnectDialogOpen(true)}
|
||||||
>
|
>
|
||||||
<PlusIcon size={16} />
|
<PlusIcon size={16} />
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { NodeDetail } from "@app/components/PageComponents/Map/NodeDetail";
|
import { NodeDetail } from "@app/components/PageComponents/Map/NodeDetail";
|
||||||
import { Avatar } from "@app/components/UI/Avatar";
|
import { Avatar } from "@app/components/UI/Avatar";
|
||||||
|
import { useTheme } from "@app/core/hooks/useTheme";
|
||||||
import { PageLayout } from "@components/PageLayout.tsx";
|
import { PageLayout } from "@components/PageLayout.tsx";
|
||||||
import { Sidebar } from "@components/Sidebar.tsx";
|
import { Sidebar } from "@components/Sidebar.tsx";
|
||||||
import { useAppStore } from "@core/stores/appStore.ts";
|
|
||||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
import { useDevice } from "@core/stores/deviceStore.ts";
|
||||||
import type { Protobuf } from "@meshtastic/js";
|
import type { Protobuf } from "@meshtastic/js";
|
||||||
import { bbox, lineString } from "@turf/turf";
|
import { bbox, lineString } from "@turf/turf";
|
||||||
|
import { current } from "immer";
|
||||||
import { MapPinIcon } from "lucide-react";
|
import { MapPinIcon } from "lucide-react";
|
||||||
import { type JSX, useCallback, useEffect, useMemo, useState } from "react";
|
import { type JSX, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -34,9 +35,11 @@ const convertToLatLng = (position: {
|
|||||||
|
|
||||||
const MapPage = (): JSX.Element => {
|
const MapPage = (): JSX.Element => {
|
||||||
const { nodes, waypoints } = useDevice();
|
const { nodes, waypoints } = useDevice();
|
||||||
const { darkMode } = useAppStore();
|
const currentTheme = useTheme();
|
||||||
const { default: map } = useMap();
|
const { default: map } = useMap();
|
||||||
|
|
||||||
|
const darkMode = currentTheme === "dark";
|
||||||
|
|
||||||
const [selectedNode, setSelectedNode] =
|
const [selectedNode, setSelectedNode] =
|
||||||
useState<Protobuf.Mesh.NodeInfo | null>(null);
|
useState<Protobuf.Mesh.NodeInfo | null>(null);
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ export const MessagesPage = () => {
|
|||||||
placeholder="Search nodes..."
|
placeholder="Search nodes..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full p-2 border border-gray-300 rounded bg-white text-black"
|
className="w-full p-2 border border-gray-300 rounded-sm bg-white text-slate-900"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
@@ -88,7 +88,7 @@ export const MessagesPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</SidebarSection>
|
</SidebarSection>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
<div className="flex flex-col flex-grow">
|
<div className="flex flex-col grow">
|
||||||
<PageLayout
|
<PageLayout
|
||||||
label={`Messages: ${
|
label={`Messages: ${
|
||||||
chatType === "broadcast" && currentChannel
|
chatType === "broadcast" && currentChannel
|
||||||
|
|||||||
+1
-2
@@ -4,7 +4,6 @@ import { TracerouteResponseDialog } from "@app/components/Dialog/TracerouteRespo
|
|||||||
import Footer from "@app/components/UI/Footer";
|
import Footer from "@app/components/UI/Footer";
|
||||||
import { Sidebar } from "@components/Sidebar.tsx";
|
import { Sidebar } from "@components/Sidebar.tsx";
|
||||||
import { Avatar } from "@components/UI/Avatar.tsx";
|
import { Avatar } from "@components/UI/Avatar.tsx";
|
||||||
import { Button } from "@components/UI/Button.tsx";
|
|
||||||
import { Mono } from "@components/generic/Mono.tsx";
|
import { Mono } from "@components/generic/Mono.tsx";
|
||||||
import { Table } from "@components/generic/Table/index.tsx";
|
import { Table } from "@components/generic/Table/index.tsx";
|
||||||
import { TimeAgo } from "@components/generic/TimeAgo.tsx";
|
import { TimeAgo } from "@components/generic/TimeAgo.tsx";
|
||||||
@@ -79,7 +78,7 @@ const NodesPage = (): JSX.Element => {
|
|||||||
placeholder="Search nodes..."
|
placeholder="Search nodes..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full p-2 border border-gray-300 rounded bg-white text-black"
|
className="w-full p-2 border border-gray-300 rounded-sm bg-white text-slate-900"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="overflow-y-auto h-full">
|
<div className="overflow-y-auto h-full">
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
const { fontFamily } = require("tailwindcss/defaultTheme");
|
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
module.exports = {
|
|
||||||
darkMode: ["class", '[data-theme="dark"]'],
|
|
||||||
content: ["./index.html", "./src/**/*.{ts,tsx}"],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
fontFamily: {
|
|
||||||
mono: ["Cascadia Code", ...fontFamily.mono],
|
|
||||||
sans: ["Inter var", ...fontFamily.sans],
|
|
||||||
},
|
|
||||||
keyframes: {
|
|
||||||
"accordion-down": {
|
|
||||||
from: { height: 0 },
|
|
||||||
to: { height: "var(--radix-accordion-content-height)" },
|
|
||||||
},
|
|
||||||
"accordion-up": {
|
|
||||||
from: { height: "var(--radix-accordion-content-height)" },
|
|
||||||
to: { height: 0 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
"accordion-down": "accordion-down 0.2s ease-out",
|
|
||||||
"accordion-up": "accordion-up 0.2s ease-out",
|
|
||||||
},
|
|
||||||
colors: {
|
|
||||||
backgroundPrimary: "var(--backgroundPrimary)",
|
|
||||||
backgroundSecondary: "var(--backgroundSecondary)",
|
|
||||||
accent: "var(--accent)",
|
|
||||||
accentMuted: "var(--accentMuted)",
|
|
||||||
textPrimary: "var(--textPrimary)",
|
|
||||||
textSecondary: "var(--textSecondary)",
|
|
||||||
link: "var(--link)",
|
|
||||||
},
|
|
||||||
brightness: {
|
|
||||||
hover: "var(--brighnessHover)",
|
|
||||||
press: "var(--brightnessPress)",
|
|
||||||
disabled: "var(--brightnessDisabled)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [require("tailwindcss-animate")],
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user