feat: Added interface options

This commit is contained in:
Simeon Radivoev 2026-03-04 13:18:18 +02:00
parent 4739b89933
commit 2f32cbc730
Signed by: simeonradivoev
GPG key ID: 7611A451D2A5D37A
25 changed files with 327 additions and 74 deletions

View file

@ -4,7 +4,7 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { RouterContext } from "..";
import Notifications from "../components/Notifications";
import { Toaster } from "react-hot-toast";
import { mobileCheck } from "../scripts/utils";
import { mobileCheck, useLocalSetting } from "../scripts/utils";
export const Route = createRootRouteWithContext<RouterContext>()({
component: RootComponent,
@ -13,9 +13,10 @@ export const Route = createRootRouteWithContext<RouterContext>()({
function RootComponent ()
{
const isMobile = mobileCheck();
const theme = useLocalSetting('theme');
return (
<div className="w-screen h-screen overflow-hidden">
<div data-theme={theme === 'auto' ? undefined : theme} className="w-screen h-screen overflow-hidden">
<Outlet />
<Notifications />
<Toaster containerStyle={{ viewTimelineName: 'toasters' }} />

View file

@ -113,6 +113,7 @@ function RouteComponent ()
{
const { focus } = Route.useSearch();
const { ref, focusKey, focusSelf } = useFocusable({
focusKey: "accounts",
preferredChildFocusKey: focus
});

View file

@ -41,7 +41,7 @@ function DriveComponent (data: { drive: DownloadsDrive; downloadsSize: number; r
return <li ref={ref} className={twMerge('flex flex-row p-4 bg-base-300 rounded-2xl gap-1 items-end',
classNames({
"ring-7": focused,
"ring-7 ring-accent": focused,
"border-dashed border-primary border-4": data.drive.isCurrentlyUsed,
"border-solid": data.drive.unusableReason === 'already_used',
"ring-error": data.drive.unusableReason === 'not_enough_space',
@ -75,6 +75,7 @@ function RouteComponent ()
{
const { focus } = Route.useSearch();
const { ref, focusKey, focusSelf } = useFocusable({
focusKey: "directories",
preferredChildFocusKey: focus
});

View file

@ -32,11 +32,14 @@ function EmulatorsPending ()
function EmulatorListCat (data: { selected: string, set: (c: string) => void; })
{
const { ref, focusKey } = useFocusable({ focusKey: 'categories' });
const { ref, focused, focusKey } = useFocusable({ focusKey: 'categories' });
return <ul className='flex gap-1' ref={ref}>
<FocusContext value={focusKey}>
{[..."ABCDEFGHIJKLMNOPQRSTVWXYZ"].map(c =>
<OptionElement key={c} className={classNames('p-2 justify-center size-8 text-base-content bg-base-300 text-lg', { "ring-4 ring-primary": data.selected === c })} onFocus={() => data.set(c)} content={c} id={c} action={(ctx) => ctx.focus()} type="primary" />
<OptionElement key={c} className={twMerge('p-2 justify-center size-8 text-base-content bg-base-300 text-lg',
classNames({
"ring-4 ring-primary": data.selected === c,
}))} onFocus={() => data.set(c)} content={c} id={c} action={(ctx) => ctx.focus()} type="primary" />
)}
</FocusContext>
</ul>;
@ -47,7 +50,7 @@ function EmulatorListType (data: { category: string, action: (e: string) => void
const { ref, focusKey } = useFocusable({ focusKey: 'list-section' });
return <div ref={ref} className='grow'>
<FocusContext value={focusKey}>
<ContextList className='h-[60vh]' options={Object.keys(emulators).filter(e => e.startsWith(data.category)).map(e => ({
<ContextList className='sm:h-[80vh] md:h-[60vh] overflow-auto' options={Object.keys(emulators).filter(e => e.startsWith(data.category)).map(e => ({
id: e,
action: (ctx) =>
{
@ -152,7 +155,12 @@ function EmulatorPath (data: { id: string; })
};
return (
<OptionSpace label={<><p className='font-semibold'>{data.id}</p><small className='text-base-content/40'>{emulators[data.id]}</small></>}>
<OptionSpace label={
focus => <>
<p className='font-semibold'>{data.id}</p>
<small className='opacity-40'>{emulators[data.id]}</small>
</>
}>
<div className='flex gap-2'>
<OptionInput
name={data.id ?? ""}
@ -160,9 +168,9 @@ function EmulatorPath (data: { id: string; })
onBlur={handleSave}
autocomplete="off"
defaultValue={remoteValue}
onChange={(e) =>
onChange={(v) =>
{
setLocalValue(e.currentTarget.value);
setLocalValue(v);
setDirty(true);
}}
value={localValue}
@ -223,8 +231,9 @@ function EmulatorBadge (data: {
<div ref={ref} className={
twMerge('flex flex-col rounded-3xl bg-base-300 w-64 h-16 justify-center items-center p-4 overflow-hidden',
classNames({
"bg-base-200/50": !data.path,
"border-dashed border-base-content/40 border-2": focused
"bg-base-200": !data.path,
"border-dashed border-base-content/40 border-2": !data.path && !focused,
"border-dashed border-accent border-4": focused
}))
}>
@ -253,6 +262,7 @@ function RouteComponent ()
{
const { focus } = Route.useSearch();
const { ref, focusKey, focusSelf } = useFocusable({
focusKey: "emulators-setting",
preferredChildFocusKey: focus
});

View file

@ -0,0 +1,23 @@
import { LocalOption } from '@/mainview/components/options/LocalOption';
import { FocusContext, useFocusable } from '@noriginmedia/norigin-spatial-navigation';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/settings/interface')({
component: RouteComponent,
});
function RouteComponent ()
{
const { focus } = Route.useSearch();
const { ref, focusKey, focusSelf } = useFocusable({
focusKey: "interface-settings",
preferredChildFocusKey: focus
});
return <ul ref={ref} className="list rounded-box gap-2">
<FocusContext value={focusKey}>
<LocalOption id="backgroundBlur" label="Background Blur" type='checkbox'></LocalOption>
<LocalOption id="theme" label="Theme" type='dropdown' values={['dark', 'light', 'auto']}></LocalOption>
</FocusContext>
</ul>;
}

View file

@ -83,7 +83,7 @@ function MenuItem (data: {
"group rounded-full p-3 md:pl-5 text-base-content/80",
classNames({
"bg-primary text-primary-content": acitve,
"font-semibold sm:ring-4 md:ring-7 ring-primary-content": focused && !isPointer,
"font-semibold sm:ring-4 md:ring-7 ring-accent": focused && !isPointer,
"bg-secondary text-secondary-content ring-primary": data.return && focused,
}),
data.linkClassName,
@ -110,7 +110,7 @@ function SettingsMenu (data: {})
return <ul
ref={ref}
className="menu portrait:menu-horizontal md:menu-xl landscape:flex-nowrap bg-base-200 sm:p-2 md:p-4 sm:portrait:gap-0 sm:landscape:gap-0 md:gap-2! rounded-4xl overflow-auto portrait:w-full"
className="menu portrait:menu-horizontal md:menu-xl landscape:flex-nowrap bg-base-200 sm:p-2 md:p-4 sm:portrait:gap-0 sm:landscape:gap-0 md:landscape:w-128 md:gap-2! rounded-4xl overflow-auto portrait:w-full"
>
<FocusContext value={focusKey}>
<MenuItem
@ -184,7 +184,7 @@ export function SettingsUI ()
return (
<FocusContext.Provider value={focusKey}>
<div ref={ref} className="flex flex-col w-full h-full md:p-4 bg-base-100">
<div ref={ref} className="bg-base-100 flex flex-col w-full h-full md:p-4">
<div className="flex landscape:flex-row portrait:flex-col-reverse grow overflow-hidden">
<div id="Menu" className="flex flex-row landscape:h-full md:landscape:w-56">
<SettingsMenu />