feat: Pattern Matching TextField + added Minecraft settings page

This commit is contained in:
LynithDev 2024-06-18 22:01:48 +02:00
parent af177152f0
commit ff989fba41
4 changed files with 105 additions and 5 deletions

View file

@ -1,8 +1,13 @@
.textfield { .textfield {
@apply bg-component-bg [&:not(:focus-within)]:hover:bg-component-bg-hover focus-within:bg-component-bg-pressed disabled:bg-component-bg-disabled; @apply bg-component-bg [&:not(:focus-within)]:hover:bg-component-bg-hover focus-within:bg-component-bg-pressed disabled:bg-component-bg-disabled;
@apply border border-gray-05 text-fg-primary fill-fg-primary; @apply border border-gray-05 text-fg-primary fill-fg-primary;
@apply rounded-lg px-2 py-1 max-h-8; @apply rounded-lg px-2.5 py-1.5 max-h-8;
@apply flex flex-row items-center justify-start gap-x-1.5; @apply flex flex-row items-center justify-start gap-x-1.5;
@apply text-sm;
&.invalid {
@apply border-danger;
}
& input { & input {
&::selection { &::selection {

View file

@ -1,23 +1,57 @@
import type { JSX } from 'solid-js'; import type { JSX } from 'solid-js';
import { createUniqueId, splitProps } from 'solid-js'; import { createEffect, createSignal, createUniqueId, splitProps } from 'solid-js';
import styles from './TextField.module.scss'; import styles from './TextField.module.scss';
type TextFieldProps = { type TextFieldProps = {
iconLeft?: JSX.Element; iconLeft?: JSX.Element;
iconRight?: JSX.Element; iconRight?: JSX.Element;
inputFilter?: (value: string) => boolean;
onValidInput?: (value: string) => any;
onValidSubmit?: (value: string) => any;
} & JSX.InputHTMLAttributes<HTMLInputElement>; } & JSX.InputHTMLAttributes<HTMLInputElement>;
function TextField(props: TextFieldProps) { function TextField(props: TextFieldProps) {
const [fieldProps, rest] = splitProps(props, ['iconLeft', 'iconRight']); const [fieldProps, rest] = splitProps(props, ['iconLeft', 'iconRight', 'inputFilter', 'onValidInput', 'onValidSubmit']);
const [isValid, setIsValid] = createSignal(true);
const id = createUniqueId(); const id = createUniqueId();
function validate(e: Event & { currentTarget: HTMLInputElement }) {
if (!fieldProps.inputFilter)
return;
const value = e.currentTarget.value;
const valid = fieldProps.inputFilter(value);
setIsValid(valid);
if (valid && fieldProps.onValidInput)
fieldProps.onValidInput(value);
}
function onSubmit(e: Event & { currentTarget: HTMLInputElement }) {
if (isValid() && fieldProps.onValidSubmit)
fieldProps.onValidSubmit(e.currentTarget.value);
}
return ( return (
<label for={id} class={styles.textfield}> <label for={id} class={`${styles.textfield} ${isValid() ? {} : styles.invalid}`}>
{fieldProps.iconLeft && <span class={styles.icon}>{fieldProps.iconLeft}</span>} {fieldProps.iconLeft && <span class={styles.icon}>{fieldProps.iconLeft}</span>}
<input id={id} type="text" {...rest} /> <input
id={id}
type="text"
onInput={validate}
onChange={onSubmit}
{...rest}
/>
{fieldProps.iconRight && <span class={styles.icon}>{fieldProps.iconRight}</span>} {fieldProps.iconRight && <span class={styles.icon}>{fieldProps.iconRight}</span>}
</label> </label>
); );
} }
TextField.Number = (props: TextFieldProps) => (
<TextField
inputFilter={value => /^\d*$/.test(value)}
{...props}
/>
);
export default TextField; export default TextField;

View file

@ -5,15 +5,22 @@ import Sidebar from '../../components/Sidebar';
import SettingsGeneral from './launcher/SettingsGeneral'; import SettingsGeneral from './launcher/SettingsGeneral';
import SettingsAppearance from './launcher/SettingsAppearance'; import SettingsAppearance from './launcher/SettingsAppearance';
import SettingsAccounts from './game/SettingsAccounts'; import SettingsAccounts from './game/SettingsAccounts';
import SettingsMinecraft from './game/SettingsMinecraft';
import AnimatedRoutes from '~ui/components/AnimatedRoutes'; import AnimatedRoutes from '~ui/components/AnimatedRoutes';
import ErrorBoundary from '~ui/components/ErrorBoundary'; import ErrorBoundary from '~ui/components/ErrorBoundary';
function SettingsRoutes() { function SettingsRoutes() {
return ( return (
<> <>
{/* Launcher Settings */}
<Route path="/" component={SettingsGeneral} /> <Route path="/" component={SettingsGeneral} />
<Route path="/appearance" component={SettingsAppearance} /> <Route path="/appearance" component={SettingsAppearance} />
{/* Game Settings */}
<Route path="/minecraft" component={SettingsMinecraft} />
<Route path="/accounts" component={SettingsAccounts} /> <Route path="/accounts" component={SettingsAccounts} />
{/* About */}
</> </>
); );
} }

View file

@ -0,0 +1,54 @@
import { LayoutTopIcon, Maximize01Icon, XIcon } from '@untitled-theme/icons-solid';
import TextField from '~ui/components/base/TextField';
import Toggle from '~ui/components/base/Toggle';
import ScrollableContainer from '~ui/components/ScrollableContainer';
import SettingsRow from '~ui/components/SettingsRow';
import Sidebar from '~ui/components/Sidebar';
import useSettingsContext from '~ui/hooks/useSettings';
function SettingsMinecraft() {
const settings = useSettingsContext();
return (
<Sidebar.Page>
<h1>Global Game Settings</h1>
<ScrollableContainer>
<SettingsRow
title="Force Fullscreen"
description="Force Minecraft to start in fullscreen mode"
icon={<Maximize01Icon />}
>
<Toggle
defaultChecked={settings.force_fullscreen ?? false}
onChecked={value => settings.force_fullscreen = value}
/>
</SettingsRow>
<SettingsRow
title="Resolution"
description="The game window resolution"
icon={<LayoutTopIcon />}
>
<div class="grid grid-justify-center grid-items-center grid-cols-[70px_24px_70px]">
<TextField.Number
value={settings.resolution[0]}
onValidSubmit={(value) => {
settings.resolution = [Number.parseInt(value), settings.resolution[1]];
}}
/>
<XIcon />
<TextField.Number
onValidSubmit={(value) => {
settings.resolution = [settings.resolution[0], Number.parseInt(value)];
}}
/>
</div>
</SettingsRow>
</ScrollableContainer>
</Sidebar.Page>
);
}
export default SettingsMinecraft;