Refactor layout components to improve responsiveness and accessibility; integrate appShell classes for consistent styling across DesktopAIShell and BottomNav. Update SelectPage to enhance option selection UI with image support and language filtering logic in SystemPage for better localization handling.

This commit is contained in:
yangy
2026-05-25 16:30:40 +08:00
parent 961c3b4de9
commit c34571392d
18 changed files with 130 additions and 55 deletions
+65 -19
View File
@@ -13,8 +13,12 @@
import { useLocation } from "wouter";
import { ChevronLeft, Check } from "lucide-react";
import { useDevice } from "@/contexts/DeviceContext";
import { cn } from "@/lib/utils";
import { useEffect, useState } from "react";
const iisModeImageUrl = (index: number) =>
`${import.meta.env.BASE_URL}IIS/iis_app${index + 1}.png`;
/* ── Key → API field mapping ── */
const KEY_TO_SETTING: Record<string, string> = {
effect_value: "effect_value",
@@ -118,26 +122,68 @@ export default function SelectPage() {
<div className="w-8" />
</div>
{/* ── Options list ── */}
<div className="px-4 pt-4 pb-24">
<div className="ios-list-group">
{options.map((opt, idx) => {
const isSelected = idx === selectedIdx;
return (
<button
key={idx}
className="ios-list-row w-full text-left active:bg-white/5 transition-colors"
onClick={() => void handleSelect(idx)}>
<span className={`flex-1 text-[16px] ${isSelected ? "text-white font-medium" : "text-white/75"}`}>
{opt}
</span>
{isSelected && (
<Check size={18} style={{ color: "#00FFF6" }} />
)}
</button>
);
})}
</div>
{key === "IISMode" ? (
<div className="grid w-full grid-cols-2 gap-3">
{options.map((opt, idx) => {
const isSelected = idx === selectedIdx;
return (
<button
key={idx}
type="button"
disabled={saving}
className={cn(
"flex flex-col items-center gap-2 rounded-xl border-2 p-1.5 transition-all active:scale-[0.98] disabled:opacity-60",
isSelected
? "border-[#00FFF6] shadow-[0_0_14px_rgba(0,255,246,0.35)]"
: "border-white/10",
)}
onClick={() => void handleSelect(idx)}
aria-pressed={isSelected}
aria-label={opt}
>
<img
src={iisModeImageUrl(idx)}
alt={opt}
className="block w-full h-auto rounded-lg bg-black/40"
loading="lazy"
/>
<span
className={cn(
"w-6 h-6 rounded-full flex items-center justify-center shrink-0",
isSelected
? "bg-[#00FFF6] text-black shadow-[0_0_10px_rgba(0,255,246,0.45)]"
: "border-2 border-white/20",
)}
>
{isSelected && <Check size={12} strokeWidth={3} />}
</span>
</button>
);
})}
</div>
) : (
<div className="ios-list-group">
{options.map((opt, idx) => {
const isSelected = idx === selectedIdx;
return (
<button
key={idx}
className="ios-list-row w-full text-left active:bg-white/5 transition-colors"
onClick={() => void handleSelect(idx)}
disabled={saving}
>
<span className={`flex-1 text-[16px] ${isSelected ? "text-white font-medium" : "text-white/75"}`}>
{opt}
</span>
{isSelected && (
<Check size={18} style={{ color: "#00FFF6" }} />
)}
</button>
);
})}
</div>
)}
</div>
</div>
);
+19 -2
View File
@@ -107,6 +107,18 @@ function optionLabels(items: Array<{ index: number; label: string }> | undefined
return sorted.map((item) => item.label);
}
/** Device language index: 0=en, 1=zh-HK, 2=zh-CN */
const SUPPORTED_LANGUAGE_INDICES = new Set([0, 1, 2]);
function languageLabels(
items: Array<{ index: number; label: string }> | undefined,
fallback: string[],
) {
const filtered = (items ?? []).filter((o) => SUPPORTED_LANGUAGE_INDICES.has(o.index));
if (filtered.length === 0) return fallback;
return [...filtered].sort((a, b) => a.index - b.index).map((o) => o.label);
}
export default function SystemPage() {
const [, setLocation] = useLocation();
const { deviceState, updateSetting, refresh, isConnected, api, ip } = useDevice();
@@ -124,7 +136,8 @@ export default function SystemPage() {
const screenOffIdx = ds?.screenOff ?? 2;
const sleepIdx = ds?.sleep ?? 0;
const autoHomeIdx = ds?.autoHome ?? 1;
const langIdx = ds?.language ?? 2;
const rawLangIdx = ds?.language ?? 2;
const langIdx = SUPPORTED_LANGUAGE_INDICES.has(rawLangIdx) ? rawLangIdx : 2;
const knobBreathLightOn = (ds?.knob_breathlight ?? 1) === 0;
const localeData = LOCALE_BY_LANG[langIdx] ?? localeZh;
const settingsText = localeData.settings;
@@ -134,7 +147,11 @@ export default function SystemPage() {
const SCREEN_OFF_OPTIONS = optionLabels(settingsText.turnOffScreen?.options, ["常亮", "30秒", "1分钟", "3分钟", "5分钟"]);
const SLEEP_OPTIONS = optionLabels(settingsText.sleepTime?.options ?? settingsText.sleep?.options, ["关闭", "1分钟", "5分钟", "10分钟"]);
const AUTO_HOME_OPTIONS = optionLabels(settingsText.Automatic?.options, ["关闭", "20秒", "40秒", "60秒"]);
const LANG_OPTIONS = optionLabels(settingsText.language?.options, ["English", "繁體中文", "简体中文"]);
const LANG_OPTIONS = languageLabels(settingsText.language?.options, [
"English",
"繁體中文",
"简体中文",
]);
const sleepRowLabel =
settingsText.sleepTime?.label ??