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 { toStringUI: (v?: T) => string; fromStringUI: (v?: string) => T; } export interface UIFormProps { name: K; label: TranslatedString; placeholder?: TranslatedString; tooltip?: TranslatedString; help?: TranslatedString; before?: Addon; after?: Addon; required?: boolean; converter?: StringConverter; } export type FormErrors = { [P in keyof T]?: string | FormErrors; }; //@ts-ignore const TooltipIcon = ( ); export function LabelWithTooltipMaybeRequired({ label, required, tooltip, }: { label: TranslatedString; required?: boolean; tooltip?: TranslatedString; }): VNode { const Label = (
); const WithTooltip = tooltip ? (
{Label} {TooltipIcon}
) : ( Label ); if (required) { return (
{WithTooltip} *
); } return WithTooltip; } function InputWrapper({ children, label, tooltip, before, after, help, error, required, }: { error?: string; children: ComponentChildren } & UIFormProps): VNode { return (
{before && (before.type === "text" ? ( {before.text} ) : before.type === "icon" ? (
{before.icon}
) : before.type === "button" ? ( ) : undefined)} {children} {after && (after.type === "text" ? ( {after.text} ) : after.type === "icon" ? (
{after.icon}
) : after.type === "button" ? ( ) : undefined)}
{error && (

{error}

)} {help && (

{help}

)}
); } function defaultToString(v: unknown) { return v === undefined ? "" : typeof v !== "object" ? String(v) : ""; } function defaultFromString(v: string) { return v; } type InputType = "text" | "text-area" | "password" | "email" | "number"; export function InputLine( props: { type: InputType } & UIFormProps, ): VNode { const { name, placeholder, before, after, converter, type } = props; const { value, onChange, state, isDirty } = useField(name); if (state.hidden) return
; let clazz = "block w-full rounded-md border-0 py-1.5 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6 disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-500 disabled:ring-gray-200"; if (before) { switch (before.type) { case "icon": { clazz += " pl-10"; break; } case "button": { clazz += " rounded-none rounded-r-md "; break; } case "text": { clazz += " min-w-0 flex-1 rounded-r-md rounded-none "; break; } } } if (after) { switch (after.type) { case "icon": { clazz += " pr-10"; break; } case "button": { clazz += " rounded-none rounded-l-md"; break; } case "text": { clazz += " min-w-0 flex-1 rounded-l-md rounded-none "; break; } } } const showError = isDirty && state.error; if (showError) { clazz += " text-red-900 ring-red-300 placeholder:text-red-300 focus:ring-red-500"; } else { 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 ( {...props} error={showError ? state.error : undefined} >