Browse Source

feat: updated app to tailwind 4.x

pull/417/head
Dan Ditomaso 1 year ago
parent
commit
b959a59e7b
  1. 2
      index.html
  2. 43
      package.json
  3. 1588
      pnpm-lock.yaml
  4. 3
      postcss.config.cjs
  5. 14
      src/App.tsx
  6. 2
      src/PageRouter.tsx
  7. 116
      src/components/CommandPalette.tsx
  8. 24
      src/components/DeviceSelector.tsx
  9. 6
      src/components/Dialog/ImportDialog.tsx
  10. 6
      src/components/Dialog/NewDeviceDialog.tsx
  11. 4
      src/components/Dialog/QRDialog.tsx
  12. 7
      src/components/Form/DynamicForm.tsx
  13. 2
      src/components/PageComponents/Connect/BLE.tsx
  14. 7
      src/components/PageComponents/Connect/HTTP.tsx
  15. 2
      src/components/PageComponents/Connect/Serial.tsx
  16. 12
      src/components/PageComponents/Map/NodeDetail.tsx
  17. 4
      src/components/PageComponents/Messages/ChannelChat.tsx
  18. 8
      src/components/PageComponents/Messages/Message.tsx
  19. 2
      src/components/PageComponents/Messages/MessageInput.tsx
  20. 4
      src/components/PageComponents/Messages/TraceRoute.tsx
  21. 2
      src/components/PageLayout.tsx
  22. 2
      src/components/Sidebar.tsx
  23. 39
      src/components/ThemeSwitcher.tsx
  24. 6
      src/components/UI/Button.tsx
  25. 2
      src/components/UI/Checkbox.tsx
  26. 6
      src/components/UI/Command.tsx
  27. 15
      src/components/UI/Dialog.tsx
  28. 8
      src/components/UI/DropdownMenu.tsx
  29. 15
      src/components/UI/Footer.tsx
  30. 2
      src/components/UI/Generator.tsx
  31. 12
      src/components/UI/Input.tsx
  32. 10
      src/components/UI/Menubar.tsx
  33. 2
      src/components/UI/MultiSelect.tsx
  34. 2
      src/components/UI/Popover.tsx
  35. 8
      src/components/UI/Select.tsx
  36. 2
      src/components/UI/Sidebar/sidebarButton.tsx
  37. 2
      src/components/UI/Switch.tsx
  38. 4
      src/components/UI/Tabs.tsx
  39. 10
      src/components/UI/Toast.tsx
  40. 2
      src/components/UI/Typography/Code.tsx
  41. 2
      src/components/UI/Typography/P.tsx
  42. 2
      src/components/generic/Mono.tsx
  43. 4
      src/components/generic/Table/index.tsx
  44. 18
      src/components/generic/ThemeController.tsx
  45. 67
      src/components/generic/ThemeProvider.tsx
  46. 37
      src/core/hooks/useTheme.ts
  47. 34
      src/core/stores/appStore.ts
  48. 128
      src/index.css
  49. 8
      src/pages/Channels.tsx
  50. 11
      src/pages/Config/DeviceConfig.tsx
  51. 8
      src/pages/Config/ModuleConfig.tsx
  52. 5
      src/pages/Dashboard/index.tsx
  53. 7
      src/pages/Map/index.tsx
  54. 4
      src/pages/Messages.tsx
  55. 3
      src/pages/Nodes.tsx
  56. 44
      tailwind.config.cjs

2
index.html

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-theme="system">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/icon.svg" />

43
package.json

@ -40,20 +40,20 @@
"@bufbuild/protobuf": "^1.10.0",
"@meshtastic/js": "2.3.7-5",
"@noble/curves": "^1.8.1",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-checkbox": "^1.1.3",
"@radix-ui/react-dialog": "^1.1.5",
"@radix-ui/react-dropdown-menu": "^2.1.5",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-menubar": "^1.1.5",
"@radix-ui/react-popover": "^1.1.5",
"@radix-ui/react-scroll-area": "^1.2.2",
"@radix-ui/react-select": "^2.1.5",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.2",
"@radix-ui/react-toast": "^1.2.5",
"@radix-ui/react-tooltip": "^1.1.7",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.6",
"@radix-ui/react-label": "^2.1.2",
"@radix-ui/react-menubar": "^1.1.6",
"@radix-ui/react-popover": "^1.1.6",
"@radix-ui/react-scroll-area": "^1.2.3",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-switch": "^1.1.3",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-toast": "^1.2.6",
"@radix-ui/react-tooltip": "^1.1.8",
"@turf/turf": "^7.2.0",
"base64-js": "^1.5.1",
"class-validator": "^0.14.1",
@ -63,7 +63,7 @@
"crypto-random-string": "^5.0.0",
"immer": "^10.1.1",
"js-cookie": "^3.0.5",
"lucide-react": "^0.474.0",
"lucide-react": "^0.475.0",
"mapbox-gl": "^3.9.4",
"maplibre-gl": "4.1.2",
"react": "^19.0.0",
@ -77,11 +77,12 @@
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@types/chrome": "^0.0.299",
"@tailwindcss/postcss": "^4.0.7",
"@types/chrome": "^0.0.304",
"@types/js-cookie": "^3.0.6",
"@types/node": "^22.12.0",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@types/node": "^22.13.4",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@types/w3c-web-serial": "^1.0.7",
"@types/web-bluetooth": "^0.0.20",
"@vitejs/plugin-react": "^4.3.4",
@ -89,8 +90,8 @@
"gzipper": "^8.2.0",
"postcss": "^8.5.1",
"simple-git-hooks": "^2.11.1",
"tailwind-merge": "^2.6.0",
"tailwindcss": "^3.4.17",
"tailwind-merge": "^3.0.1",
"tailwindcss": "^4.0.7",
"tailwindcss-animate": "^1.0.7",
"tar": "^7.4.3",
"typescript": "^5.7.3",

