diff options
Diffstat (limited to 'packages/web-util/src/forms/InputLine.tsx')
-rw-r--r-- | packages/web-util/src/forms/InputLine.tsx | 80 |
1 files changed, 33 insertions, 47 deletions
diff --git a/packages/web-util/src/forms/InputLine.tsx b/packages/web-util/src/forms/InputLine.tsx index 9448ef5e4..8c44b1ca5 100644 --- a/packages/web-util/src/forms/InputLine.tsx +++ b/packages/web-util/src/forms/InputLine.tsx @@ -1,42 +1,8 @@ import { TranslatedString } from "@gnu-taler/taler-util"; import { ComponentChildren, Fragment, VNode, h } from "preact"; import { useField } from "./useField.js"; - -export interface IconAddon { - type: "icon"; - icon: VNode; -} -interface ButtonAddon { - type: "button"; - onClick: () => void; - children: ComponentChildren; -} -interface TextAddon { - type: "text"; - text: TranslatedString; -} -type Addon = IconAddon | ButtonAddon | TextAddon; - -interface StringConverter<T> { - toStringUI: (v?: T) => string; - fromStringUI: (v?: string) => T; -} - -export interface UIFormProps<T extends object, K extends keyof T> { - name: K; - label: TranslatedString; - placeholder?: TranslatedString; - tooltip?: TranslatedString; - help?: TranslatedString; - before?: Addon; - after?: Addon; - required?: boolean; - converter?: StringConverter<T[K]>; -} - -export type FormErrors<T> = { - [P in keyof T]?: string | FormErrors<T[P]>; -}; +import { useEffect, useState } from "preact/hooks"; +import { UIFormProps } from "./FormProvider.js"; //@ts-ignore const TooltipIcon = ( @@ -80,11 +46,11 @@ export function LabelWithTooltipMaybeRequired({ {Label} <span class="relative flex items-center group pl-2"> {TooltipIcon} - <div class="absolute bottom-0 flex flex-col items-center hidden mb-6 group-hover:flex"> - <span class="relative z-10 p-2 text-xs leading-none text-white whitespace-no-wrap bg-black shadow-lg"> + <div class="absolute bottom-0 -ml-10 hidden flex-col items-center mb-6 group-hover:flex w-28"> + <div class="relative z-10 p-2 text-xs leading-none text-white whitespace-no-wrap bg-black shadow-lg"> {tooltip} - </span> - <div class="w-3 h-3 -mt-2 rotate-45 bg-black"></div> + </div> + <div class="w-3 h-3 -mt-2 rotate-45 bg-black"></div> </div> </span> </div> @@ -110,8 +76,9 @@ function InputWrapper<T extends object, K extends keyof T>({ after, help, error, + disabled, required, -}: { error?: string; children: ComponentChildren } & UIFormProps<T, K>): VNode { +}: { error?: string; disabled: boolean, children: ComponentChildren } & UIFormProps<T, K>): VNode { return ( <div class="sm:col-span-6"> <LabelWithTooltipMaybeRequired @@ -132,6 +99,7 @@ function InputWrapper<T extends object, K extends keyof T>({ ) : before.type === "button" ? ( <button type="button" + disabled={disabled} onClick={before.onClick} class="relative -ml-px inline-flex items-center gap-x-1.5 rounded-l-md px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50" > @@ -153,6 +121,7 @@ function InputWrapper<T extends object, K extends keyof T>({ ) : after.type === "button" ? ( <button type="button" + disabled={disabled} onClick={after.onClick} class="relative -ml-px inline-flex items-center gap-x-1.5 rounded-r-md px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50" > @@ -189,6 +158,18 @@ export function InputLine<T extends object, K extends keyof T>( const { name, placeholder, before, after, converter, type } = props; const { value, onChange, state, isDirty } = useField<T, K>(name); + const [text, setText] = useState("") + const fromString: (s: string) => any = + converter?.fromStringUI ?? defaultFromString; + const toString: (s: any) => string = converter?.toStringUI ?? defaultToString; + + useEffect(() => { + const newValue = toString(value) + if (newValue) { + setText(newValue) + } + }, [value]) + if (state.hidden) return <div />; let clazz = @@ -233,14 +214,13 @@ export function InputLine<T extends object, K extends keyof T>( clazz += " text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-indigo-600"; } - const fromString: (s: string) => any = - converter?.fromStringUI ?? defaultFromString; - const toString: (s: any) => string = converter?.toStringUI ?? defaultToString; if (type === "text-area") { return ( <InputWrapper<T, K> {...props} + help={props.help ?? state.help} + disabled={state.disabled ?? false} error={showError ? state.error : undefined} > <textarea @@ -262,15 +242,21 @@ export function InputLine<T extends object, K extends keyof T>( } return ( - <InputWrapper<T, K> {...props} error={showError ? state.error : undefined}> + <InputWrapper<T, K> {...props} + help={props.help ?? state.help} + disabled={state.disabled ?? false} error={showError ? state.error : undefined} + > <input name={String(name)} type={type} onChange={(e) => { - onChange(fromString(e.currentTarget.value)); + setText(e.currentTarget.value) }} placeholder={placeholder ? placeholder : undefined} - value={toString(value) ?? ""} + value={text} + onBlur={() => { + onChange(fromString(text)); + }} // defaultValue={toString(value)} disabled={state.disabled} aria-invalid={showError} |