From f45305bfb9943476710ee5a5b54d2b619d150641 Mon Sep 17 00:00:00 2001 From: Bloop <13398309+vinylspiders@users.noreply.github.com> Date: Sat, 6 Jan 2024 16:33:25 -0500 Subject: [PATCH] [MISSED MIRROR] Input changes take XII: Responsive small lists (#80720) (#346) Input changes take XII: Responsive small lists (#80720) ## About The Pull Request Admittedly it's been tough to find a sweet spot that will let us handle large lists and basic inputs. This PR aims to address this by adding the `expensive` prop on Input. This introduces the debounce that #80688 without compromising the responsiveness of smaller filters like those in orbit, giving the author the keys to monitor performance of their UIs. On a more human note inputs just feel better with this PR. Along with this change I've expanded documentation for the typescript types and included a discriminating union to let contributors know that the `expensive` prop requires `onInput` to work. ## Why It's Good For The Game Bug fixes, responsiveness, etc ## Changelog :cl: fix: Search bars for smaller lists should return to their former responsiveness. /:cl: Co-authored-by: Jeremiah <42397676+jlsnow301@users.noreply.github.com> --- tgui/docs/component-reference.md | 2 + tgui/packages/tgui/components/Input.tsx | 84 +++++++++++++++---- .../tgui/interfaces/CameraConsole.tsx | 2 + .../tgui/interfaces/common/SearchBar.tsx | 1 + 4 files changed, 73 insertions(+), 16 deletions(-) diff --git a/tgui/docs/component-reference.md b/tgui/docs/component-reference.md index 76598fc4674..7e0626f6814 100644 --- a/tgui/docs/component-reference.md +++ b/tgui/docs/component-reference.md @@ -556,6 +556,8 @@ A basic text input, which allow users to enter text into a UI. - `onEnter: (e, value) => void` - Fires when the user hits enter. - `onEscape: (e) => void` - Fires when the user hits escape. - `onInput: (e, value) => void` - Fires when the user types into the input. +- `expensive: boolean` - Introduces a delay before updating the input. Useful for large filters, + where you don't want to update on every keystroke. ### `Knob` diff --git a/tgui/packages/tgui/components/Input.tsx b/tgui/packages/tgui/components/Input.tsx index a8c9038022e..b4993914c5c 100644 --- a/tgui/packages/tgui/components/Input.tsx +++ b/tgui/packages/tgui/components/Input.tsx @@ -11,39 +11,81 @@ import { KeyboardEvent, SyntheticEvent, useEffect, useRef } from 'react'; import { Box, BoxProps } from './Box'; -type Props = Partial<{ +type ConditionalProps = + | { + /** + * Mark this if you want to debounce onInput. + * + * This is useful for expensive filters, large lists etc. + * + * Requires `onInput` to be set. + */ + expensive?: boolean; + /** + * Fires on each key press / value change. Used for searching. + * + * If it's a large list, consider using `expensive` prop. + */ + onInput: (event: SyntheticEvent, value: string) => void; + } + | { + /** This prop requires onInput to be set */ + expensive?: never; + onInput?: never; + }; + +type OptionalProps = Partial<{ + /** Automatically focuses the input on mount */ autoFocus: boolean; + /** Automatically selects the input value on focus */ autoSelect: boolean; + /** The class name of the input */ className: string; + /** Disables the input */ disabled: boolean; + /** Mark this if you want the input to be as wide as possible */ fluid: boolean; + /** The maximum length of the input value */ maxLength: number; + /** Mark this if you want to use a monospace font */ monospace: boolean; /** Fires when user is 'done typing': Clicked out, blur, enter key */ onChange: (event: SyntheticEvent, value: string) => void; /** Fires once the enter key is pressed */ - onEnter: (event: SyntheticEvent, value: string) => void; + onEnter?: (event: SyntheticEvent, value: string) => void; /** Fires once the escape key is pressed */ onEscape: (event: SyntheticEvent) => void; - /** Fires on each key press / value change. Used for searching */ - onInput: (event: SyntheticEvent, value: string) => void; + /** The placeholder text when everything is cleared */ placeholder: string; + /** Clears the input value on enter */ selfClear: boolean; + /** The state variable of the input. */ value: string | number; -}> & - BoxProps; +}>; + +type Props = OptionalProps & ConditionalProps & BoxProps; -export const toInputValue = (value: string | number | undefined) => - typeof value !== 'number' && typeof value !== 'string' ? '' : String(value); +export function toInputValue(value: string | number | undefined) { + return typeof value !== 'number' && typeof value !== 'string' + ? '' + : String(value); +} -const inputDebounce = debounce((onInput: () => void) => onInput(), 200); +const inputDebounce = debounce((onInput: () => void) => onInput(), 250); -export const Input = (props: Props) => { +/** + * ### Input + * A basic text input which allow users to enter text into a UI. + * > Input does not support custom font size and height due to the way + * > it's implemented in CSS. Eventually, this needs to be fixed. + */ +export function Input(props: Props) { const { autoFocus, autoSelect, className, disabled, + expensive, fluid, maxLength, monospace, @@ -59,7 +101,19 @@ export const Input = (props: Props) => { const inputRef = useRef(null); - const handleKeyDown = (event: KeyboardEvent) => { + function handleInput(event: SyntheticEvent) { + if (!onInput) return; + + const value = event.currentTarget?.value; + + if (expensive) { + inputDebounce(() => onInput(event, value)); + } else { + onInput(event, value); + } + } + + function handleKeyDown(event: KeyboardEvent) { if (event.key === KEY.Enter) { onEnter?.(event, event.currentTarget.value); if (selfClear) { @@ -78,7 +132,7 @@ export const Input = (props: Props) => { event.currentTarget.value = toInputValue(value); event.currentTarget.blur(); } - }; + } /** Focuses the input on mount */ useEffect(() => { @@ -123,13 +177,11 @@ export const Input = (props: Props) => { disabled={disabled} maxLength={maxLength} onBlur={(event) => onChange?.(event, event.target.value)} - onChange={(event) => - onInput && inputDebounce(() => onInput(event, event.target.value)) - } + onChange={handleInput} onKeyDown={handleKeyDown} placeholder={placeholder} ref={inputRef} /> ); -}; +} diff --git a/tgui/packages/tgui/interfaces/CameraConsole.tsx b/tgui/packages/tgui/interfaces/CameraConsole.tsx index 974b85895e9..adad4f8748e 100644 --- a/tgui/packages/tgui/interfaces/CameraConsole.tsx +++ b/tgui/packages/tgui/interfaces/CameraConsole.tsx @@ -115,10 +115,12 @@ const CameraSelector = (props) => { setSearchText(value)} + value={searchText} /> diff --git a/tgui/packages/tgui/interfaces/common/SearchBar.tsx b/tgui/packages/tgui/interfaces/common/SearchBar.tsx index 0a7c3a32cf9..ab82dfd7084 100644 --- a/tgui/packages/tgui/interfaces/common/SearchBar.tsx +++ b/tgui/packages/tgui/interfaces/common/SearchBar.tsx @@ -40,6 +40,7 @@ export function SearchBar(props: Props) { onSearch(value)} placeholder={placeholder}