1588
pnpm-lock.yaml

File diff suppressed because it is too large

3
postcss.config.cjs

@ -1,6 +1,5 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
'@tailwindcss/postcss': {},
},
};

14
src/App.tsx

@ -1,5 +1,6 @@
import { DeviceWrapper } from "@app/DeviceWrapper.tsx";
import { PageRouter } from "@app/PageRouter.tsx";
import { ThemeProvider } from "@app/components/generic/ThemeProvider";
import { CommandPalette } from "@components/CommandPalette.tsx";
import { DeviceSelector } from "@components/DeviceSelector.tsx";
import { DialogManager } from "@components/Dialog/DialogManager";
@ -7,10 +8,10 @@ import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.tsx";
import { KeyBackupReminder } from "@components/KeyBackupReminder";
import { Toaster } from "@components/Toaster.tsx";
import Footer from "@components/UI/Footer.tsx";
import { ThemeController } from "@components/generic/ThemeController.tsx";
import { useAppStore } from "@core/stores/appStore.ts";
import { useDeviceStore } from "@core/stores/deviceStore.ts";
import { Dashboard } from "@pages/Dashboard/index.tsx";
import type { JSX } from "react";
import { MapProvider } from "react-map-gl";
export const App = (): JSX.Element => {
@ -21,7 +22,7 @@ export const App = (): JSX.Element => {
const device = getDevice(selectedDevice);
return (
<ThemeController>
<>
<NewDeviceDialog
open={connectDialogOpen}
onOpenChange={(open) => {
@ -31,10 +32,10 @@ export const App = (): JSX.Element => {
<Toaster />
<MapProvider>
<DeviceWrapper device={device}>
<div className="flex h-screen flex-col overflow-hidden bg-backgroundPrimary text-textPrimary">
<div className="flex flex-grow">
<div className="flex h-screen flex-col overflow-hidden bg-background-primary text-text-primary">
<div className="flex grow">
<DeviceSelector />
<div className="flex flex-grow flex-col">
<div className="flex grow flex-col">
{device ? (
<div className="flex h-screen">
<DialogManager />
@ -45,7 +46,6 @@ export const App = (): JSX.Element => {
) : (
<>
<Dashboard />
<div className="flex flex-grow" />
<Footer />
</>
)}
@ -54,6 +54,6 @@ export const App = (): JSX.Element => {
</div>
</DeviceWrapper>
</MapProvider>
</ThemeController>
</>
);
};

2
src/PageRouter.tsx

@ -1,7 +1,7 @@
import MapPage from "@app/pages/Map/index.tsx";
import { useDevice } from "@core/stores/deviceStore.ts";
import ChannelsPage from "@pages/Channels.tsx";
import ConfigPage from "@pages/Config/index.tsx";
import MapPage from "@pages/Map.tsx";
import MessagesPage from "@pages/Messages.tsx";
import NodesPage from "@pages/Nodes.tsx";

116
src/components/CommandPalette.tsx

@ -17,13 +17,10 @@ import {
EraserIcon,
FactoryIcon,
LayersIcon,
LayoutIcon,
LinkIcon,
type LucideIcon,
MapIcon,
MessageSquareIcon,
MoonIcon,
PaletteIcon,
PlusIcon,
PowerIcon,
QrCodeIcon,
@ -62,9 +59,6 @@ export const CommandPalette = () => {
setSelectedDevice,
removeDevice,
selectedDevice,
darkMode,
setDarkMode,
setAccent,
} = useAppStore();
const { getDevices } = useDeviceStore();
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(() => {

24
src/components/DeviceSelector.tsx

@ -3,15 +3,9 @@ import { Separator } from "@components/UI/Seperator.tsx";
import { Code } from "@components/UI/Typography/Code.tsx";
import { useAppStore } from "@core/stores/appStore.ts";
import { useDeviceStore } from "@core/stores/deviceStore.ts";
import {
HomeIcon,
LanguagesIcon,
MoonIcon,
PlusIcon,
SearchIcon,
SunIcon,
} from "lucide-react";
import { HomeIcon, PlusIcon, SearchIcon } from "lucide-react";
import type { JSX } from "react";
import ThemeSwitcher from "./ThemeSwitcher";
import { Avatar } from "./UI/Avatar";
export const DeviceSelector = (): JSX.Element => {
@ -19,14 +13,12 @@ export const DeviceSelector = (): JSX.Element => {
const {
selectedDevice,
setSelectedDevice,
darkMode,
setDarkMode,
setCommandPaletteOpen,
setConnectDialogOpen,
} = useAppStore();
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">
<ul className="flex w-20 grow flex-col items-center space-y-4 bg-transparent py-4 px-5">
<DeviceSelectorButton
@ -65,13 +57,14 @@ export const DeviceSelector = (): JSX.Element => {
</ul>
</div>
<div className="flex w-20 flex-col items-center space-y-5 bg-transparent px-5 pb-5">
<button
<ThemeSwitcher />
{/* <button
type="button"
className="transition-all hover:text-accent"
onClick={() => setDarkMode(!darkMode)}
>
{darkMode ? <SunIcon /> : <MoonIcon />}
</button>
</button> */}
<button
type="button"
className="transition-all hover:text-accent"
@ -79,9 +72,10 @@ export const DeviceSelector = (): JSX.Element => {
>
<SearchIcon />
</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 />
</button>
</button> */}
<Separator />
<Code>{process.env.COMMIT_HASH}</Code>
</div>

6
src/components/Dialog/ImportDialog.tsx

@ -14,7 +14,7 @@ import { Switch } from "@components/UI/Switch.tsx";
import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/js";
import { toByteArray } from "base64-js";
import { useEffect, useState } from "react";
import { type JSX, useEffect, useState } from "react";
export interface ImportDialogProps {
open: boolean;
@ -65,7 +65,7 @@ export const ImportDialog = ({
}, [importDialogInput]);
const apply = () => {
channelSet?.settings.map((ch, index) => {
channelSet?.settings.map((ch: unknown, index: number) => {
connection?.setChannel(
new Protobuf.Channel.Channel({
index,
@ -134,7 +134,7 @@ export const ImportDialog = ({
{renderOptions(Protobuf.Config_LoRaConfig_RegionCode)}
</Select> */}
<span className="text-md block font-medium text-textPrimary">
<span className="text-md block font-medium text-text-primary">
Channels:
</span>
<div className="flex w-40 flex-col gap-1">

6
src/components/Dialog/NewDeviceDialog.tsx

@ -19,7 +19,7 @@ import {
} from "@components/UI/Tabs.tsx";
import { Subtle } from "@components/UI/Typography/Subtle.tsx";
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";
export interface TabElementProps {
@ -78,9 +78,9 @@ const ErrorMessage = ({ missingFeatures }: FeatureErrorProps) => {
};
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">
<AlertCircle size={40} className="mr-2 flex-shrink-0" />
<AlertCircle size={40} className="mr-2 shrink-0" />
<div className="flex flex-col gap-3">
<p className="text-sm">
{browserFeatures.length > 0 && (

4
src/components/Dialog/QRDialog.tsx

@ -100,7 +100,7 @@ export const QRDialog = ({
<div className="flex justify-center">
<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
? "focus:ring-green-800 bg-green-800 text-white"
: "focus:ring-slate-400 bg-slate-400 hover:bg-green-600"
@ -111,7 +111,7 @@ export const QRDialog = ({
</button>
<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
? "focus:ring-green-800 bg-green-800 text-white"
: "focus:ring-slate-400 bg-slate-400 hover:bg-green-600"

7
src/components/Form/DynamicForm.tsx

@ -84,7 +84,7 @@ export function DynamicForm<T extends FieldValues>({
return (
<form
className="space-y-8 divide-y divide-gray-200"
className="space-y-8"
{...(submitType === "onSubmit"
? { onSubmit: handleSubmit(onSubmit) }
: {
@ -92,10 +92,7 @@ export function DynamicForm<T extends FieldValues>({
})}
>
{fieldGroups.map((fieldGroup) => (
<div
key={fieldGroup.label}
className="space-y-8 divide-y divide-gray-200 sm:space-y-5"
>
<div key={fieldGroup.label} className="space-y-8 sm:space-y-5">
<div>
<H4 className="font-medium">{fieldGroup.label}</H4>
<Subtle>{fieldGroup.description}</Subtle>

2
src/components/PageComponents/Connect/BLE.tsx

@ -41,6 +41,7 @@ export const BLE = ({ closeDialog }: TabElementProps): JSX.Element => {
{bleDevices.map((device) => (
<Button
key={device.id}
className="dark:bg-slate-900 dark:text-white"
onClick={() => {
onConnect(device);
}}
@ -53,6 +54,7 @@ export const BLE = ({ closeDialog }: TabElementProps): JSX.Element => {
)}
</div>
<Button
className="dark:bg-slate-900 dark:text-white"
onClick={async () => {
await navigator.bluetooth
.requestDevice({

7
src/components/PageComponents/Connect/HTTP.tsx

@ -57,6 +57,7 @@ export const HTTP = ({ closeDialog }: TabElementProps): JSX.Element => {
<Input
prefix={https ? "https://" : "http://"}
placeholder="000.000.000.000 / meshtastic.local"
className="text-black"
disabled={connectionInProgress}
{...register("ip")}
/>
@ -83,7 +84,11 @@ export const HTTP = ({ closeDialog }: TabElementProps): JSX.Element => {
)}
/>
</div>
<Button type="submit" disabled={connectionInProgress}>
<Button
type="submit"
disabled={connectionInProgress}
className="dark:bg-slate-900 dark:text-white"
>
<span>{connectionInProgress ? "Connecting..." : "Connect"}</span>
</Button>
</form>

2
src/components/PageComponents/Connect/Serial.tsx

@ -54,6 +54,7 @@ export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => {
<Button
key={`${usbVendorId ?? "UNK"}-${usbProductId ?? "UNK"}-${index}`}
disabled={port.readable !== null}
className="dark:bg-slate-900 dark:text-white"
onClick={async () => {
await onConnect(port);
}}
@ -69,6 +70,7 @@ export const Serial = ({ closeDialog }: TabElementProps): JSX.Element => {
)}
</div>
<Button
className="dark:bg-slate-900 dark:text-white"
onClick={async () => {
await navigator.serial.requestPort().then((port) => {
setSerialPorts(serialPorts.concat(port));

12
src/components/PageComponents/Map/NodeDetail.tsx

@ -31,7 +31,7 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
Protobuf.Mesh.HardwareModel[hwModel]?.replaceAll("_", " ") ?? `${hwModel}`;
return (
<div className="dark:text-black p-1">
<div className="dark:text-slate-900 p-1">
<div className="flex gap-2">
<div className="flex flex-col items-center gap-2 min-w-6 pt-1">
<Avatar text={node.user?.shortName} />
@ -120,14 +120,14 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
<Separator className="my-1" />
<div className="flex mt-2 text-sm">
<div className="flex items-center flex-grow">
<div className="border-2 border-black rounded px-0.5 mr-1">
<div className="flex items-center grow">
<div className="border-2 border-black rounded-sm px-0.5 mr-1">
{Number.isNaN(node.hopsAway) ? "?" : node.hopsAway}
</div>
<div>{node.hopsAway === 1 ? "Hop" : "Hops"}</div>
</div>
{node.position?.altitude && (
<div className="flex items-center flex-grow">
<div className="flex items-center grow">
<MountainSnow
size={15}
className="ml-2 mr-1"
@ -145,7 +145,7 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
<div className="flex mt-2">
{!!node.deviceMetrics?.channelUtilization && (
<div className="flex-grow">
<div className="grow">
<div>Channel Util</div>
<Mono>
{node.deviceMetrics?.channelUtilization.toPrecision(3)}%
@ -153,7 +153,7 @@ export const NodeDetail = ({ node }: NodeDetailProps) => {
</div>
)}
{!!node.deviceMetrics?.airUtilTx && (
<div className="flex-grow">
<div className="grow">
<div>Airtime Util</div>
<Mono>{node.deviceMetrics?.airUtilTx.toPrecision(3)}%</Mono>
</div>

4
src/components/PageComponents/Messages/ChannelChat.tsx

@ -55,7 +55,7 @@ export const ChannelChat = ({
<div className="flex-1 flex items-center justify-center">
<EmptyState />
</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} />
</div>
</div>
@ -81,7 +81,7 @@ export const ChannelChat = ({
<div ref={messagesEndRef} className="w-full" />
</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} />
</div>
</div>

8
src/components/PageComponents/Messages/Message.tsx

@ -76,7 +76,7 @@ const StatusIcon = ({ state, className, ...otherProps }: StatusIconProps) => {
const isFailed = state === MESSAGE_STATES.FAILED;
const iconClass = cn(
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];
@ -99,8 +99,8 @@ const getMessageTextStyles = (state: MessageState) => {
return cn(
"break-words overflow-hidden",
isAcknowledged
? "text-black dark:text-white"
: "text-black dark:text-gray-400",
? "text-slate-900 dark:text-white"
: "text-slate-900 dark:text-gray-400",
isFailed && "text-red-500 dark:text-red-500",
);
};
@ -109,7 +109,7 @@ const TimeDisplay = ({
date,
className,
}: { 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">
{date.toLocaleDateString()}
</span>

2
src/components/PageComponents/Messages/MessageInput.tsx

@ -94,7 +94,7 @@ export const MessageInput = ({
});
}}
>
<div className="flex flex-grow gap-2">
<div className="flex grow gap-2">
<span className="w-full">
<Input
autoFocus={true}

4
src/components/PageComponents/Messages/TraceRoute.tsx

@ -24,7 +24,7 @@ export const TraceRoute = ({
return (
<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>{to?.user?.longName}</p>
<p> {snrTowards?.[0] ? snrTowards[0] : "??"}dB</p>
@ -39,7 +39,7 @@ export const TraceRoute = ({
{from?.user?.longName}
</span>
{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>{from?.user?.longName}</p>
<p> {snrBack?.[0] ? snrBack[0] : "??"}dB</p>

2
src/components/PageLayout.tsx

@ -22,7 +22,7 @@ export const PageLayout = ({
return (
<>
<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
type="button"
className="pl-4 transition-all hover:text-accent md:hidden"

2
src/components/Sidebar.tsx

@ -65,7 +65,7 @@ export const Sidebar = ({ children }: SidebarProps): JSX.Element => {
];
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>
<span className="text-lg font-medium">

39
src/components/ThemeSwitcher.tsx

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

6
src/components/UI/Button.tsx

@ -4,12 +4,12 @@ import * as React from "react";
import { cn } from "@core/utils/cn.ts";
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: {
variant: {
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:
"bg-red-500 text-white hover:bg-red-600 dark:hover:bg-red-600",
success:
@ -17,7 +17,7 @@ const buttonVariants = cva(
outline:
"bg-transparent border border-slate-200 hover:bg-slate-100 dark:border-slate-400 dark:text-slate-100",
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:
"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",

2
src/components/UI/Checkbox.tsx

@ -11,7 +11,7 @@ const Checkbox = React.forwardRef<
<CheckboxPrimitive.Root
ref={ref}
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,
)}
{...props}

6
src/components/UI/Command.tsx

@ -45,7 +45,7 @@ const CommandInput = React.forwardRef<
<CommandPrimitive.Input
ref={ref}
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,
)}
{...props}
@ -88,7 +88,7 @@ const CommandGroup = React.forwardRef<
<CommandPrimitive.Group
ref={ref}
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,
)}
{...props}
@ -116,7 +116,7 @@ const CommandItem = React.forwardRef<
<CommandPrimitive.Item
ref={ref}
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,
)}
{...props}

15
src/components/UI/Dialog.tsx

@ -26,7 +26,7 @@ const DialogOverlay = React.forwardRef<
>(({ className, children, ...props }, ref) => (
<DialogPrimitive.Overlay
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,
)}
{...props}
@ -44,14 +44,13 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
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",
"dark:bg-slate-900",
"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",
className,
)}
{...props}
>
{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" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
@ -66,7 +65,7 @@ const DialogHeader = ({
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
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,
)}
{...props}
@ -94,11 +93,7 @@ const DialogTitle = React.forwardRef<
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold text-slate-900",
"dark:text-slate-50",
className,
)}
className={cn("text-lg font-semibold text-slate-900", className)}
{...props}
/>
));

8
src/components/UI/DropdownMenu.tsx

@ -25,7 +25,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger
ref={ref}
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",
className,
)}
@ -81,7 +81,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item
ref={ref}
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",
className,
)}
@ -97,7 +97,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
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,
)}
checked={checked}
@ -121,7 +121,7 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem
ref={ref}
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,
)}
{...props}

15
src/components/UI/Footer.tsx

@ -1,3 +1,4 @@
import { cn } from "@app/core/utils/cn";
import React from "react";
export interface FooterProps extends React.HTMLAttributes<HTMLElement> {}
@ -5,26 +6,18 @@ export interface FooterProps extends React.HTMLAttributes<HTMLElement> {}
const Footer = React.forwardRef<HTMLElement, FooterProps>(
({ className, ...props }, ref) => {
return (
<footer
className={`flex mt-auto justify-center p-2 ${className}`}
style={{
backgroundColor: "var(--backgroundPrimary)",
color: "var(--textPrimary)",
}}
>
<footer className={cn("flex mt-auto justify-center p-2", className)}>
<p>
<a
href="https://vercel.com/?utm_source=meshtastic&utm_campaign=oss"
className="hover:underline"
style={{ color: "var(--link)" }}
className="hover:underline text-link"
>
Powered by Vercel
</a>{" "}
| Meshtastic® is a registered trademark of Meshtastic LLC. |{" "}
<a
href="https://meshtastic.org/docs/legal"
className="hover:underline"
style={{ color: "var(--link)" }}
className="hover:underline text-link"
>
Legal Information
</a>

2
src/components/UI/Generator.tsx

@ -86,7 +86,7 @@ const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>(
onValueChange={(e) => selectChange(e)}
disabled={disabled}
>
<SelectTrigger className="!max-w-max">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>

12
src/components/UI/Input.tsx

@ -5,7 +5,7 @@ import { type VariantProps, cva } from "class-variance-authority";
import type { LucideIcon } from "lucide-react";
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: {
variant: {
@ -35,29 +35,29 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
return (
<div className="relative w-full">
{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}
</span>
)}
<input
className={cn(
action && "pr-8",
className,
inputVariants({ variant }),
className,
)}
value={value}
ref={ref}
{...props}
/>
{suffix && (
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-9 font-mono text-textSecondary">
<span className="text-gray-500 sm:text-sm">{suffix}</span>
<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-100/40 sm:text-sm">{suffix}</span>
</div>
)}
{action && (
<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}
>
<action.icon size={20} />

10
src/components/UI/Menubar.tsx

@ -36,7 +36,7 @@ const MenubarTrigger = React.forwardRef<
<MenubarPrimitive.Trigger
ref={ref}
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,
)}
{...props}
@ -53,7 +53,7 @@ const MenubarSubTrigger = React.forwardRef<
<MenubarPrimitive.SubTrigger
ref={ref}
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",
className,
)}
@ -114,7 +114,7 @@ const MenubarItem = React.forwardRef<
<MenubarPrimitive.Item
ref={ref}
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",
className,
)}
@ -130,7 +130,7 @@ const MenubarCheckboxItem = React.forwardRef<
<MenubarPrimitive.CheckboxItem
ref={ref}
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,
)}
checked={checked}
@ -153,7 +153,7 @@ const MenubarRadioItem = React.forwardRef<
<MenubarPrimitive.RadioItem
ref={ref}
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,
)}
{...props}

2
src/components/UI/MultiSelect.tsx

@ -41,7 +41,7 @@ const MultiSelectItem = ({
inline-flex items-center rounded-md px-3 py-2 text-sm transition-colors
border border-slate-300
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`,
className,
)}

2
src/components/UI/Popover.tsx

@ -17,7 +17,7 @@ const PopoverContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
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,
)}
{...props}

8
src/components/UI/Select.tsx

@ -17,7 +17,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
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,
)}
{...props}
@ -36,7 +36,7 @@ const SelectContent = React.forwardRef<
<SelectPrimitive.Content
ref={ref}
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,
)}
{...props}
@ -56,7 +56,7 @@ const SelectLabel = React.forwardRef<
<SelectPrimitive.Label
ref={ref}
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,
)}
{...props}
@ -71,7 +71,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item
ref={ref}
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,
)}
{...props}

2
src/components/UI/Sidebar/sidebarButton.tsx

@ -25,6 +25,6 @@ export const SidebarButton = ({
>
{Icon && <Icon size={16} />}
{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>
);

2
src/components/UI/Switch.tsx

@ -9,7 +9,7 @@ const Switch = React.forwardRef<
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
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,
)}
{...props}

4
src/components/UI/Tabs.tsx

@ -12,7 +12,7 @@ const TabsList = React.forwardRef<
<TabsPrimitive.List
ref={ref}
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,
)}
{...props}
@ -26,7 +26,7 @@ const TabsTrigger = React.forwardRef<
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
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,
)}
{...props}

10
src/components/UI/Toast.tsx

@ -14,7 +14,7 @@ const ToastViewport = React.forwardRef<
<ToastPrimitives.Viewport
ref={ref}
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,
)}
{...props}
@ -23,12 +23,12 @@ const ToastViewport = React.forwardRef<
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
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: {
variant: {
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:
"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
ref={ref}
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,
)}
{...props}
@ -76,7 +76,7 @@ const ToastClose = React.forwardRef<
<ToastPrimitives.Close
ref={ref}
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,
)}
toast-close=""

2
src/components/UI/Typography/Code.tsx

@ -3,7 +3,7 @@ export interface CodeProps {
}
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}
</code>
);

2
src/components/UI/Typography/P.tsx

@ -3,5 +3,5 @@ export interface PProps {
}
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>
);

2
src/components/generic/Mono.tsx

@ -5,7 +5,7 @@ export const Mono = ({
}: JSX.IntrinsicElements["span"]): JSX.Element => {
return (
<span
className={`font-mono text-sm text-textSecondary ${className ?? ""}`}
className={`font-mono text-sm text-text-secondary ${className ?? ""}`}
{...rest}
>
{children}

4
src/components/generic/Table/index.tsx

@ -58,7 +58,7 @@ export const Table = ({ headings, rows }: TableProps): JSX.Element => {
return (
<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>
{headings.map((heading) => (
<th
@ -92,7 +92,7 @@ export const Table = ({ headings, rows }: TableProps): JSX.Element => {
{row.map((item, index) => (
<td
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}
</td>

18
src/components/generic/ThemeController.tsx

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

67
src/components/generic/ThemeProvider.tsx

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

37
src/core/hooks/useTheme.ts

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

34
src/core/stores/appStore.ts

@ -9,15 +9,6 @@ export interface RasterSource {
tileSize: number;
}
export type AccentColor =
| "red"
| "orange"
| "yellow"
| "green"
| "blue"
| "purple"
| "pink";
interface AppState {
selectedDevice: number;
devices: {
@ -26,9 +17,7 @@ interface AppState {
}[];
rasterSources: RasterSource[];
commandPaletteOpen: boolean;
darkMode: boolean;
nodeNumToBeRemoved: number;
accent: AccentColor;
connectDialogOpen: boolean;
nodeNumDetails: number;
activeChat: number;
@ -42,9 +31,7 @@ interface AppState {
addDevice: (device: { id: number; num: number }) => void;
removeDevice: (deviceId: number) => void;
setCommandPaletteOpen: (open: boolean) => void;
setDarkMode: (enabled: boolean) => void;
setNodeNumToBeRemoved: (nodeNum: number) => void;
setAccent: (color: AccentColor) => void;
setConnectDialogOpen: (open: boolean) => void;
setNodeNumDetails: (nodeNum: number) => void;
setActiveChat: (chat: number) => void;
@ -57,11 +44,6 @@ export const useAppStore = create<AppState>()((set) => ({
currentPage: "messages",
rasterSources: [],
commandPaletteOpen: false,
darkMode:
localStorage.getItem("theme-dark") !== null
? localStorage.getItem("theme-dark") === "true"
: window.matchMedia("(prefers-color-scheme: dark)").matches,
accent: "orange",
connectDialogOpen: false,
nodeNumToBeRemoved: 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) =>
set((state) => ({
nodeNumToBeRemoved: nodeNum,
})),
setAccent(color) {
set(
produce<AppState>((draft) => {
draft.accent = color;
}),
);
},
setConnectDialogOpen: (open: boolean) => {
set(
produce<AppState>((draft) => {
@ -134,6 +101,7 @@ export const useAppStore = create<AppState>()((set) => ({
}),
);
},
setNodeNumDetails: (nodeNum) =>
set((state) => ({
nodeNumDetails: nodeNum,

128
src/index.css

@ -1,101 +1,95 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss";
@plugin "tailwindcss-animate";
: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;
--backgroundSecondary: #e6e9ed;
--textPrimary: #111132;
--textSecondary: #64748b;
--link: #0b69bf;
--brighnessHover: 0.95;
--brightnessHover: 0.95;
--brightnessPress: 1.05;
--brightnessDisabled: 0.75;
}
[data-theme="dark"] {
--backgroundPrimary: #0f172a;
--backgroundSecondary: #363638;
--textPrimary: #ebebeb;
--textSecondary: #bdbdbd;
--link: #8ec9ff;
--brighnessHover: 1.1;
--brightnessHover: 1.1;
--brightnessPress: 0.9;
--brightnessDisabled: 0.75;
}
[data-accent="red"] {
--accent: #f28585;
--accentMuted: #f4abab;
}
[data-accent="red"][data-theme="dark"] {
--accent: #f25555;
--accentMuted: #b04749;
}
/* Accordion Animations */
@keyframes accordion-down {
from {
height: 0;
}
[data-accent="orange"] {
--accent: #edb17a;
--accentMuted: #efc7a4;
to {
height: var(--radix-accordion-content-height);
}
}
[data-accent="orange"][data-theme="dark"] {
--accent: #e1720b;
--accentMuted: #a55c17;
}
[data-accent="yellow"] {
--accent: #e0cc87;
--accentMuted: #e8daad;
}
@keyframes accordion-up {
from {
height: var(--radix-accordion-content-height);
}
[data-accent="yellow"][data-theme="dark"] {
--accent: #ac8c1a;
--accentMuted: #826c22;
to {
height: 0;
}
}
[data-accent="green"] {
--accent: #8bc9c5;
--accentMuted: #afd7d5;
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
[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;
}
@layer components {
.maplibregl-popup-close-button {
padding: 4px 10px 8px 0;
font-size: 1.2rem;
}
[data-accent="pink"][data-theme="dark"] {
--accent: #e454c4;
--accentMuted: #a84892;
.maplibregl-popup-close-button:hover {
background-color: white !important;
}
}
/* Prevent image dragging */
img {
-webkit-user-drag: none;
}

8
src/pages/Channels.tsx

@ -52,9 +52,13 @@ const ChannelsPage = () => {
]}
>
<Tabs defaultValue="0">
<TabsList>
<TabsList className="dark:bg-slate-800 ">
{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)}
</TabsTrigger>
))}

11
src/pages/Config/DeviceConfig.tsx

@ -12,11 +12,8 @@ import {
TabsList,
TabsTrigger,
} from "@components/UI/Tabs.tsx";
import { useDevice } from "@core/stores/deviceStore.ts";
export const DeviceConfig = (): JSX.Element => {
const { metadata } = useDevice();
const tabs = [
{
label: "Device",
@ -56,9 +53,13 @@ export const DeviceConfig = (): JSX.Element => {
return (
<Tabs defaultValue="Device">
<TabsList>
<TabsList className="dark:bg-slate-800">
{tabs.map((tab) => (
<TabsTrigger key={tab.label} value={tab.label}>
<TabsTrigger
key={tab.label}
value={tab.label}
className="dark:text-white"
>
{tab.label}
</TabsTrigger>
))}

8
src/pages/Config/ModuleConfig.tsx

@ -71,9 +71,13 @@ export const ModuleConfig = (): JSX.Element => {
return (
<Tabs defaultValue="MQTT">
<TabsList>
<TabsList className="dark:bg-slate-800">
{tabs.map((tab) => (
<TabsTrigger key={tab.label} value={tab.label}>
<TabsTrigger
key={tab.label}
value={tab.label}
className="dark:text-white"
>
{tab.label}
</TabsTrigger>
))}

5
src/pages/Dashboard/index.tsx

@ -41,7 +41,7 @@ export const Dashboard = () => {
<li key={device.id}>
<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={() => {
setSelectedDevice(device.id);
}}
@ -89,11 +89,12 @@ export const Dashboard = () => {
</ul>
) : (
<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>
<Subtle>Connect at least one device to get started</Subtle>
<Button
className="gap-2"
variant={"default"}
onClick={() => setConnectDialogOpen(true)}
>
<PlusIcon size={16} />

7
src/pages/Map.tsx → src/pages/Map/index.tsx

@ -1,11 +1,12 @@
import { NodeDetail } from "@app/components/PageComponents/Map/NodeDetail";
import { Avatar } from "@app/components/UI/Avatar";
import { useTheme } from "@app/core/hooks/useTheme";
import { PageLayout } from "@components/PageLayout.tsx";
import { Sidebar } from "@components/Sidebar.tsx";
import { useAppStore } from "@core/stores/appStore.ts";
import { useDevice } from "@core/stores/deviceStore.ts";
import type { Protobuf } from "@meshtastic/js";
import { bbox, lineString } from "@turf/turf";
import { current } from "immer";
import { MapPinIcon } from "lucide-react";
import { type JSX, useCallback, useEffect, useMemo, useState } from "react";
import {
@ -34,9 +35,11 @@ const convertToLatLng = (position: {
const MapPage = (): JSX.Element => {
const { nodes, waypoints } = useDevice();
const { darkMode } = useAppStore();
const currentTheme = useTheme();
const { default: map } = useMap();
const darkMode = currentTheme === "dark";
const [selectedNode, setSelectedNode] =
useState<Protobuf.Mesh.NodeInfo | null>(null);

4
src/pages/Messages.tsx

@ -62,7 +62,7 @@ export const MessagesPage = () => {
placeholder="Search nodes..."
value={searchTerm}
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 className="flex flex-col gap-4">
@ -88,7 +88,7 @@ export const MessagesPage = () => {
</div>
</SidebarSection>
</Sidebar>
<div className="flex flex-col flex-grow">
<div className="flex flex-col grow">
<PageLayout
label={`Messages: ${
chatType === "broadcast" && currentChannel

3
src/pages/Nodes.tsx

@ -4,7 +4,6 @@ import { TracerouteResponseDialog } from "@app/components/Dialog/TracerouteRespo
import Footer from "@app/components/UI/Footer";
import { Sidebar } from "@components/Sidebar.tsx";
import { Avatar } from "@components/UI/Avatar.tsx";
import { Button } from "@components/UI/Button.tsx";
import { Mono } from "@components/generic/Mono.tsx";
import { Table } from "@components/generic/Table/index.tsx";
import { TimeAgo } from "@components/generic/TimeAgo.tsx";
@ -79,7 +78,7 @@ const NodesPage = (): JSX.Element => {
placeholder="Search nodes..."
value={searchTerm}
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 className="overflow-y-auto h-full">

44
tailwind.config.cjs

@ -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")],
};
Loading…
Cancel
Save