feat: Pattern Matching TextField + added Minecraft settings page
This commit is contained in:
parent
af177152f0
commit
ff989fba41
4 changed files with 105 additions and 5 deletions
|
|
@ -1,8 +1,13 @@
|
|||
.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 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 text-sm;
|
||||
|
||||
&.invalid {
|
||||
@apply border-danger;
|
||||
}
|
||||
|
||||
& input {
|
||||
&::selection {
|
||||
|
|
|
|||
|
|
@ -1,23 +1,57 @@
|
|||
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';
|
||||
|
||||
type TextFieldProps = {
|
||||
iconLeft?: JSX.Element;
|
||||
iconRight?: JSX.Element;
|
||||
inputFilter?: (value: string) => boolean;
|
||||
onValidInput?: (value: string) => any;
|
||||
onValidSubmit?: (value: string) => any;
|
||||
} & JSX.InputHTMLAttributes<HTMLInputElement>;
|
||||
|
||||
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();
|
||||
|
||||
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 (
|
||||
<label for={id} class={styles.textfield}>
|
||||
<label for={id} class={`${styles.textfield} ${isValid() ? {} : styles.invalid}`}>
|
||||
{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>}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
TextField.Number = (props: TextFieldProps) => (
|
||||
<TextField
|
||||
inputFilter={value => /^\d*$/.test(value)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
export default TextField;
|
||||
|
|
|
|||
|
|
@ -5,15 +5,22 @@ import Sidebar from '../../components/Sidebar';
|
|||
import SettingsGeneral from './launcher/SettingsGeneral';
|
||||
import SettingsAppearance from './launcher/SettingsAppearance';
|
||||
import SettingsAccounts from './game/SettingsAccounts';
|
||||
import SettingsMinecraft from './game/SettingsMinecraft';
|
||||
import AnimatedRoutes from '~ui/components/AnimatedRoutes';
|
||||
import ErrorBoundary from '~ui/components/ErrorBoundary';
|
||||
|
||||
function SettingsRoutes() {
|
||||
return (
|
||||
<>
|
||||
{/* Launcher Settings */}
|
||||
<Route path="/" component={SettingsGeneral} />
|
||||
<Route path="/appearance" component={SettingsAppearance} />
|
||||
|
||||
{/* Game Settings */}
|
||||
<Route path="/minecraft" component={SettingsMinecraft} />
|
||||
<Route path="/accounts" component={SettingsAccounts} />
|
||||
|
||||
{/* About */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
54
desktop/src/ui/pages/settings/game/SettingsMinecraft.tsx
Normal file
54
desktop/src/ui/pages/settings/game/SettingsMinecraft.tsx
Normal 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;
|
||||
Loading…
Add table
Reference in a new issue