diff options
author | Sebastian <sebasjm@gmail.com> | 2023-12-31 15:32:12 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2023-12-31 15:32:12 -0300 |
commit | bedeebff1572fa8cfdb0a818030f6b13a3fc0f53 (patch) | |
tree | f782867e60420630f179a47d0d4f0d8d35a4ad4b /packages/aml-backoffice-ui/src/handlers | |
parent | 08d3e79a9c2adbc295549bdcc05353b6e50565f3 (diff) | |
download | wallet-core-bedeebff1572fa8cfdb0a818030f6b13a3fc0f53.tar.xz |
remove handlers from impl
Diffstat (limited to 'packages/aml-backoffice-ui/src/handlers')
36 files changed, 0 insertions, 2859 deletions
diff --git a/packages/aml-backoffice-ui/src/handlers/Calendar.tsx b/packages/aml-backoffice-ui/src/handlers/Calendar.tsx deleted file mode 100644 index e476bf6f6..000000000 --- a/packages/aml-backoffice-ui/src/handlers/Calendar.tsx +++ /dev/null @@ -1,119 +0,0 @@ -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"> - <div class="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 py-4 px-5 sm:px-8 items-center justify-center rounded-full"> - {format(current, "dd")} - </time> - </button> - ))} - </div> - {daysInMonth.length < 40 ? <div class="w-7 h-7 m-1.5" /> : undefined} - </div> - </div> -} diff --git a/packages/aml-backoffice-ui/src/handlers/Caption.tsx b/packages/aml-backoffice-ui/src/handlers/Caption.tsx deleted file mode 100644 index 8facddec3..000000000 --- a/packages/aml-backoffice-ui/src/handlers/Caption.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { TranslatedString } from "@gnu-taler/taler-util"; -import { VNode, h } from "preact"; -import { - LabelWithTooltipMaybeRequired -} from "./InputLine.js"; - -interface Props { - label: TranslatedString; - tooltip?: TranslatedString; - help?: TranslatedString; - before?: VNode; - after?: VNode; -} - -export function Caption({ before, after, label, tooltip, help }: Props): VNode { - return ( - <div class="sm:col-span-6 flex"> - {before !== undefined && ( - <span class="pointer-events-none flex items-center pr-2">{before}</span> - )} - <LabelWithTooltipMaybeRequired label={label} tooltip={tooltip} /> - {after !== undefined && ( - <span class="pointer-events-none flex items-center pl-2">{after}</span> - )} - {help && ( - <p class="mt-2 text-sm text-gray-500" id="email-description"> - {help} - </p> - )} - </div> - ); -} diff --git a/packages/aml-backoffice-ui/src/handlers/Dialog.tsx b/packages/aml-backoffice-ui/src/handlers/Dialog.tsx deleted file mode 100644 index 7b41fe487..000000000 --- a/packages/aml-backoffice-ui/src/handlers/Dialog.tsx +++ /dev/null @@ -1,15 +0,0 @@ -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 "> - <div class="relative transform overflow-hidden rounded-lg bg-white p-1 text-left shadow-xl transition-all" onClick={(e) => e.stopPropagation()}> - {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 deleted file mode 100644 index b9f9f7832..000000000 --- a/packages/aml-backoffice-ui/src/handlers/FormProvider.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import { - AbsoluteTime, - AmountJson, - TranslatedString, -} from "@gnu-taler/taler-util"; -import { ComponentChildren, VNode, createContext, h } from "preact"; -import { - MutableRef, - StateUpdater, - useState -} from "preact/hooks"; - -export interface FormType<T extends object> { - value: MutableRef<Partial<T>>; - initialValue?: Partial<T>; - readOnly?: boolean; - onUpdate?: StateUpdater<T>; - computeFormState?: (v: T) => FormState<T>; -} - -//@ts-ignore -export const FormContext = createContext<FormType<any>>({}); - -/** - * Map of {[field]:BehaviorResult} - * for every field of type - * - any native (string, number, etc...) - * - absoluteTime - * - amountJson - * - * except for: - * - object => recurse into - * - array => behavior result and element field - */ -export type FormState<T extends object | undefined> = { - [field in keyof T]?: T[field] extends AbsoluteTime - ? BehaviorResult - : T[field] extends AmountJson - ? BehaviorResult - : T[field] extends Array<infer P extends object> - ? InputArrayFieldState<P> - : T[field] extends (object | undefined) - ? FormState<T[field]> - : BehaviorResult; -}; - -export type BehaviorResult = Partial<InputFieldState> & FieldUIOptions - -export interface InputFieldState { - /* should show the error */ - error?: TranslatedString; - /* should not allow to edit */ - readonly: boolean; - /* should show as disable */ - disabled: boolean; - /* should not show */ - hidden: boolean; -} - -export interface IconAddon { - type: "icon"; - icon: VNode; -} -export interface ButtonAddon { - type: "button"; - onClick: () => void; - children: ComponentChildren; -} -export interface TextAddon { - type: "text"; - text: TranslatedString; -} -export type Addon = IconAddon | ButtonAddon | TextAddon; - -export interface StringConverter<T> { - toStringUI: (v?: T) => string; - fromStringUI: (v?: string) => T; -} - -type FieldUIOptions = { - placeholder?: TranslatedString; - tooltip?: TranslatedString; - help?: TranslatedString; - required?: boolean; -} - -export interface UIFormProps<T extends object, K extends keyof T> extends FieldUIOptions { - name: K; - label: TranslatedString; - before?: Addon; - after?: Addon; - converter?: StringConverter<T[K]>; -} - -export interface InputArrayFieldState<P extends object> extends BehaviorResult { - elements?: FormState<P>[]; -} - -export function FormProvider<T extends object>({ - children, - initialValue, - 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 [state, setState] = useState<Partial<T>>(initialValue ?? {}); - const value = { current: state }; - const onUpdate = (v: typeof state) => { - setState(v); - if (notify) notify(v); - }; - return ( - <FormContext.Provider - value={{ initialValue, value, onUpdate, computeFormState, readOnly }} - > - <form - onSubmit={(e) => { - e.preventDefault(); - //@ts-ignore - if (onSubmit) - onSubmit( - value.current, - !computeFormState ? undefined : computeFormState(value.current), - ); - }} - > - {children} - </form> - </FormContext.Provider> - ); -} diff --git a/packages/aml-backoffice-ui/src/handlers/Group.tsx b/packages/aml-backoffice-ui/src/handlers/Group.tsx deleted file mode 100644 index 0645f6d97..000000000 --- a/packages/aml-backoffice-ui/src/handlers/Group.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { TranslatedString } from "@gnu-taler/taler-util"; -import { VNode, h } from "preact"; -import { LabelWithTooltipMaybeRequired } from "./InputLine.js"; -import { RenderAllFieldsByUiConfig, UIFormField } from "./forms.js"; - -interface Props { - before?: TranslatedString; - after?: TranslatedString; - tooltipBefore?: TranslatedString; - tooltipAfter?: TranslatedString; - fields: UIFormField[]; -} - -export function Group({ - before, - after, - tooltipAfter, - tooltipBefore, - fields, -}: Props): VNode { - return ( - <div class="sm:col-span-6 p-4 rounded-lg border-r-2 border-2 bg-gray-50"> - <div class="pb-4"> - {before && ( - <LabelWithTooltipMaybeRequired - label={before} - tooltip={tooltipBefore} - /> - )} - </div> - <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-2 sm:grid-cols-6"> - <RenderAllFieldsByUiConfig fields={fields} /> - </div> - <div class="pt-4"> - {after && ( - <LabelWithTooltipMaybeRequired label={after} tooltip={tooltipAfter} /> - )} - </div> - </div> - ); -} diff --git a/packages/aml-backoffice-ui/src/handlers/InputAbsoluteTime.stories.tsx b/packages/aml-backoffice-ui/src/handlers/InputAbsoluteTime.stories.tsx deleted file mode 100644 index 54e41ffae..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputAbsoluteTime.stories.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { AbsoluteTime, TranslatedString } from "@gnu-taler/taler-util"; -import * as tests from "@gnu-taler/web-util/testing"; -import { - NiceForm as TestedComponent, -} from "./NiceForm.js"; -import { FlexibleForm } from "./forms.js"; - -export default { - title: "Input Absolute Time", -}; - -export namespace Simplest { - export interface Form { - comment: string; - } -} - -type TargetObject = { - today: AbsoluteTime; -} -const initial: TargetObject = { - today: AbsoluteTime.now() -} - -const form: FlexibleForm<TargetObject> = { - design: [{ - title: "this is a simple form" as TranslatedString, - fields: [{ - type: "absoluteTime", - props: { - label: "label of the field" as TranslatedString, - name: "today", - pattern: "dd/MM/yyyy HH:mm" - }, - }] - }] -} - -export const SimpleComment = tests.createExample(TestedComponent, { initial, form }); diff --git a/packages/aml-backoffice-ui/src/handlers/InputAbsoluteTime.tsx b/packages/aml-backoffice-ui/src/handlers/InputAbsoluteTime.tsx deleted file mode 100644 index 0e03c5595..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputAbsoluteTime.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { AbsoluteTime } from "@gnu-taler/taler-util"; -import { InputLine } from "./InputLine.js"; -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"; -import { UIFormProps } from "./FormProvider.js"; -import { TimePicker } from "./TimePicker.js"; - -export function InputAbsoluteTime<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(true) - const { value, onChange } = useField<T, K>(props.name); - return ( - <Fragment> - - <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> - } */} - {open && - <Dialog onClose={() => setOpen(false)} > - <TimePicker value={value as AbsoluteTime ?? AbsoluteTime.now()} - onChange={(v) => { - onChange(v as any) - }} - onConfirm={() => { - setOpen(false) - }} /> - </Dialog>} - </Fragment> - ); -} diff --git a/packages/aml-backoffice-ui/src/handlers/InputAmount.stories.tsx b/packages/aml-backoffice-ui/src/handlers/InputAmount.stories.tsx deleted file mode 100644 index 872726247..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputAmount.stories.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { AmountJson, Amounts, TranslatedString } from "@gnu-taler/taler-util"; -import * as tests from "@gnu-taler/web-util/testing"; -import { - NiceForm as TestedComponent, -} from "./NiceForm.js"; -import { FlexibleForm } from "./forms.js"; - -export default { - title: "Input Amount", -}; - -export namespace Simplest { - export interface Form { - comment: string; - } -} - -type TargetObject = { - amount: AmountJson; -} -const initial: TargetObject = { - amount: Amounts.parseOrThrow("USD:10") -} - -const form: FlexibleForm<TargetObject> = { - design: [{ - title: "this is a simple form" as TranslatedString, - fields: [{ - type: "amount", - props: { - label: "label of the field" as TranslatedString, - name: "amount", - }, - }] - }] -} - -export const SimpleComment = tests.createExample(TestedComponent, { initial, form }); diff --git a/packages/aml-backoffice-ui/src/handlers/InputAmount.tsx b/packages/aml-backoffice-ui/src/handlers/InputAmount.tsx deleted file mode 100644 index 29ec43525..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputAmount.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { AmountJson, Amounts, TranslatedString } from "@gnu-taler/taler-util"; -import { VNode, h } from "preact"; -import { InputLine } from "./InputLine.js"; -import { useField } from "./useField.js"; -import { UIFormProps } from "./FormProvider.js"; - -export function InputAmount<T extends object, K extends keyof T>( - props: { currency?: string } & UIFormProps<T, K>, -): VNode { - const { value } = useField<T, K>(props.name); - const currency = - !value || !(value as any).currency - ? props.currency - : (value as any).currency; - return ( - <InputLine<T, K> - type="text" - before={{ - type: "text", - text: currency as TranslatedString, - }} - converter={{ - //@ts-ignore - fromStringUI: (v): AmountJson => { - - return Amounts.parse(`${currency}:${v}`) ?? Amounts.zeroOfCurrency(currency); - }, - //@ts-ignore - toStringUI: (v: AmountJson) => { - return v === undefined ? "" : Amounts.stringifyValue(v); - }, - }} - {...props} - /> - ); -} diff --git a/packages/aml-backoffice-ui/src/handlers/InputArray.stories.tsx b/packages/aml-backoffice-ui/src/handlers/InputArray.stories.tsx deleted file mode 100644 index ee25d355b..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputArray.stories.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { TranslatedString } from "@gnu-taler/taler-util"; -import * as tests from "@gnu-taler/web-util/testing"; -import { - NiceForm as TestedComponent, -} from "./NiceForm.js"; -import { FlexibleForm } from "./forms.js"; - -export default { - title: "Input Array", -}; - -export namespace Simplest { - export interface Form { - comment: string; - } -} - -type TargetObject = { - people: { - name: string; - age: number; - }[]; -} -const initial: TargetObject = { - people: [{ - name: "me", - age: 17, - }] -} - -const form: FlexibleForm<TargetObject> = { - design: [{ - title: "this is a simple form" as TranslatedString, - fields: [{ - type: "array", - props: { - label: "People" as TranslatedString, - name: "comment", - fields: [{ - type: "text", - props: { - label: "the name" as TranslatedString, - name: "name", - } - }, { - type: "integer", - props: { - label: "the age" as TranslatedString, - name: "age", - } - }], - labelField: "name" - }, - }] - }] -} - -export const SimpleComment = tests.createExample(TestedComponent, { initial, form }); diff --git a/packages/aml-backoffice-ui/src/handlers/InputArray.tsx b/packages/aml-backoffice-ui/src/handlers/InputArray.tsx deleted file mode 100644 index 38c399e66..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputArray.tsx +++ /dev/null @@ -1,186 +0,0 @@ -import { TranslatedString } from "@gnu-taler/taler-util"; -import { Fragment, VNode, h } from "preact"; -import { useState } from "preact/hooks"; -import { FormProvider, UIFormProps } from "./FormProvider.js"; -import { LabelWithTooltipMaybeRequired } from "./InputLine.js"; -import { RenderAllFieldsByUiConfig, UIFormField } from "./forms.js"; -import { useField } from "./useField.js"; - -function Option({ - label, - disabled, - isFirst, - isLast, - isSelected, - onClick, -}: { - label: TranslatedString; - isFirst?: boolean; - isLast?: boolean; - isSelected?: boolean; - disabled?: boolean; - onClick: () => void; -}): VNode { - let clazz = "relative flex border p-4 focus:outline-none disabled:text-grey"; - if (isFirst) { - clazz += " rounded-tl-md rounded-tr-md "; - } - if (isLast) { - clazz += " rounded-bl-md rounded-br-md "; - } - if (isSelected) { - clazz += " z-10 border-indigo-200 bg-indigo-50 "; - } else { - clazz += " border-gray-200"; - } - if (disabled) { - clazz += - " cursor-not-allowed bg-gray-50 text-gray-500 ring-gray-200 text-gray"; - } else { - clazz += " cursor-pointer"; - } - return ( - <label class={clazz}> - <input - type="radio" - name="privacy-setting" - checked={isSelected} - disabled={disabled} - onClick={onClick} - class="mt-0.5 h-4 w-4 shrink-0 text-indigo-600 disabled:cursor-not-allowed disabled:bg-gray-50 disabled:text-gray-500 disabled:ring-gray-200 focus:ring-indigo-600" - aria-labelledby="privacy-setting-0-label" - aria-describedby="privacy-setting-0-description" - /> - <span class="ml-3 flex flex-col"> - <span - id="privacy-setting-0-label" - disabled - class="block text-sm font-medium" - > - {label} - </span> - {/* <!-- Checked: "text-indigo-700", Not Checked: "text-gray-500" --> */} - {/* <span - id="privacy-setting-0-description" - class="block text-sm" - > - This project would be available to anyone who has the link - </span> */} - </span> - </label> - ); -} - -export function InputArray<T extends object, K extends keyof T>( - props: { - fields: UIFormField[]; - labelField: string; - } & UIFormProps<T, K>, -): VNode { - const { fields, labelField, name, label, required, tooltip } = props; - const { value, onChange, state } = useField<T, K>(name); - const list = (value ?? []) as Array<Record<string, string | undefined>>; - const [selectedIndex, setSelected] = useState<number | undefined>(undefined); - const selected = - selectedIndex === undefined ? undefined : list[selectedIndex]; - - return ( - <div class="sm:col-span-6"> - <LabelWithTooltipMaybeRequired - label={label} - required={required} - tooltip={tooltip} - /> - - <div class="-space-y-px rounded-md bg-white "> - {list.map((v, idx) => { - return ( - <Option - label={v[labelField] as TranslatedString} - isSelected={selectedIndex === idx} - isLast={idx === list.length - 1} - disabled={selectedIndex !== undefined && selectedIndex !== idx} - isFirst={idx === 0} - onClick={() => { - setSelected(selectedIndex === idx ? undefined : idx); - }} - /> - ); - })} - {!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 && ( - /** - * This form provider act as a substate of the parent form - * Consider creating an InnerFormProvider since not every feature is expected - */ - <FormProvider - initialValue={selected} - readOnly={state.disabled} - computeFormState={(v) => { - // current state is ignored - // the state is defined by the parent form - - // elements should be present in the state object since this is expected to be an array - //@ts-ignore - return state.elements[selectedIndex]; - }} - onSubmit={(v) => { - const newValue = [...list]; - newValue.splice(selectedIndex, 1, v); - onChange(newValue as T[K]); - setSelected(undefined); - }} - onUpdate={(v) => { - const newValue = [...list]; - newValue.splice(selectedIndex, 1, v); - onChange(newValue as T[K]); - }} - > - <div class="px-4 py-6"> - <div class="grid grid-cols-1 gap-y-8 "> - <RenderAllFieldsByUiConfig fields={fields} /> - </div> - </div> - </FormProvider> - )} - {selectedIndex !== undefined && ( - <div class="flex items-center pt-3"> - <div class="flex-auto"> - {selected !== undefined && ( - <button - type="button" - onClick={() => { - const newValue = [...list]; - newValue.splice(selectedIndex, 1); - onChange(newValue as T[K]); - setSelected(undefined); - }} - class="block rounded-md bg-red-600 px-3 py-2 text-center text-sm text-white shadow-sm hover:bg-red-500 " - > - Remove - </button> - )} - </div> - </div> - )} - </div> - ); -} diff --git a/packages/aml-backoffice-ui/src/handlers/InputChoiceHorizontal.stories.tsx b/packages/aml-backoffice-ui/src/handlers/InputChoiceHorizontal.stories.tsx deleted file mode 100644 index 7872afac7..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputChoiceHorizontal.stories.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { TranslatedString } from "@gnu-taler/taler-util"; -import * as tests from "@gnu-taler/web-util/testing"; -import { - NiceForm as TestedComponent, -} from "./NiceForm.js"; -import { FlexibleForm } from "./forms.js"; - -export default { - title: "Input Choice Horizontal", -}; - -export namespace Simplest { - export interface Form { - comment: string; - } -} - -type TargetObject = { - comment: string; -} -const initial: TargetObject = { - comment: "0" -} - -const form: FlexibleForm<TargetObject> = { - design: [{ - title: "this is a simple form" as TranslatedString, - fields: [{ - type: "choiceHorizontal", - props: { - label: "label of the field" as TranslatedString, - name: "comment", - choices: [{ - label: "first choice" as TranslatedString, - value: "1" - }, { - label: "second choice" as TranslatedString, - value: "2" - }, { - label: "thrid choice" as TranslatedString, - value: "3" - },], - }, - }] - }] -} - -export const SimpleComment = tests.createExample(TestedComponent, { initial, form }); diff --git a/packages/aml-backoffice-ui/src/handlers/InputChoiceHorizontal.tsx b/packages/aml-backoffice-ui/src/handlers/InputChoiceHorizontal.tsx deleted file mode 100644 index 594b1c32e..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputChoiceHorizontal.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { TranslatedString } from "@gnu-taler/taler-util"; -import { Fragment, VNode, h } from "preact"; -import { LabelWithTooltipMaybeRequired } from "./InputLine.js"; -import { useField } from "./useField.js"; -import { UIFormProps } from "./FormProvider.js"; - -export interface Choice<V> { - label: TranslatedString; - value: V; -} - -export function InputChoiceHorizontal<T extends object, K extends keyof T>( - props: { - choices: Choice<T[K]>[]; - } & UIFormProps<T, K>, -): VNode { - const { - choices, - name, - label, - tooltip, - help, - placeholder, - required, - before, - after, - converter, - } = props; - const { value, onChange, state, isDirty } = useField<T, K>(name); - if (state.hidden) { - return <Fragment />; - } - - return ( - <div class="sm:col-span-6"> - <LabelWithTooltipMaybeRequired - label={label} - required={required} - tooltip={tooltip} - /> - <fieldset class="mt-2"> - <div class="isolate inline-flex rounded-md shadow-sm"> - {choices.map((choice, idx) => { - const isFirst = idx === 0; - const isLast = idx === choices.length - 1; - let clazz = - "relative inline-flex items-center px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 focus:z-10"; - if (choice.value === value) { - clazz += - " text-white bg-indigo-600 hover:bg-indigo-500 ring-2 ring-indigo-600 hover:ring-indigo-500"; - } else { - clazz += " hover:bg-gray-100 border-gray-300"; - } - if (isFirst) { - clazz += " rounded-l-md"; - } else { - clazz += " -ml-px"; - } - if (isLast) { - clazz += " rounded-r-md"; - } - return ( - <button - type="button" - disabled={state.disabled} - label={choice.label} - class={clazz} - onClick={(e) => { - onChange( - (value === choice.value ? undefined : choice.value) as T[K], - ); - }} - > - {choice.label} - </button> - ); - })} - </div> - </fieldset> - {help && ( - <p class="mt-2 text-sm text-gray-500" id="email-description"> - {help} - </p> - )} - </div> - ); -} diff --git a/packages/aml-backoffice-ui/src/handlers/InputChoiceStacked.stories.tsx b/packages/aml-backoffice-ui/src/handlers/InputChoiceStacked.stories.tsx deleted file mode 100644 index 215418430..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputChoiceStacked.stories.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { TranslatedString } from "@gnu-taler/taler-util"; -import * as tests from "@gnu-taler/web-util/testing"; -import { - NiceForm as TestedComponent, -} from "./NiceForm.js"; -import { FlexibleForm } from "./forms.js"; - -export default { - title: "Input Choice Stacked", -}; - -export namespace Simplest { - export interface Form { - comment: string; - } -} - -type TargetObject = { - comment: string; -} -const initial: TargetObject = { - comment: "some initial comment" -} - -const form: FlexibleForm<TargetObject> = { - design: [{ - title: "this is a simple form" as TranslatedString, - fields: [{ - type: "choiceStacked", - props: { - label: "label of the field" as TranslatedString, - name: "comment", - choices: [{ - label: "first choice" as TranslatedString, - value: "1" - }, { - label: "second choice" as TranslatedString, - value: "2" - }, { - label: "thrid choice" as TranslatedString, - value: "3" - },], - }, - }] - }] -} - -export const SimpleComment = tests.createExample(TestedComponent, { initial, form }); diff --git a/packages/aml-backoffice-ui/src/handlers/InputChoiceStacked.tsx b/packages/aml-backoffice-ui/src/handlers/InputChoiceStacked.tsx deleted file mode 100644 index 48d367ff2..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputChoiceStacked.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { TranslatedString } from "@gnu-taler/taler-util"; -import { Fragment, VNode, h } from "preact"; -import { LabelWithTooltipMaybeRequired } from "./InputLine.js"; -import { useField } from "./useField.js"; -import { UIFormProps } from "./FormProvider.js"; - -export interface Choice<V> { - label: TranslatedString; - description?: TranslatedString; - value: V; -} - -export function InputChoiceStacked<T extends object, K extends keyof T>( - props: { - choices: Choice<T[K]>[]; - } & UIFormProps<T, K>, -): VNode { - const { - choices, - name, - label, - tooltip, - help, - placeholder, - required, - before, - after, - converter, - } = props; - const { value, onChange, state, isDirty } = useField<T, K>(name); - if (state.hidden) { - return <Fragment />; - } - - return ( - <div class="sm:col-span-6"> - <LabelWithTooltipMaybeRequired - label={label} - required={required} - tooltip={tooltip} - /> - <fieldset class="mt-2"> - <div class="space-y-4"> - {choices.map((choice) => { - // const currentValue = !converter - // ? choice.value - // : converter.fromStringUI(choice.value) ?? ""; - - let clazz = - "border relative block cursor-pointer rounded-lg bg-white px-6 py-4 shadow-sm focus:outline-none sm:flex sm:justify-between"; - if (choice.value === value) { - clazz += - " border-transparent border-indigo-600 ring-2 ring-indigo-600"; - } else { - clazz += " border-gray-300"; - } - - return ( - <label class={clazz}> - <input - type="radio" - name="server-size" - // defaultValue={choice.value} - disabled={state.disabled} - value={ - (!converter - ? (choice.value as string) - : converter?.toStringUI(choice.value)) ?? "" - } - onClick={(e) => { - onChange( - (value === choice.value - ? undefined - : choice.value) as T[K], - ); - }} - class="sr-only" - aria-labelledby="server-size-0-label" - aria-describedby="server-size-0-description-0 server-size-0-description-1" - /> - <span class="flex items-center"> - <span class="flex flex-col text-sm"> - <span - id="server-size-0-label" - class="font-medium text-gray-900" - > - {choice.label} - </span> - {choice.description !== undefined && ( - <span - id="server-size-0-description-0" - class="text-gray-500" - > - <span class="block sm:inline"> - {choice.description} - </span> - </span> - )} - </span> - </span> - </label> - ); - })} - </div> - </fieldset> - {help && ( - <p class="mt-2 text-sm text-gray-500" id="email-description"> - {help} - </p> - )} - </div> - ); -} diff --git a/packages/aml-backoffice-ui/src/handlers/InputFile.stories.tsx b/packages/aml-backoffice-ui/src/handlers/InputFile.stories.tsx deleted file mode 100644 index 8a1783bda..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputFile.stories.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { TranslatedString } from "@gnu-taler/taler-util"; -import * as tests from "@gnu-taler/web-util/testing"; -import { - NiceForm as TestedComponent, -} from "./NiceForm.js"; -import { FlexibleForm } from "./forms.js"; - -export default { - title: "Input File", -}; - -export namespace Simplest { - export interface Form { - comment: string; - } -} - -type TargetObject = { - comment: string; -} -const initial: TargetObject = { - comment: "some initial comment" -} - -const form: FlexibleForm<TargetObject> = { - design: [{ - title: "this is a simple form" as TranslatedString, - fields: [{ - type: "file", - props: { - label: "label of the field" as TranslatedString, - name: "comment", - required: true, - maxBites: 2 * 1024 * 1024, - accept: ".png", - tooltip: "this is a very long tooltip that explain what the field does without being short" as TranslatedString, - help: "Max size of 2 mega bytes" as TranslatedString, - }, - }] - }] -} - -export const SimpleComment = tests.createExample(TestedComponent, { initial, form }); diff --git a/packages/aml-backoffice-ui/src/handlers/InputFile.tsx b/packages/aml-backoffice-ui/src/handlers/InputFile.tsx deleted file mode 100644 index bc460f370..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputFile.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { Fragment, VNode, h } from "preact"; -import { LabelWithTooltipMaybeRequired } from "./InputLine.js"; -import { useField } from "./useField.js"; -import { UIFormProps, BehaviorResult } from "./FormProvider.js"; - -export function InputFile<T extends object, K extends keyof T>( - props: { maxBites: number; accept?: string } & UIFormProps<T, K>, -): VNode { - const { - name, - label, - placeholder, - tooltip, - required, - help: propsHelp, - maxBites, - accept, - } = props; - const { value, onChange, state } = useField<T, K>(name); - const help = propsHelp ?? state.help - if (state.hidden) { - return <div />; - } - return ( - <div class="col-span-full"> - <LabelWithTooltipMaybeRequired - label={label} - tooltip={tooltip} - required={required} - /> - {!value || !(value as string).startsWith("data:image/") ? ( - <div class="mt-2 flex justify-center rounded-lg border border-dashed border-gray-900/25 py-1"> - <div class="text-center"> - <svg - class="mx-auto h-12 w-12 text-gray-300" - viewBox="0 0 24 24" - fill="currentColor" - aria-hidden="true" - > - <path - fill-rule="evenodd" - d="M1.5 6a2.25 2.25 0 012.25-2.25h16.5A2.25 2.25 0 0122.5 6v12a2.25 2.25 0 01-2.25 2.25H3.75A2.25 2.25 0 011.5 18V6zM3 16.06V18c0 .414.336.75.75.75h16.5A.75.75 0 0021 18v-1.94l-2.69-2.689a1.5 1.5 0 00-2.12 0l-.88.879.97.97a.75.75 0 11-1.06 1.06l-5.16-5.159a1.5 1.5 0 00-2.12 0L3 16.061zm10.125-7.81a1.125 1.125 0 112.25 0 1.125 1.125 0 01-2.25 0z" - clip-rule="evenodd" - /> - </svg> - {!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> - ) : ( - <div class="mt-2 flex justify-center rounded-lg border border-dashed border-gray-900/25 relative"> - <img - src={value as string} - class=" h-24 w-full object-cover relative" - /> - - {!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>} - </div> - ); -} diff --git a/packages/aml-backoffice-ui/src/handlers/InputInteger.stories.tsx b/packages/aml-backoffice-ui/src/handlers/InputInteger.stories.tsx deleted file mode 100644 index 344865817..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputInteger.stories.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { TranslatedString } from "@gnu-taler/taler-util"; -import * as tests from "@gnu-taler/web-util/testing"; -import { - NiceForm as TestedComponent, -} from "./NiceForm.js"; -import { FlexibleForm } from "./forms.js"; - -export default { - title: "Input Integer", -}; - - -type TargetObject = { - age: number; -} -const initial: TargetObject = { - age: 5, -} - -const form: FlexibleForm<TargetObject> = { - design: [{ - title: "this is a simple form" as TranslatedString, - fields: [{ - type: "integer", - props: { - label: "label of the field" as TranslatedString, - name: "age", - tooltip: "just numbers" as TranslatedString, - }, - }] - }] -} - -export const SimpleComment = tests.createExample(TestedComponent, { initial, form }); diff --git a/packages/aml-backoffice-ui/src/handlers/InputInteger.tsx b/packages/aml-backoffice-ui/src/handlers/InputInteger.tsx deleted file mode 100644 index a6a02ad43..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputInteger.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { VNode, h } from "preact"; -import { InputLine } from "./InputLine.js"; -import { UIFormProps } from "./FormProvider.js"; - -export function InputInteger<T extends object, K extends keyof T>( - props: UIFormProps<T, K>, -): VNode { - return ( - <InputLine - type="number" - converter={{ - //@ts-ignore - fromStringUI: (v): number => { - return !v ? 0 : Number.parseInt(v, 10); - }, - //@ts-ignore - toStringUI: (v?: number): string => { - return v === undefined ? "" : String(v); - }, - }} - {...props} - /> - ); -} diff --git a/packages/aml-backoffice-ui/src/handlers/InputLine.stories.tsx b/packages/aml-backoffice-ui/src/handlers/InputLine.stories.tsx deleted file mode 100644 index 0d55bddf7..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputLine.stories.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { TranslatedString } from "@gnu-taler/taler-util"; -import * as tests from "@gnu-taler/web-util/testing"; -import { - NiceForm as TestedComponent, -} from "./NiceForm.js"; -import { FlexibleForm } from "./forms.js"; - -export default { - title: "Input Line", -}; - -export namespace Simplest { - export interface Form { - comment: string; - } -} - -type TargetObject = { - comment: string; -} -const initial: TargetObject = { - comment: "some initial comment" -} - -const form: FlexibleForm<TargetObject> = { - design: [{ - title: "this is a simple form" as TranslatedString, - fields: [{ - type: "text", - props: { - label: "label of the field" as TranslatedString, - name: "comment", - }, - }] - }] -} - -export const SimpleComment = tests.createExample(TestedComponent, { initial, form }); diff --git a/packages/aml-backoffice-ui/src/handlers/InputLine.tsx b/packages/aml-backoffice-ui/src/handlers/InputLine.tsx deleted file mode 100644 index 8c44b1ca5..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputLine.tsx +++ /dev/null @@ -1,268 +0,0 @@ -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"; -import { UIFormProps } from "./FormProvider.js"; - -//@ts-ignore -const TooltipIcon = ( - <svg - class="w-5 h-5" - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 20 20" - fill="currentColor" - > - <path - fill-rule="evenodd" - d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" - clip-rule="evenodd" - /> - </svg> -); - -export function LabelWithTooltipMaybeRequired({ - label, - required, - tooltip, -}: { - label: TranslatedString; - required?: boolean; - tooltip?: TranslatedString; -}): VNode { - const Label = ( - <Fragment> - <div class="flex justify-between"> - <label - htmlFor="email" - class="block text-sm font-medium leading-6 text-gray-900" - > - {label} - </label> - </div> - </Fragment> - ); - const WithTooltip = tooltip ? ( - <div class="relative flex flex-grow items-stretch focus-within:z-10"> - {Label} - <span class="relative flex items-center group pl-2"> - {TooltipIcon} - <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} - </div> - <div class="w-3 h-3 -mt-2 rotate-45 bg-black"></div> - </div> - </span> - </div> - ) : ( - Label - ); - if (required) { - return ( - <div class="flex justify-between"> - {WithTooltip} - <span class="text-sm leading-6 text-red-600">*</span> - </div> - ); - } - return WithTooltip; -} - -function InputWrapper<T extends object, K extends keyof T>({ - children, - label, - tooltip, - before, - after, - help, - error, - disabled, - required, -}: { error?: string; disabled: boolean, children: ComponentChildren } & UIFormProps<T, K>): VNode { - return ( - <div class="sm:col-span-6"> - <LabelWithTooltipMaybeRequired - label={label} - required={required} - tooltip={tooltip} - /> - <div class="relative mt-2 flex rounded-md shadow-sm"> - {before && - (before.type === "text" ? ( - <span class="inline-flex items-center rounded-l-md border border-r-0 border-gray-300 px-3 text-gray-500 sm:text-sm"> - {before.text} - </span> - ) : before.type === "icon" ? ( - <div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"> - {before.icon} - </div> - ) : 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" - > - {before.children} - </button> - ) : undefined)} - - {children} - - {after && - (after.type === "text" ? ( - <span class="inline-flex items-center rounded-r-md border border-l-0 border-gray-300 px-3 text-gray-500 sm:text-sm"> - {after.text} - </span> - ) : after.type === "icon" ? ( - <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"> - {after.icon} - </div> - ) : 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" - > - {after.children} - </button> - ) : undefined)} - </div> - {error && ( - <p class="mt-2 text-sm text-red-600" id="email-error"> - {error} - </p> - )} - {help && ( - <p class="mt-2 text-sm text-gray-500" id="email-description"> - {help} - </p> - )} - </div> - ); -} - -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<T extends object, K extends keyof T>( - props: { type: InputType } & UIFormProps<T, K>, -): VNode { - 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 = - "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"; - } - - if (type === "text-area") { - return ( - <InputWrapper<T, K> - {...props} - help={props.help ?? state.help} - disabled={state.disabled ?? false} - error={showError ? state.error : undefined} - > - <textarea - rows={4} - name={String(name)} - onChange={(e) => { - onChange(fromString(e.currentTarget.value)); - }} - placeholder={placeholder ? placeholder : undefined} - value={toString(value) ?? ""} - // defaultValue={toString(value)} - disabled={state.disabled} - aria-invalid={showError} - // aria-describedby="email-error" - class={clazz} - /> - </InputWrapper> - ); - } - - return ( - <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) => { - setText(e.currentTarget.value) - }} - placeholder={placeholder ? placeholder : undefined} - value={text} - onBlur={() => { - onChange(fromString(text)); - }} - // defaultValue={toString(value)} - disabled={state.disabled} - aria-invalid={showError} - // aria-describedby="email-error" - class={clazz} - /> - </InputWrapper> - ); -} diff --git a/packages/aml-backoffice-ui/src/handlers/InputSelectMultiple.stories.tsx b/packages/aml-backoffice-ui/src/handlers/InputSelectMultiple.stories.tsx deleted file mode 100644 index 4dac61f21..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputSelectMultiple.stories.tsx +++ /dev/null @@ -1,90 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { TranslatedString } from "@gnu-taler/taler-util"; -import * as tests from "@gnu-taler/web-util/testing"; -import { - NiceForm as TestedComponent, -} from "./NiceForm.js"; -import { FlexibleForm } from "./forms.js"; - -export default { - title: "Input Select Multiple", -}; - -export namespace Simplest { - export interface Form { - comment: string; - } -} - -type TargetObject = { - pets: string[]; - things: string[]; -} -const initial: TargetObject = { - pets: [], - things: [], -} - -const form: FlexibleForm<TargetObject> = { - design: [{ - title: "this is a simple form" as TranslatedString, - fields: [{ - type: "selectMultiple", - props: { - label: "allow diplicates" as TranslatedString, - name: "pets", - placeholder: "search..." as TranslatedString, - choices: [{ - label: "one label" as TranslatedString, - value: "one" - }, { - label: "two label" as TranslatedString, - value: "two" - }, { - label: "five label" as TranslatedString, - value: "five" - }] - }, - }, { - type: "selectMultiple", - props: { - label: "unique values" as TranslatedString, - name: "things", - unique: true, - placeholder: "search..." as TranslatedString, - choices: [{ - label: "one label" as TranslatedString, - value: "one" - }, { - label: "two label" as TranslatedString, - value: "two" - }, { - label: "five label" as TranslatedString, - value: "five" - }] - }, - }] - }] -} - -export const SimpleComment = tests.createExample(TestedComponent, { initial, form }); diff --git a/packages/aml-backoffice-ui/src/handlers/InputSelectMultiple.tsx b/packages/aml-backoffice-ui/src/handlers/InputSelectMultiple.tsx deleted file mode 100644 index 06eb91bb3..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputSelectMultiple.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { Fragment, VNode, h } from "preact"; -import { Choice } from "./InputChoiceStacked.js"; -import { LabelWithTooltipMaybeRequired } from "./InputLine.js"; -import { useField } from "./useField.js"; -import { useState } from "preact/hooks"; -import { UIFormProps } from "./FormProvider.js"; - -export function InputSelectMultiple<T extends object, K extends keyof T>( - props: { - choices: Choice<T[K]>[]; - unique?: boolean; - max?: number; - } & UIFormProps<T, K>, -): VNode { - const { name, label, choices, placeholder, tooltip, required, unique, max } = - props; - const { value, onChange, state } = useField<T, K>(name); - - const [filter, setFilter] = useState<string | undefined>(undefined); - const regex = new RegExp(`.*${filter}.*`, "i"); - const choiceMap = choices.reduce((prev, curr) => { - return { ...prev, [curr.value as string]: curr.label }; - }, {} as Record<string, string>); - - const list = (value ?? []) as string[]; - const filteredChoices = - filter === undefined - ? undefined - : choices.filter((v) => { - return regex.test(v.label); - }); - return ( - <div class="sm:col-span-6"> - <LabelWithTooltipMaybeRequired - label={label} - required={required} - tooltip={tooltip} - /> - {list.map((v, idx) => { - return ( - <span class="inline-flex items-center gap-x-0.5 rounded-md bg-gray-100 p-1 mr-2 text-xs font-medium text-gray-600"> - {choiceMap[v]} - <button - type="button" - disabled={state.disabled} - onClick={() => { - const newValue = [...list]; - newValue.splice(idx, 1); - onChange(newValue as T[K]); - setFilter(undefined); - }} - class="group relative h-5 w-5 rounded-sm hover:bg-gray-500/20" - > - <span class="sr-only">Remove</span> - <svg - viewBox="0 0 14 14" - class="h-5 w-5 stroke-gray-700/50 group-hover:stroke-gray-700/75" - > - <path d="M4 4l6 6m0-6l-6 6" /> - </svg> - <span class="absolute -inset-1"></span> - </button> - </span> - ); - })} - - {!state.disabled && <div class="relative mt-2"> - <input - id="combobox" - type="text" - value={filter ?? ""} - onChange={(e) => { - setFilter(e.currentTarget.value); - }} - placeholder={placeholder} - class="w-full rounded-md border-0 bg-white py-1.5 pl-3 pr-12 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" - role="combobox" - aria-controls="options" - aria-expanded="false" - /> - <button - type="button" - disabled={state.disabled} - onClick={() => { - setFilter(filter === undefined ? "" : undefined); - }} - class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none" - > - <svg - class="h-5 w-5 text-gray-400" - viewBox="0 0 20 20" - fill="currentColor" - aria-hidden="true" - > - <path - fill-rule="evenodd" - d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z" - clip-rule="evenodd" - /> - </svg> - </button> - - {filteredChoices !== undefined && ( - <ul - class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" - id="options" - role="listbox" - > - {filteredChoices.map((v, idx) => { - return ( - <li - class="relative cursor-pointer select-none py-2 pl-3 pr-9 text-gray-900 hover:text-white hover:bg-indigo-600" - id="option-0" - role="option" - onClick={() => { - setFilter(undefined); - if (unique && list.indexOf(v.value as string) !== -1) { - return; - } - if (max !== undefined && list.length >= max) { - return; - } - const newValue = [...list]; - newValue.splice(0, 0, v.value as string); - onChange(newValue as T[K]); - }} - - // tabindex="-1" - > - {/* <!-- Selected: "font-semibold" --> */} - <span class="block truncate">{v.label}</span> - - {/* <!-- - Checkmark, only display for selected option. - - Active: "text-white", Not Active: "text-indigo-600" - --> */} - </li> - ); - })} - - {/* <!-- - Combobox option, manage highlight styles based on mouseenter/mouseleave and keyboard navigation. - - Active: "text-white bg-indigo-600", Not Active: "text-gray-900" - --> */} - - {/* <!-- More items... --> */} - </ul> - )} - </div>} - </div> - ); -} diff --git a/packages/aml-backoffice-ui/src/handlers/InputSelectOne.stories.tsx b/packages/aml-backoffice-ui/src/handlers/InputSelectOne.stories.tsx deleted file mode 100644 index 0bb871500..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputSelectOne.stories.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { TranslatedString } from "@gnu-taler/taler-util"; -import * as tests from "@gnu-taler/web-util/testing"; -import { - NiceForm as TestedComponent, -} from "./NiceForm.js"; -import { FlexibleForm } from "./forms.js"; - -export default { - title: "Input Select One", -}; - -export namespace Simplest { - export interface Form { - comment: string; - } -} - -type TargetObject = { - things: string; -} -const initial: TargetObject = { - things: "one" -} - -const form: FlexibleForm<TargetObject> = { - design: [{ - title: "this is a simple form" as TranslatedString, - fields: [{ - type: "selectOne", - props: { - label: "label of the field" as TranslatedString, - name: "things", - placeholder: "search..." as TranslatedString, - choices: [{ - label: "one label" as TranslatedString, - value: "one" - }, { - label: "two label" as TranslatedString, - value: "two" - }, { - label: "five label" as TranslatedString, - value: "five" - }] - }, - }] - }] -} - -export const SimpleComment = tests.createExample(TestedComponent, { initial, form }); diff --git a/packages/aml-backoffice-ui/src/handlers/InputSelectOne.tsx b/packages/aml-backoffice-ui/src/handlers/InputSelectOne.tsx deleted file mode 100644 index 98430306e..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputSelectOne.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { Fragment, VNode, h } from "preact"; -import { Choice } from "./InputChoiceStacked.js"; -import { LabelWithTooltipMaybeRequired } from "./InputLine.js"; -import { useField } from "./useField.js"; -import { useState } from "preact/hooks"; -import { UIFormProps } from "./FormProvider.js"; - -export function InputSelectOne<T extends object, K extends keyof T>( - props: { - choices: Choice<T[K]>[]; - } & UIFormProps<T, K>, -): VNode { - const { name, label, choices, placeholder, tooltip, required } = props; - const { value, onChange } = useField<T, K>(name); - - const [filter, setFilter] = useState<string | undefined>(undefined); - const regex = new RegExp(`.*${filter}.*`, "i"); - const choiceMap = choices.reduce((prev, curr) => { - return { ...prev, [curr.value as string]: curr.label }; - }, {} as Record<string, string>); - - const filteredChoices = - filter === undefined - ? undefined - : choices.filter((v) => { - return regex.test(v.label); - }); - return ( - <div class="sm:col-span-6"> - <LabelWithTooltipMaybeRequired - label={label} - required={required} - tooltip={tooltip} - /> - {value ? ( - <span class="inline-flex items-center gap-x-0.5 rounded-md bg-gray-100 p-1 mr-2 font-medium text-gray-600"> - {choiceMap[value as string]} - <button - type="button" - onClick={() => { - onChange(undefined!); - }} - class="group relative h-5 w-5 rounded-sm hover:bg-gray-500/20" - > - <span class="sr-only">Remove</span> - <svg - viewBox="0 0 14 14" - class="h-5 w-5 stroke-gray-700/50 group-hover:stroke-gray-700/75" - > - <path d="M4 4l6 6m0-6l-6 6" /> - </svg> - <span class="absolute -inset-1"></span> - </button> - </span> - ) : ( - <div class="relative mt-2"> - <input - id="combobox" - type="text" - value={filter ?? ""} - onChange={(e) => { - setFilter(e.currentTarget.value); - }} - placeholder={placeholder} - class="w-full rounded-md border-0 bg-white py-1.5 pl-3 pr-12 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" - role="combobox" - aria-controls="options" - aria-expanded="false" - /> - <button - type="button" - onClick={() => { - setFilter(filter === undefined ? "" : undefined); - }} - class="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none" - > - <svg - class="h-5 w-5 text-gray-400" - viewBox="0 0 20 20" - fill="currentColor" - aria-hidden="true" - > - <path - fill-rule="evenodd" - d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z" - clip-rule="evenodd" - /> - </svg> - </button> - - {filteredChoices !== undefined && ( - <ul - class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm" - id="options" - role="listbox" - > - {filteredChoices.map((v, idx) => { - return ( - <li - class="relative cursor-pointer select-none py-2 pl-3 pr-9 text-gray-900 hover:text-white hover:bg-indigo-600" - id="option-0" - role="option" - onClick={() => { - setFilter(undefined); - onChange(v.value as T[K]); - }} - - // tabindex="-1" - > - {/* <!-- Selected: "font-semibold" --> */} - <span class="block truncate">{v.label}</span> - - {/* <!-- - Checkmark, only display for selected option. - - Active: "text-white", Not Active: "text-indigo-600" - --> */} - </li> - ); - })} - - {/* <!-- - Combobox option, manage highlight styles based on mouseenter/mouseleave and keyboard navigation. - - Active: "text-white bg-indigo-600", Not Active: "text-gray-900" - --> */} - - {/* <!-- More items... --> */} - </ul> - )} - </div> - )} - </div> - ); -} diff --git a/packages/aml-backoffice-ui/src/handlers/InputText.stories.tsx b/packages/aml-backoffice-ui/src/handlers/InputText.stories.tsx deleted file mode 100644 index 9ce733d4a..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputText.stories.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { TranslatedString } from "@gnu-taler/taler-util"; -import * as tests from "@gnu-taler/web-util/testing"; -import { - NiceForm as TestedComponent, -} from "./NiceForm.js"; -import { FlexibleForm } from "./forms.js"; - -export default { - title: "Input Text", -}; - -export namespace Simplest { - export interface Form { - comment: string; - } -} - -type TargetObject = { - comment: string; -} -const initial: TargetObject = { - comment: "some initial comment" -} - -const form: FlexibleForm<TargetObject> = { - design: [{ - title: "this is a simple form" as TranslatedString, - fields: [{ - type: "text", - props: { - label: "label of the field" as TranslatedString, - name: "comment", - }, - }] - }] -} - -export const SimpleComment = tests.createExample(TestedComponent, { initial, form }); diff --git a/packages/aml-backoffice-ui/src/handlers/InputText.tsx b/packages/aml-backoffice-ui/src/handlers/InputText.tsx deleted file mode 100644 index 7ad36b737..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputText.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { VNode, h } from "preact"; -import { InputLine } from "./InputLine.js"; -import { UIFormProps } from "./FormProvider.js"; - -export function InputText<T extends object, K extends keyof T>( - props: UIFormProps<T, K>, -): VNode { - return <InputLine type="text" {...props} />; -} diff --git a/packages/aml-backoffice-ui/src/handlers/InputTextArea.stories.tsx b/packages/aml-backoffice-ui/src/handlers/InputTextArea.stories.tsx deleted file mode 100644 index df35b25c4..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputTextArea.stories.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { TranslatedString } from "@gnu-taler/taler-util"; -import * as tests from "@gnu-taler/web-util/testing"; -import { - NiceForm as TestedComponent, -} from "./NiceForm.js"; -import { FlexibleForm } from "./forms.js"; - -export default { - title: "Input Text Area", -}; - -export namespace Simplest { - export interface Form { - comment: string; - } -} - -type TargetObject = { - comment: string; -} -const initial: TargetObject = { - comment: "some initial comment" -} - -const form: FlexibleForm<TargetObject> = { - design: [{ - title: "this is a simple form" as TranslatedString, - fields: [{ - type: "text", - props: { - label: "label of the field" as TranslatedString, - name: "comment", - }, - }] - }] -} - -export const SimpleComment = tests.createExample(TestedComponent, { initial, form }); diff --git a/packages/aml-backoffice-ui/src/handlers/InputTextArea.tsx b/packages/aml-backoffice-ui/src/handlers/InputTextArea.tsx deleted file mode 100644 index 6b76d8329..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputTextArea.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { VNode, h } from "preact"; -import { InputLine } from "./InputLine.js"; -import { UIFormProps } from "./FormProvider.js"; - -export function InputTextArea<T extends object, K extends keyof T>( - props: UIFormProps<T, K>, -): VNode { - return <InputLine type="text-area" {...props} />; -} diff --git a/packages/aml-backoffice-ui/src/handlers/InputToggle.stories.tsx b/packages/aml-backoffice-ui/src/handlers/InputToggle.stories.tsx deleted file mode 100644 index 735e812f3..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputToggle.stories.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2022 Taler Systems S.A. - - GNU Taler is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * - * @author Sebastian Javier Marchano (sebasjm) - */ - -import { TranslatedString } from "@gnu-taler/taler-util"; -import * as tests from "@gnu-taler/web-util/testing"; -import { - NiceForm as TestedComponent, -} from "./NiceForm.js"; -import { FlexibleForm } from "./forms.js"; - -export default { - title: "Input Toggle", -}; - -export namespace Simplest { - export interface Form { - comment: string; - } -} - -type TargetObject = { - comment: string; -} -const initial: TargetObject = { - comment: "some initial comment" -} - -const form: FlexibleForm<TargetObject> = { - design: [{ - title: "this is a simple form" as TranslatedString, - fields: [{ - type: "toggle", - props: { - label: "label of the field" as TranslatedString, - name: "comment", - }, - }] - }] -} - -export const SimpleComment = tests.createExample(TestedComponent, { initial, form }); diff --git a/packages/aml-backoffice-ui/src/handlers/InputToggle.tsx b/packages/aml-backoffice-ui/src/handlers/InputToggle.tsx deleted file mode 100644 index 1ea8699b2..000000000 --- a/packages/aml-backoffice-ui/src/handlers/InputToggle.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { VNode, h } from "preact"; -import { InputLine, LabelWithTooltipMaybeRequired } from "./InputLine.js"; -import { UIFormProps } from "./FormProvider.js"; -import { useField } from "./useField.js"; - -export function InputToggle<T extends object, K extends keyof T>( - props: UIFormProps<T, K>, -): VNode { - const { - name, - label, - tooltip, - help, - placeholder, - required, - before, - after, - converter, - } = props; - const { value, onChange, state, isDirty } = useField<T, K>(name); - - const isOn = !!value - return <div class="sm:col-span-6"> - <div class="flex items-center justify-between"> - <LabelWithTooltipMaybeRequired - label={label} - required={required} - tooltip={tooltip} - /> - <button type="button" data-enabled={isOn} - class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2" - role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description" - onClick={() => { onChange(!isOn as any); }}> - <span aria-hidden="true" data-enabled={isOn} class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span> - </button> - </div> - </div> -} diff --git a/packages/aml-backoffice-ui/src/handlers/NiceForm.tsx b/packages/aml-backoffice-ui/src/handlers/NiceForm.tsx deleted file mode 100644 index d01b80b02..000000000 --- a/packages/aml-backoffice-ui/src/handlers/NiceForm.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { ComponentChildren, Fragment, h } from "preact"; -import { FormProvider } from "./FormProvider.js"; -import { FlexibleForm, RenderAllFieldsByUiConfig } from "./forms.js"; - -export function NiceForm<T extends object>({ - initial, - onUpdate, - form, - onSubmit, - children, - readOnly, -}: { - children?: ComponentChildren; - initial: Partial<T>; - onSubmit?: (v: Partial<T>) => void; - form: FlexibleForm<T>; - readOnly?: boolean; - onUpdate?: (d: Partial<T>) => void; -}) { - return ( - <FormProvider - initialValue={initial} - onUpdate={onUpdate} - onSubmit={onSubmit} - readOnly={readOnly} - computeFormState={form.behavior} - > - <div class="space-y-10 divide-y -mt-5 divide-gray-900/10"> - {form.design.map((section, i) => { - if (!section) return <Fragment />; - return ( - <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-5 md:grid-cols-3"> - <div class="px-4 sm:px-0"> - <h2 class="text-base font-semibold leading-7 text-gray-900"> - {section.title} - </h2> - {section.description && ( - <p class="mt-1 text-sm leading-6 text-gray-600"> - {section.description} - </p> - )} - </div> - <div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-md md:col-span-2"> - <div class="p-3"> - <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> - <RenderAllFieldsByUiConfig - key={i} - fields={section.fields} - /> - </div> - </div> - </div> - </div> - ); - })} - </div> - {children} - </FormProvider> - ); -} diff --git a/packages/aml-backoffice-ui/src/handlers/TimePicker.tsx b/packages/aml-backoffice-ui/src/handlers/TimePicker.tsx deleted file mode 100644 index c6dc3e794..000000000 --- a/packages/aml-backoffice-ui/src/handlers/TimePicker.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { AbsoluteTime } from "@gnu-taler/taler-util" -import { useTranslationContext } from "@gnu-taler/web-util/browser" -import { startOfDay, getHours, getMinutes, getSeconds, setHours } from "date-fns" -import { Fragment, VNode, h } from "preact" -import { useState } from "preact/hooks" - -export function TimePicker({ value, onChange, onConfirm }: { value: AbsoluteTime | undefined, onChange: (v: AbsoluteTime) => void, onConfirm: () => void }): VNode { - const date = !value ? new Date() : new Date(AbsoluteTime.toStampMs(value)) - const hours = getHours(date) % 12 - const minutes = getMinutes(date) - const seconds = getSeconds(date) - - const { i18n } = useTranslationContext() - - return <Fragment> - <div class="flex flex-col bg-white rounded-t-sm justify-around" > - {/* time selection */} - <div id="" class="bg-[#3b71ca] dark:bg-zinc-700 h-24 rounded-t-lg p-12 flex flex-row items-center justify-center"> - <div class="flex w-full justify-evenly"> - <div class=""> - <span class="relative h-full"> - <button type="button" class="py-1 px-3 text-[3.75rem] font-light leading-[1.2] text-white opacity-[.54] border-none bg-transparent p-0 cursor-pointer hover:bg-[#00000026] hover:outline-none focus:bg-[#00000026] focus:outline-none " - style="pointer-events: none;"> - {new String(hours).padStart(2, "0")} - </button> - </span> - <span type="button" class="font-light leading-[1.2] text-[3.75rem] opacity-[.54] border-none bg-transparent p-0 text-white " >:</span> - <span class="relative h-full"> - <button type="button" class="py-1 px-3 text-[3.75rem] font-light leading-[1.2] text-white opacity-[.54] border-none bg-transparent p-0 cursor-pointer hover:bg-[#00000026] hover:outline-none focus:bg-[#00000026] focus:outline-none " > - {new String(minutes).padStart(2, "0")} - </button> - </span> - <span type="button" class="font-light leading-[1.2] text-[3.75rem] opacity-[.54] border-none bg-transparent p-0 text-white " >:</span> - <span class="relative h-full"> - <button type="button" class="py-1 px-3 text-[3.75rem] font-light leading-[1.2] text-white opacity-[.54] border-none bg-transparent p-0 cursor-pointer hover:bg-[#00000026] hover:outline-none focus:bg-[#00000026] focus:outline-none " > - {new String(seconds).padStart(2, "0")} - </button> - </span> - </div> - <div class="flex flex-col justify-center text-[18px] text-[#ffffff8a] "> - <button type="button" class="py-1 px-3 bg-transparent border-none text-white cursor-pointer hover:bg-[#00000026] hover:outline-none focus:bg-[#00000026] focus:outline-none" > - AM - </button> - <button type="button" class="py-1 px-3 bg-transparent border-none text-white cursor-pointer hover:bg-[#00000026] hover:outline-none focus:bg-[#00000026] focus:outline-none" > - PM - </button> - </div> - </div> - </div> - {/* clock */} - <div id="" class="mt-2 min-w-[310px] max-w-[325px] min-h-[305px] overflow-x-hidden h-full flex justify-center mx-auto flex-col items-center dark:bg-zinc-500" > - <div class="relative rounded-[100%] w-[260px] h-[260px] cursor-default my-0 mx-auto bg-[#00000012] dark:bg-zinc-600/50 animate-[show-up-clock_350ms_linear]" > - - <span class="top-1/2 left-1/2 w-[6px] h-[6px] -translate-y-1/2 -translate-x-1/2 rounded-[50%] bg-[#3b71ca] absolute" ></span> - <div class="bg-[#3b71ca] bottom-1/2 h-2/5 left-[calc(50%-1px)] rtl:!left-auto origin-[center_bottom_0] rtl:!origin-[50%_50%_0] w-[2px] absolute" style={{ transform: "rotateZ(60deg)", height: "calc(35% + 1px)" }}> - {/* <div class="-top-[21px] -left-[15px] w-[4px] border-[14px] border-solid border-[#3b71ca] h-[4px] box-content rounded-[100%] absolute" style="background-color: rgb(25, 118, 210);"></div> */} - </div> - - <span onClick={() => onChange(AbsoluteTime.fromStampMs(setHours(date, 12).getTime()))} class="absolute rounded-[100%] w-[32px] h-[32px] text-center cursor-pointer text-[1.1rem] bg-transparent flex justify-center items-center font-light focus:outline-none selection:bg-transparent data-[selected=true]:text-white data-[selected=true]:bg-[#3b71ca] data-[selected=true]:font-normal" style="left: 114px; bottom: 224px;"> - <span>0</span> - </span> - <span onClick={() => onChange(AbsoluteTime.fromStampMs(setHours(date, 1).getTime()))} class="absolute rounded-[100%] w-[32px] h-[32px] text-center cursor-pointer text-[1.1rem] bg-transparent flex justify-center items-center font-light focus:outline-none selection:bg-transparent data-[selected=true]:text-white data-[selected=true]:bg-[#3b71ca] data-[selected=true]:font-normal" style="left: 169px; bottom: 209.263px;"> - <span >1</span> - </span> - <span onClick={() => onChange(AbsoluteTime.fromStampMs(setHours(date, 2).getTime()))} class="absolute rounded-[100%] w-[32px] h-[32px] text-center cursor-pointer text-[1.1rem] bg-transparent flex justify-center items-center font-light focus:outline-none selection:bg-transparent data-[selected=true]:text-white data-[selected=true]:bg-[#3b71ca] data-[selected=true]:font-normal" data-selected={true} style="left: 209.263px; bottom: 169px;" > - <span >2</span> - </span> - <span onClick={() => onChange(AbsoluteTime.fromStampMs(setHours(date, 3).getTime()))} class="absolute rounded-[100%] w-[32px] h-[32px] text-center cursor-pointer text-[1.1rem] bg-transparent flex justify-center items-center font-light focus:outline-none selection:bg-transparent data-[selected=true]:text-white data-[selected=true]:bg-[#3b71ca] data-[selected=true]:font-normal" style="left: 224px; bottom: 114px;"> - <span >3</span> - </span> - <span onClick={() => onChange(AbsoluteTime.fromStampMs(setHours(date, 4).getTime()))} class="absolute rounded-[100%] w-[32px] h-[32px] text-center cursor-pointer text-[1.1rem] bg-transparent flex justify-center items-center font-light focus:outline-none selection:bg-transparent data-[selected=true]:text-white data-[selected=true]:bg-[#3b71ca] data-[selected=true]:font-normal" style="left: 209.263px; bottom: 59px;"> - <span >4</span> - </span> - <span onClick={() => onChange(AbsoluteTime.fromStampMs(setHours(date, 5).getTime()))} class="absolute rounded-[100%] w-[32px] h-[32px] text-center cursor-pointer text-[1.1rem] bg-transparent flex justify-center items-center font-light focus:outline-none selection:bg-transparent data-[selected=true]:text-white data-[selected=true]:bg-[#3b71ca] data-[selected=true]:font-normal" style="left: 169px; bottom: 18.7372px;"> - <span >5</span> - </span> - <span onClick={() => onChange(AbsoluteTime.fromStampMs(setHours(date, 6).getTime()))} class="absolute rounded-[100%] w-[32px] h-[32px] text-center cursor-pointer text-[1.1rem] bg-transparent flex justify-center items-center font-light focus:outline-none selection:bg-transparent data-[selected=true]:text-white data-[selected=true]:bg-[#3b71ca] data-[selected=true]:font-normal" style="left: 114px; bottom: 4px;"> - <span >6</span> - </span> - <span onClick={() => onChange(AbsoluteTime.fromStampMs(setHours(date, 7).getTime()))} class="absolute rounded-[100%] w-[32px] h-[32px] text-center cursor-pointer text-[1.1rem] bg-transparent flex justify-center items-center font-light focus:outline-none selection:bg-transparent data-[selected=true]:text-white data-[selected=true]:bg-[#3b71ca] data-[selected=true]:font-normal" style="left: 59px; bottom: 18.7372px;"> - <span >7</span> - </span> - <span onClick={() => onChange(AbsoluteTime.fromStampMs(setHours(date, 8).getTime()))} class="absolute rounded-[100%] w-[32px] h-[32px] text-center cursor-pointer text-[1.1rem] bg-transparent flex justify-center items-center font-light focus:outline-none selection:bg-transparent data-[selected=true]:text-white data-[selected=true]:bg-[#3b71ca] data-[selected=true]:font-normal" style="left: 18.7372px; bottom: 59px;"> - <span >8</span> - </span> - <span onClick={() => onChange(AbsoluteTime.fromStampMs(setHours(date, 9).getTime()))} class="absolute rounded-[100%] w-[32px] h-[32px] text-center cursor-pointer text-[1.1rem] bg-transparent flex justify-center items-center font-light focus:outline-none selection:bg-transparent data-[selected=true]:text-white data-[selected=true]:bg-[#3b71ca] data-[selected=true]:font-normal" style="left: 4px; bottom: 114px;"> - <span >9</span> - </span> - <span onClick={() => onChange(AbsoluteTime.fromStampMs(setHours(date, 10).getTime()))} class="absolute rounded-[100%] w-[32px] h-[32px] text-center cursor-pointer text-[1.1rem] bg-transparent flex justify-center items-center font-light focus:outline-none selection:bg-transparent data-[selected=true]:text-white data-[selected=true]:bg-[#3b71ca] data-[selected=true]:font-normal" style="left: 18.7372px; bottom: 169px;"> - <span >10</span> - </span> - <span onClick={() => onChange(AbsoluteTime.fromStampMs(setHours(date, 11).getTime()))} class="absolute rounded-[100%] w-[32px] h-[32px] text-center cursor-pointer text-[1.1rem] bg-transparent flex justify-center items-center font-light focus:outline-none selection:bg-transparent data-[selected=true]:text-white data-[selected=true]:bg-[#3b71ca] data-[selected=true]:font-normal" style="left: 59px; bottom: 209.263px;"> - <span >11</span> - </span> - </div> - </div> - </div> - <div id="" class="rounded-b-lg flex justify-between items-center w-full h-[56px] px-[12px] bg-white dark:bg-zinc-500"> - <div class="w-full flex justify-end"> - <button - type="submit" - onClick={onConfirm} - class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" - > - <i18n.Translate>Confirm</i18n.Translate> - </button> - </div> - </div> - </Fragment> -} diff --git a/packages/aml-backoffice-ui/src/handlers/forms.ts b/packages/aml-backoffice-ui/src/handlers/forms.ts deleted file mode 100644 index 1c212fafa..000000000 --- a/packages/aml-backoffice-ui/src/handlers/forms.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { TranslatedString } from "@gnu-taler/taler-util"; -import { InputText } from "./InputText.js"; -import { InputAbsoluteTime } from "./InputAbsoluteTime.js"; -import { InputInteger } from "./InputInteger.js"; -import { h as create, Fragment, VNode } from "preact"; -import { InputChoiceStacked } from "./InputChoiceStacked.js"; -import { InputArray } from "./InputArray.js"; -import { InputSelectMultiple } from "./InputSelectMultiple.js"; -import { InputTextArea } from "./InputTextArea.js"; -import { InputFile } from "./InputFile.js"; -import { Caption } from "./Caption.js"; -import { Group } from "./Group.js"; -import { InputSelectOne } from "./InputSelectOne.js"; -import { FormProvider, FormState } from "./FormProvider.js"; -import { InputLine } from "./InputLine.js"; -import { InputAmount } from "./InputAmount.js"; -import { InputChoiceHorizontal } from "./InputChoiceHorizontal.js"; -import { InputToggle } from "./InputToggle.js"; - -export type DoubleColumnForm = Array<DoubleColumnFormSection | undefined>; - -export type DoubleColumnFormSection = { - title: TranslatedString; - description?: TranslatedString; - fields: UIFormField[]; -}; -export interface FlexibleForm<T extends object> { - design: DoubleColumnForm; - behavior?: (form: Partial<T>) => FormState<T>; -} - -/** - * Constrain the type with the ui props - */ -type FieldType<T extends object = any, K extends keyof T = any> = { - group: Parameters<typeof Group>[0]; - caption: Parameters<typeof Caption>[0]; - array: Parameters<typeof InputArray<T, K>>[0]; - file: Parameters<typeof InputFile<T, K>>[0]; - selectOne: Parameters<typeof InputSelectOne<T, K>>[0]; - selectMultiple: Parameters<typeof InputSelectMultiple<T, K>>[0]; - text: Parameters<typeof InputText<T, K>>[0]; - textArea: Parameters<typeof InputTextArea<T, K>>[0]; - choiceStacked: Parameters<typeof InputChoiceStacked<T, K>>[0]; - choiceHorizontal: Parameters<typeof InputChoiceHorizontal<T, K>>[0]; - absoluteTime: Parameters<typeof InputAbsoluteTime<T, K>>[0]; - integer: Parameters<typeof InputInteger<T, K>>[0]; - toggle: Parameters<typeof InputToggle<T, K>>[0]; - amount: Parameters<typeof InputAmount<T, K>>[0]; -}; - -/** - * List all the form fields so typescript can type-check the form instance - */ -export type UIFormField = - | { type: "group"; props: FieldType["group"] } - | { type: "caption"; props: FieldType["caption"] } - | { type: "array"; props: FieldType["array"] } - | { type: "file"; props: FieldType["file"] } - | { type: "amount"; props: FieldType["amount"] } - | { type: "selectOne"; props: FieldType["selectOne"] } - | { type: "selectMultiple"; props: FieldType["selectMultiple"] } - | { type: "text"; props: FieldType["text"] } - | { type: "textArea"; props: FieldType["textArea"] } - | { type: "choiceStacked"; props: FieldType["choiceStacked"] } - | { type: "choiceHorizontal"; props: FieldType["choiceHorizontal"] } - | { type: "integer"; props: FieldType["integer"] } - | { type: "toggle"; props: FieldType["toggle"] } - | { type: "absoluteTime"; props: FieldType["absoluteTime"] }; - -type FieldComponentFunction<key extends keyof FieldType> = ( - props: FieldType[key], -) => VNode; - -type UIFormFieldMap = { - [key in keyof FieldType]: FieldComponentFunction<key>; -}; - -/** - * Maps input type with component implementation - */ -const UIFormConfiguration: UIFormFieldMap = { - group: Group, - caption: Caption, - //@ts-ignore - array: InputArray, - text: InputText, - //@ts-ignore - file: InputFile, - textArea: InputTextArea, - //@ts-ignore - absoluteTime: InputAbsoluteTime, - //@ts-ignore - choiceStacked: InputChoiceStacked, - //@ts-ignore - choiceHorizontal: InputChoiceHorizontal, - integer: InputInteger, - //@ts-ignore - selectOne: InputSelectOne, - //@ts-ignore - selectMultiple: InputSelectMultiple, - //@ts-ignore - toggle: InputToggle, - //@ts-ignore - amount: InputAmount, -}; - -export function RenderAllFieldsByUiConfig({ - fields, -}: { - fields: UIFormField[]; -}): VNode { - return create( - Fragment, - {}, - fields.map((field, i) => { - const Component = UIFormConfiguration[ - field.type - ] as FieldComponentFunction<any>; - return Component(field.props); - }), - ); -} - -type FormSet<T extends object> = { - Provider: typeof FormProvider<T>; - InputLine: <K extends keyof T>() => typeof InputLine<T, K>; - InputChoiceHorizontal: <K extends keyof T>() => typeof InputChoiceHorizontal<T, K>; -}; -export function createNewForm<T extends object>() { - const res: FormSet<T> = { - Provider: FormProvider, - InputLine: () => InputLine, - InputChoiceHorizontal: () => InputChoiceHorizontal, - }; - return { - Provider: res.Provider, - InputLine: res.InputLine(), - InputChoiceHorizontal: res.InputChoiceHorizontal(), - }; -} diff --git a/packages/aml-backoffice-ui/src/handlers/index.stories.ts b/packages/aml-backoffice-ui/src/handlers/index.stories.ts deleted file mode 100644 index 55878cb02..000000000 --- a/packages/aml-backoffice-ui/src/handlers/index.stories.ts +++ /dev/null @@ -1,13 +0,0 @@ -export * as a1 from "./InputAmount.stories.js"; -export * as a2 from "./InputArray.stories.js"; -export * as a3 from "./InputChoiceHorizontal.stories.js"; -export * as a4 from "./InputChoiceStacked.stories.js"; -export * as a5 from "./InputAbsoluteTime.stories.js"; -export * as a6 from "./InputFile.stories.js"; -export * as a7 from "./InputInteger.stories.js"; -export * as a8 from "./InputLine.stories.js"; -export * as a9 from "./InputSelectMultiple.stories.js"; -export * as a10 from "./InputSelectOne.stories.js"; -export * as a11 from "./InputText.stories.js"; -export * as a12 from "./InputTextArea.stories.js"; -export * as a13 from "./InputToggle.stories.js"; diff --git a/packages/aml-backoffice-ui/src/handlers/useField.ts b/packages/aml-backoffice-ui/src/handlers/useField.ts deleted file mode 100644 index 651778628..000000000 --- a/packages/aml-backoffice-ui/src/handlers/useField.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { useContext, useState } from "preact/compat"; -import { BehaviorResult, FormContext, InputFieldState } from "./FormProvider.js"; - -export interface InputFieldHandler<Type> { - value: Type; - onChange: (s: Type) => void; - state: BehaviorResult; - isDirty: boolean; -} - -export function useField<T extends object, K extends keyof T>( - name: K, -): InputFieldHandler<T[K]> { - const { - initialValue, - value: formValue, - computeFormState, - onUpdate: notifyUpdate, - readOnly: readOnlyForm, - } = useContext(FormContext); - - type P = typeof name; - type V = T[P]; - const formState = computeFormState ? computeFormState(formValue.current) : {}; - - const fieldValue = readField(formValue.current, String(name)) as V; - // console.log("USE FIELD", String(name), formValue.current, fieldValue); - const [currentValue, setCurrentValue] = useState<any | undefined>(fieldValue); - const fieldState = - readField<Partial<BehaviorResult>>(formState, String(name)) ?? {}; - - //compute default state - const state = { - disabled: readOnlyForm ? true : (fieldState.disabled ?? false), - readonly: readOnlyForm ? true : (fieldState.readonly ?? false), - hidden: fieldState.hidden ?? false, - error: fieldState.error, - help: fieldState.help, - elements: "elements" in fieldState ? fieldState.elements ?? [] : [], - }; - - function onChange(value: V): void { - setCurrentValue(value); - formValue.current = setValueDeeper( - formValue.current, - String(name).split("."), - value, - ); - if (notifyUpdate) { - notifyUpdate(formValue.current); - } - } - - return { - value: fieldValue, - onChange, - isDirty: currentValue !== undefined, - state, - }; -} - -/** - * read the field of an object an support accessing it using '.' - * - * @param object - * @param name - * @returns - */ -function readField<T>( - object: any, - name: string, - debug?: boolean, -): T | undefined { - return name.split(".").reduce((prev, current) => { - if (debug) { - console.log( - "READ", - name, - prev, - current, - prev ? prev[current] : undefined, - ); - } - return prev ? prev[current] : undefined; - }, object); -} - -function setValueDeeper(object: any, names: string[], value: any): any { - if (names.length === 0) return value; - const [head, ...rest] = names; - if (object === undefined) { - return { [head]: setValueDeeper({}, rest, value) }; - } - return { ...object, [head]: setValueDeeper(object[head] ?? {}, rest, value) }; -} |