diff options
Diffstat (limited to 'packages/aml-backoffice-ui/src/handlers')
11 files changed, 303 insertions, 114 deletions
diff --git a/packages/aml-backoffice-ui/src/handlers/Calendar.tsx b/packages/aml-backoffice-ui/src/handlers/Calendar.tsx new file mode 100644 index 000000000..9da6e1757 --- /dev/null +++ b/packages/aml-backoffice-ui/src/handlers/Calendar.tsx @@ -0,0 +1,116 @@ +import { AbsoluteTime } from "@gnu-taler/taler-util" +import { useTranslationContext } from "@gnu-taler/web-util/browser" +import { add as dateAdd, sub as dateSub, eachDayOfInterval, endOfMonth, endOfWeek, format, getMonth, getYear, isSameDay, isSameMonth, startOfDay, startOfMonth, startOfWeek } from "date-fns" +import { VNode, h } from "preact" +import { useState } from "preact/hooks" + +export function Calendar({ value, onChange }: { value: AbsoluteTime | undefined, onChange: (v: AbsoluteTime) => void }): VNode { + const today = startOfDay(new Date()) + const selected = !value ? today : new Date(AbsoluteTime.toStampMs(value)) + const [showingDate, setShowingDate] = useState(selected) + const month = getMonth(showingDate) + const year = getYear(showingDate) + + const start = startOfWeek(startOfMonth(showingDate)); + const end = endOfWeek(endOfMonth(showingDate)); + const daysInMonth = eachDayOfInterval({ start, end }); + const { i18n } = useTranslationContext() + const monthNames = [ + i18n.str`January`, + i18n.str`February`, + i18n.str`March`, + i18n.str`April`, + i18n.str`May`, + i18n.str`June`, + i18n.str`July`, + i18n.str`August`, + i18n.str`September`, + i18n.str`October`, + i18n.str`November`, + i18n.str`December`, + ] + return <div class="text-center p-2"> + <div class="flex items-center text-gray-900"> + <button type="button" class="flex px-4 flex-none items-center justify-center p-1.5 text-gray-400 hover:text-gray-500 ring-2 round-sm" + onClick={() => { + setShowingDate(dateSub(showingDate, { years: 1 })) + }}> + <span class="sr-only"> + {i18n.str`Previous year`} + </span> + <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> + <path fill-rule="evenodd" d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z" clip-rule="evenodd" /> + </svg> + </button> + <div class="flex-auto text-sm font-semibold">{year}</div> + <button type="button" class="flex px-4 flex-none items-center justify-center p-1.5 text-gray-400 hover:text-gray-500 ring-2 round-sm" + onClick={() => { + setShowingDate(dateAdd(showingDate, { years: 1 })) + }}> + <span class="sr-only"> + {i18n.str`Next year`} + </span> + <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> + <path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" /> + </svg> + </button> + </div> + <div class="mt-4 flex items-center text-gray-900"> + <button type="button" class="flex px-4 flex-none items-center justify-center p-1.5 text-gray-400 hover:text-gray-500 ring-2 round-sm" + onClick={() => { + setShowingDate(dateSub(showingDate, { months: 1 })) + }}> + <span class="sr-only"> + {i18n.str`Previous month`} + </span> + <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> + <path fill-rule="evenodd" d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z" clip-rule="evenodd" /> + </svg> + </button> + <div class="flex-auto text-sm font-semibold">{monthNames[month]}</div> + <button type="button" class="flex px-4 flex-none items-center justify-center p-1.5 text-gray-400 hover:text-gray-500 ring-2 rounded-sm " + onClick={() => { + setShowingDate(dateAdd(showingDate, { months: 1 })) + }}> + <span class="sr-only"> + {i18n.str`Next month`} + </span> + <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> + <path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" /> + </svg> + </button> + </div> + <div class="mt-6 grid grid-cols-7 text-xs leading-6 text-gray-500"> + <div>M</div> + <div>T</div> + <div>W</div> + <div>T</div> + <div>F</div> + <div>S</div> + <div>S</div> + </div> + <div class="isolate mt-2 grid grid-cols-7 gap-px rounded-lg bg-gray-200 text-sm shadow ring-1 ring-gray-200"> + {daysInMonth.map(current => ( + <button type="button" + data-month={isSameMonth(current, showingDate)} + data-today={isSameDay(current, today)} + data-selected={isSameDay(current, selected)} + onClick={() => { + onChange(AbsoluteTime.fromStampMs(current.getTime())) + }} + class="text-gray-400 hover:bg-gray-700 focus:z-10 py-1.5 + data-[month=false]:bg-gray-100 data-[month=true]:bg-white + data-[today=true]:font-semibold + data-[month=true]:text-gray-900 + data-[today=true]:bg-red-300 data-[today=true]:hover:bg-red-200 + data-[month=true]:hover:bg-gray-200 + data-[selected=true]:!bg-blue-400 data-[selected=true]:hover:!bg-blue-300 "> + <time dateTime={format(current, "yyyy-MM-dd")} + class="mx-auto flex h-7 w-7 items-center justify-center rounded-full"> + {format(current, "dd")} + </time> + </button> + ))} + </div> + </div> +} diff --git a/packages/aml-backoffice-ui/src/handlers/Dialog.tsx b/packages/aml-backoffice-ui/src/handlers/Dialog.tsx new file mode 100644 index 000000000..f9899e94e --- /dev/null +++ b/packages/aml-backoffice-ui/src/handlers/Dialog.tsx @@ -0,0 +1,15 @@ +import { ComponentChildren, VNode, h } from "preact"; + +export function Dialog({ children, onClose }: { onClose?: () => void; children: ComponentChildren }): VNode { + return <div class="relative z-10" aria-labelledby="modal-title" role="dialog" aria-modal="true" onClick={onClose}> + <div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div> + + <div class="fixed inset-0 z-10 w-screen overflow-y-auto"> + <div class="flex min-h-full items-center justify-center p-4 text-center sm:items-center sm:p-0"> + <div class="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6"> + {children} + </div> + </div> + </div> + </div> +} diff --git a/packages/aml-backoffice-ui/src/handlers/FormProvider.tsx b/packages/aml-backoffice-ui/src/handlers/FormProvider.tsx index 3da2a4f07..310954bd0 100644 --- a/packages/aml-backoffice-ui/src/handlers/FormProvider.tsx +++ b/packages/aml-backoffice-ui/src/handlers/FormProvider.tsx @@ -7,14 +7,13 @@ import { ComponentChildren, VNode, createContext, h } from "preact"; import { MutableRef, StateUpdater, - useEffect, - useRef, - useState, + useState } from "preact/hooks"; export interface FormType<T> { value: MutableRef<Partial<T>>; initialValue?: Partial<T>; + readOnly?: boolean; onUpdate?: StateUpdater<T>; computeFormState?: (v: T) => FormState<T>; } @@ -24,14 +23,14 @@ export const FormContext = createContext<FormType<any>>({}); export type FormState<T> = { [field in keyof T]?: T[field] extends AbsoluteTime - ? Partial<InputFieldState> - : T[field] extends AmountJson - ? Partial<InputFieldState> - : T[field] extends Array<infer P> - ? Partial<InputArrayFieldState<P>> - : T[field] extends (object | undefined) - ? FormState<T[field]> - : Partial<InputFieldState>; + ? Partial<InputFieldState> + : T[field] extends AmountJson + ? Partial<InputFieldState> + : T[field] extends Array<infer P> + ? Partial<InputArrayFieldState<P>> + : T[field] extends (object | undefined) + ? FormState<T[field]> + : Partial<InputFieldState>; }; export interface InputFieldState { @@ -55,11 +54,13 @@ export function FormProvider<T>({ onUpdate: notify, onSubmit, computeFormState, + readOnly, }: { initialValue?: Partial<T>; onUpdate?: (v: Partial<T>) => void; onSubmit?: (v: Partial<T>, s: FormState<T> | undefined) => void; computeFormState?: (v: Partial<T>) => FormState<T>; + readOnly?: boolean; children: ComponentChildren; }): VNode { // const value = useRef(initialValue ?? {}); @@ -79,7 +80,7 @@ export function FormProvider<T>({ }; return ( <FormContext.Provider - value={{ initialValue, value, onUpdate, computeFormState }} + value={{ initialValue, value, onUpdate, computeFormState, readOnly }} > <form onSubmit={(e) => { diff --git a/packages/aml-backoffice-ui/src/handlers/InputArray.tsx b/packages/aml-backoffice-ui/src/handlers/InputArray.tsx index 00379bed6..d229b35de 100644 --- a/packages/aml-backoffice-ui/src/handlers/InputArray.tsx +++ b/packages/aml-backoffice-ui/src/handlers/InputArray.tsx @@ -107,22 +107,24 @@ export function InputArray<T extends object, K extends keyof T>( /> ); })} - <div class="pt-2"> - <Option - label={"Add..." as TranslatedString} - isSelected={selectedIndex === list.length} - isLast - isFirst - disabled={ - selectedIndex !== undefined && selectedIndex !== list.length - } - onClick={() => { - setSelected( - selectedIndex === list.length ? undefined : list.length, - ); - }} - /> - </div> + {!state.disabled && + <div class="pt-2"> + <Option + label={"Add..." as TranslatedString} + isSelected={selectedIndex === list.length} + isLast + isFirst + disabled={ + selectedIndex !== undefined && selectedIndex !== list.length + } + onClick={() => { + setSelected( + selectedIndex === list.length ? undefined : list.length, + ); + }} + /> + </div> + } </div> {selectedIndex !== undefined && ( /** @@ -131,6 +133,7 @@ export function InputArray<T extends object, K extends keyof T>( */ <FormProvider initialValue={selected} + readOnly={state.disabled} computeFormState={(v) => { // current state is ignored // the state is defined by the parent form diff --git a/packages/aml-backoffice-ui/src/handlers/InputChoiceHorizontal.tsx b/packages/aml-backoffice-ui/src/handlers/InputChoiceHorizontal.tsx index fdee35447..a5f263615 100644 --- a/packages/aml-backoffice-ui/src/handlers/InputChoiceHorizontal.tsx +++ b/packages/aml-backoffice-ui/src/handlers/InputChoiceHorizontal.tsx @@ -61,6 +61,7 @@ export function InputChoiceHorizontal<T extends object, K extends keyof T>( return ( <button type="button" + disabled={state.disabled} class={clazz} onClick={(e) => { onChange( diff --git a/packages/aml-backoffice-ui/src/handlers/InputChoiceStacked.tsx b/packages/aml-backoffice-ui/src/handlers/InputChoiceStacked.tsx index c37984368..29c596994 100644 --- a/packages/aml-backoffice-ui/src/handlers/InputChoiceStacked.tsx +++ b/packages/aml-backoffice-ui/src/handlers/InputChoiceStacked.tsx @@ -60,6 +60,7 @@ export function InputChoiceStacked<T extends object, K extends keyof T>( type="radio" name="server-size" // defaultValue={choice.value} + disabled={state.disabled} value={ (!converter ? (choice.value as string) diff --git a/packages/aml-backoffice-ui/src/handlers/InputDate.tsx b/packages/aml-backoffice-ui/src/handlers/InputDate.tsx index 0f286e001..7fcc16b33 100644 --- a/packages/aml-backoffice-ui/src/handlers/InputDate.tsx +++ b/packages/aml-backoffice-ui/src/handlers/InputDate.tsx @@ -1,40 +1,65 @@ import { AbsoluteTime } from "@gnu-taler/taler-util"; import { InputLine, UIFormProps } from "./InputLine.js"; -import { VNode, h } from "preact"; +import { Fragment, VNode, h } from "preact"; import { format, parse } from "date-fns"; +import { Dialog } from "./Dialog.js"; +import { Calendar } from "./Calendar.js"; +import { useState } from "preact/hooks"; +import { useField } from "./useField.js"; export function InputDate<T extends object, K extends keyof T>( props: { pattern?: string } & UIFormProps<T, K>, ): VNode { const pattern = props.pattern ?? "dd/MM/yyyy"; + const [open, setOpen] = useState(false) + const { value, onChange } = useField<T, K>(props.name); return ( - <InputLine<T, K> - type="text" - after={{ - type: "icon", - // icon: <CalendarIcon class="h-6 w-6" />, - icon: <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> - <path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" /> - </svg> + <Fragment> - }} - converter={{ - //@ts-ignore - fromStringUI: (v): AbsoluteTime => { - if (!v) return AbsoluteTime.never(); - const t_ms = parse(v, pattern, Date.now()).getTime(); - return AbsoluteTime.fromMilliseconds(t_ms); - }, - //@ts-ignore - toStringUI: (v: AbsoluteTime) => { - return !v || !v.t_ms - ? "" - : v.t_ms === "never" - ? "never" - : format(v.t_ms, pattern); - }, - }} - {...props} - /> + <InputLine<T, K> + type="text" + after={{ + type: "button", + onClick: () => { + setOpen(true) + }, + // icon: <CalendarIcon class="h-6 w-6" />, + children: ( + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"> + <path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" /> + </svg>) + }} + converter={{ + //@ts-ignore + fromStringUI: (v): AbsoluteTime | undefined => { + if (!v) return undefined; + try { + const t_ms = parse(v, pattern, Date.now()).getTime(); + return AbsoluteTime.fromMilliseconds(t_ms); + } catch (e) { + return undefined; + } + }, + //@ts-ignore + toStringUI: (v: AbsoluteTime | undefined) => { + return !v || !v.t_ms + ? undefined + : v.t_ms === "never" + ? "never" + : format(v.t_ms, pattern); + }, + }} + {...props} + /> + {open && + <Dialog onClose={() => setOpen(false)}> + <Calendar value={value as AbsoluteTime ?? AbsoluteTime.now()} + onChange={(v) => { + onChange(v as any) + setOpen(false) + }} /> + </Dialog> + } + </Fragment> ); } diff --git a/packages/aml-backoffice-ui/src/handlers/InputFile.tsx b/packages/aml-backoffice-ui/src/handlers/InputFile.tsx index 0d89a98a3..d9af03f86 100644 --- a/packages/aml-backoffice-ui/src/handlers/InputFile.tsx +++ b/packages/aml-backoffice-ui/src/handlers/InputFile.tsx @@ -42,40 +42,42 @@ export function InputFile<T extends object, K extends keyof T>( clip-rule="evenodd" /> </svg> - <div class="my-2 flex text-sm leading-6 text-gray-600"> - <label - for="file-upload" - class="relative cursor-pointer rounded-md bg-white font-semibold text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 hover:text-indigo-500" - > - <span>Upload a file</span> - <input - id="file-upload" - name="file-upload" - type="file" - class="sr-only" - accept={accept} - onChange={(e) => { - const f: FileList | null = e.currentTarget.files; - if (!f || f.length != 1) { - return onChange(undefined!); - } - if (f[0].size > maxBites) { - return onChange(undefined!); - } - return f[0].arrayBuffer().then((b) => { - const b64 = window.btoa( - new Uint8Array(b).reduce( - (data, byte) => data + String.fromCharCode(byte), - "", - ), - ); - return onChange(`data:${f[0].type};base64,${b64}` as any); - }); - }} - /> - </label> - {/* <p class="pl-1">or drag and drop</p> */} - </div> + {!state.disabled && + <div class="my-2 flex text-sm leading-6 text-gray-600"> + <label + for="file-upload" + class="relative cursor-pointer rounded-md bg-white font-semibold text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 hover:text-indigo-500" + > + <span>Upload a file</span> + <input + id="file-upload" + name="file-upload" + type="file" + class="sr-only" + accept={accept} + onChange={(e) => { + const f: FileList | null = e.currentTarget.files; + if (!f || f.length != 1) { + return onChange(undefined!); + } + if (f[0].size > maxBites) { + return onChange(undefined!); + } + return f[0].arrayBuffer().then((b) => { + const b64 = window.btoa( + new Uint8Array(b).reduce( + (data, byte) => data + String.fromCharCode(byte), + "", + ), + ); + return onChange(`data:${f[0].type};base64,${b64}` as any); + }); + }} + /> + </label> + {/* <p class="pl-1">or drag and drop</p> */} + </div> + } </div> </div> ) : ( @@ -85,14 +87,16 @@ export function InputFile<T extends object, K extends keyof T>( class=" h-24 w-full object-cover relative" /> - <div - class="opacity-0 hover:opacity-70 duration-300 absolute rounded-lg border inset-0 z-10 flex justify-center text-xl items-center bg-black text-white cursor-pointer " - onClick={() => { - onChange(undefined!); - }} - > - Clear - </div> + {!state.disabled && + <div + class="opacity-0 hover:opacity-70 duration-300 absolute rounded-lg border inset-0 z-10 flex justify-center text-xl items-center bg-black text-white cursor-pointer " + onClick={() => { + onChange(undefined!); + }} + > + Clear + </div> + } </div> )} {help && <p class="text-xs leading-5 text-gray-600 mt-2">{help}</p>} diff --git a/packages/aml-backoffice-ui/src/handlers/InputLine.tsx b/packages/aml-backoffice-ui/src/handlers/InputLine.tsx index 9448ef5e4..f6c709d94 100644 --- a/packages/aml-backoffice-ui/src/handlers/InputLine.tsx +++ b/packages/aml-backoffice-ui/src/handlers/InputLine.tsx @@ -1,6 +1,7 @@ import { TranslatedString } from "@gnu-taler/taler-util"; import { ComponentChildren, Fragment, VNode, h } from "preact"; import { useField } from "./useField.js"; +import { useEffect, useState } from "preact/hooks"; export interface IconAddon { type: "icon"; @@ -80,7 +81,7 @@ 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"> + <div class="absolute bottom-0 flex flex-col items-center 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"> {tooltip} </span> @@ -110,8 +111,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 +134,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 +156,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 +193,21 @@ 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) + } else { + console.log("invalid") + } + }, [value]) + if (state.hidden) return <div />; let clazz = @@ -233,14 +252,12 @@ 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} + disabled={state.disabled} error={showError ? state.error : undefined} > <textarea @@ -262,15 +279,18 @@ export function InputLine<T extends object, K extends keyof T>( } return ( - <InputWrapper<T, K> {...props} error={showError ? state.error : undefined}> + <InputWrapper<T, K> {...props} disabled={state.disabled} 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} diff --git a/packages/aml-backoffice-ui/src/handlers/InputSelectMultiple.tsx b/packages/aml-backoffice-ui/src/handlers/InputSelectMultiple.tsx index 837744827..6e6186a88 100644 --- a/packages/aml-backoffice-ui/src/handlers/InputSelectMultiple.tsx +++ b/packages/aml-backoffice-ui/src/handlers/InputSelectMultiple.tsx @@ -13,7 +13,7 @@ export function InputSelectMultiple<T extends object, K extends keyof T>( ): VNode { const { name, label, choices, placeholder, tooltip, required, unique, max } = props; - const { value, onChange } = useField<T, K>(name); + const { value, onChange, state } = useField<T, K>(name); const [filter, setFilter] = useState<string | undefined>(undefined); const regex = new RegExp(`.*${filter}.*`, "i"); @@ -26,8 +26,8 @@ export function InputSelectMultiple<T extends object, K extends keyof T>( filter === undefined ? undefined : choices.filter((v) => { - return regex.test(v.label); - }); + return regex.test(v.label); + }); return ( <div class="sm:col-span-6"> <LabelWithTooltipMaybeRequired @@ -41,6 +41,7 @@ export function InputSelectMultiple<T extends object, K extends keyof T>( {choiceMap[v]} <button type="button" + disabled={state.disabled} onClick={() => { const newValue = [...list]; newValue.splice(idx, 1); @@ -62,7 +63,7 @@ export function InputSelectMultiple<T extends object, K extends keyof T>( ); })} - <div class="relative mt-2"> + {!state.disabled && <div class="relative mt-2"> <input id="combobox" type="text" @@ -78,6 +79,7 @@ export function InputSelectMultiple<T extends object, K extends keyof T>( /> <button type="button" + disabled={state.disabled} onClick={() => { setFilter(filter === undefined ? "" : undefined); }} @@ -122,7 +124,7 @@ export function InputSelectMultiple<T extends object, K extends keyof T>( onChange(newValue as T[K]); }} - // tabindex="-1" + // tabindex="-1" > {/* <!-- Selected: "font-semibold" --> */} <span class="block truncate">{v.label}</span> @@ -145,7 +147,7 @@ export function InputSelectMultiple<T extends object, K extends keyof T>( {/* <!-- More items... --> */} </ul> )} - </div> + </div>} </div> ); } diff --git a/packages/aml-backoffice-ui/src/handlers/useField.ts b/packages/aml-backoffice-ui/src/handlers/useField.ts index bf94d2f5d..7eec5c5f8 100644 --- a/packages/aml-backoffice-ui/src/handlers/useField.ts +++ b/packages/aml-backoffice-ui/src/handlers/useField.ts @@ -16,6 +16,7 @@ export function useField<T extends object, K extends keyof T>( value: formValue, computeFormState, onUpdate: notifyUpdate, + readOnly: readOnlyForm, } = useContext(FormContext); type P = typeof name; @@ -30,8 +31,8 @@ export function useField<T extends object, K extends keyof T>( //compute default state const state = { - disabled: fieldState.disabled ?? false, - readonly: fieldState.readonly ?? false, + disabled: readOnlyForm ? true : (fieldState.disabled ?? false), + readonly: readOnlyForm ? true : (fieldState.readonly ?? false), hidden: fieldState.hidden ?? false, error: fieldState.error, elements: "elements" in fieldState ? fieldState.elements ?? [] : [], |