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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/routes/__root.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Outlet, createRootRoute, Link, Scripts, HeadContent } from '@tanstack/react-router'
// import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
import * as React from 'react'
import { useEffect } from 'react'
import { APP_CONFIG, THEMES } from '../config'
import '../styles.css'

export const Route = createRootRoute({
Expand All @@ -24,6 +25,16 @@ function RootComponent() {
)
}

function ThemeInit() {
useEffect(() => {
if (typeof localStorage === 'undefined') return;
const saved = localStorage.getItem(APP_CONFIG.THEME_STORAGE_KEY);
const theme = saved === THEMES.LIGHT || saved === THEMES.DARK ? saved : THEMES.DEFAULT;
document.documentElement.setAttribute('data-theme', theme);
}, []);
return null;
}

function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html>
Expand All @@ -34,7 +45,8 @@ function RootDocument({ children }: { children: React.ReactNode }) {
<title>Rein Remote</title>
<link rel="manifest" href="/manifest.json" />
</head>
<body className="bg-neutral-900 text-white overflow-hidden overscroll-none">
<body className="bg-base-200 text-base-content overflow-hidden overscroll-none">
<ThemeInit />
<div className="flex flex-col h-[100dvh]">
<Navbar />
<main className="flex-1 overflow-hidden relative">
Expand Down
124 changes: 79 additions & 45 deletions src/routes/settings.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createFileRoute } from '@tanstack/react-router'
import { useState, useEffect } from 'react'
import QRCode from 'qrcode';
import { CONFIG } from '../config';
import { CONFIG, APP_CONFIG, THEMES } from '../config';

export const Route = createFileRoute('/settings')({
component: SettingsPage,
Expand Down Expand Up @@ -29,6 +29,16 @@ function SettingsPage() {
return Number.isFinite(parsed) ? parsed : 1.0;
});

const [theme, setTheme] = useState(() => {
if (typeof window === 'undefined') return THEMES.DEFAULT;
try {
const saved = localStorage.getItem(APP_CONFIG.THEME_STORAGE_KEY);
return saved === THEMES.LIGHT || saved === THEMES.DARK ? saved : THEMES.DEFAULT;
} catch {
return THEMES.DEFAULT;
}
});

const [qrData, setQrData] = useState('');

// Load initial state
Expand All @@ -49,6 +59,12 @@ function SettingsPage() {
localStorage.setItem('rein_invert', JSON.stringify(invertScroll));
}, [invertScroll]);

useEffect(() => {
if (typeof window === 'undefined') return;
localStorage.setItem(APP_CONFIG.THEME_STORAGE_KEY, theme);
document.documentElement.setAttribute('data-theme', theme);
}, [theme]);

// Effect: Update LocalStorage and Generate QR
useEffect(() => {
if (!ip) return;
Expand Down Expand Up @@ -123,50 +139,6 @@ function SettingsPage() {
</label>
</div>

<div className="form-control w-full max-w-2xl mx-auto">
<label className="label" htmlFor="sensitivity-slider">
<span className="label-text">Mouse Sensitivity</span>
<span className="label-text-alt font-mono">
{sensitivity.toFixed(1)}x
</span>
</label>

<input
type="range"
id="sensitivity-slider"
min="0.1"
max="3.0"
step="0.1"
value={sensitivity}
onChange={(e) => setSensitivity(parseFloat(e.target.value))}
className="range range-primary range-sm w-full"
/>

<div className="mt-2 flex w-full justify-between px-2 text-xs opacity-50">
<span>Slow</span>
<span>Default</span>
<span>Fast</span>
</div>
</div>

<div className="form-control w-full">
<label className="label cursor-pointer">
<span className="label-text font-medium">Invert Scroll</span>
<input
type="checkbox"
className="toggle toggle-primary"
checked={invertScroll}
onChange={(e) => setInvertScroll(e.target.checked)}
/>
</label>
<br />
<label className="label">
<span className="label-text-alt opacity-50">
{invertScroll ? 'Traditional scrolling enabled' : 'Natural scrolling'}
</span>
</label>
</div>

<div className="form-control w-full">
<label className="label">
<span className="label-text">Port</span>
Expand Down Expand Up @@ -218,6 +190,68 @@ function SettingsPage() {
Save Config
</button>

<div className="divider"></div>

<h2 className="text-xl font-semibold">Client Settings</h2>

<div className="form-control w-full max-w-2xl mx-auto">
<label className="label" htmlFor="sensitivity-slider">
<span className="label-text">Mouse Sensitivity</span>
<span className="label-text-alt font-mono">
{sensitivity.toFixed(1)}x
</span>
</label>

<input
type="range"
id="sensitivity-slider"
min="0.1"
max="3.0"
step="0.1"
value={sensitivity}
onChange={(e) => setSensitivity(parseFloat(e.target.value))}
className="range range-primary range-sm w-full"
/>

<div className="mt-2 flex w-full justify-between px-2 text-xs opacity-50">
<span>Slow</span>
<span>Default</span>
<span>Fast</span>
</div>
</div>

<div className="form-control w-full">
<label className="label cursor-pointer">
<span className="label-text font-medium">Invert Scroll</span>
<input
type="checkbox"
className="toggle toggle-primary"
checked={invertScroll}
onChange={(e) => setInvertScroll(e.target.checked)}
/>
</label>
<br />
<label className="label">
<span className="label-text-alt opacity-50">
{invertScroll ? 'Traditional scrolling enabled' : 'Natural scrolling'}
</span>
</label>
Comment on lines +234 to +238
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Accessibility: labels not associated with their controls.

The static analysis tool correctly flags these two <label> elements. The helper-text label at line 234 and the "Theme" label at line 242 lack htmlFor attributes and don't wrap their associated controls.

For the Theme label, add htmlFor pointing to the <select>, and give the select an id. The helper-text label for Invert Scroll is purely decorative — consider using a <span> or <p> instead of <label>.

Proposed fix
-                    <label className="label">
+                    <span className="label">
                         <span className="label-text-alt opacity-50">
                             {invertScroll ? 'Traditional scrolling enabled' : 'Natural scrolling'}
                         </span>
-                    </label>
+                    </span>
-                    <label className="label">
+                    <label className="label" htmlFor="theme-select">
                         <span className="label-text">Theme</span>
                     </label>
                     <select
                         className="select select-bordered w-full"
+                        id="theme-select"
                         value={theme}
                         onChange={(e) => setTheme(e.target.value)}
                     >

Also applies to: 242-244

🧰 Tools
🪛 Biome (2.3.14)

[error] 234-238: A form label must be associated with an input.

Consider adding a for or htmlFor attribute to the label element or moving the input element to inside the label element.

(lint/a11y/noLabelWithoutControl)

🤖 Prompt for AI Agents
In `@src/routes/settings.tsx` around lines 234 - 238, The helper label currently
rendering the invertScroll helper text should be changed to a non-form label
element (e.g., a <span> or <p>) so it isn't treated as a form label, and the
Theme label must be associated with its control by adding htmlFor on the label
and a matching id on the <select> element (update the Theme label and the
corresponding <select> in the settings component, referencing the invertScroll
helper text and the theme <select> control by their existing JSX
locations/names) — replace the decorative <label> for invertScroll with a <span>
or <p>, and add id="theme" (or another unique id) to the <select> and
htmlFor="theme" to the Theme <label>.

</div>

<div className="form-control w-full">
<label className="label">
<span className="label-text">Theme</span>
</label>
<select
className="select select-bordered w-full"
value={theme}
onChange={(e) => setTheme(e.target.value)}
>
<option value={THEMES.DARK}>Dark</option>
<option value={THEMES.LIGHT}>Light</option>
</select>
</div>

<div className="card bg-base-200 shadow-xl">
<div className="card-body items-center text-center">
<h2 className="card-title">Connect Mobile</h2>
Expand Down