diff options
| author | Sebastian <sebasjm@gmail.com> | 2023-05-16 01:23:44 -0300 |
|---|---|---|
| committer | Sebastian <sebasjm@gmail.com> | 2023-05-16 01:23:44 -0300 |
| commit | 245ab840baf1926ef2c03a8965fce85012887d92 (patch) | |
| tree | 1acec43e991100292f82db241eec70d073b6365b /packages/exchange-backoffice-ui/src/handlers | |
| parent | 02fb71c0ff69d293911f4b0945ab964a87402d0c (diff) | |
one form left
Diffstat (limited to 'packages/exchange-backoffice-ui/src/handlers')
| -rw-r--r-- | packages/exchange-backoffice-ui/src/handlers/Group.tsx | 41 | ||||
| -rw-r--r-- | packages/exchange-backoffice-ui/src/handlers/InputChoiceStacked.tsx (renamed from packages/exchange-backoffice-ui/src/handlers/InputChoice.tsx) | 0 | ||||
| -rw-r--r-- | packages/exchange-backoffice-ui/src/handlers/InputSelectMultiple.tsx | 8 | ||||
| -rw-r--r-- | packages/exchange-backoffice-ui/src/handlers/InputSelectOne.tsx | 136 | ||||
| -rw-r--r-- | packages/exchange-backoffice-ui/src/handlers/Separator.tsx | 26 | ||||
| -rw-r--r-- | packages/exchange-backoffice-ui/src/handlers/forms.ts | 17 |
6 files changed, 220 insertions, 8 deletions
diff --git a/packages/exchange-backoffice-ui/src/handlers/Group.tsx b/packages/exchange-backoffice-ui/src/handlers/Group.tsx new file mode 100644 index 000000000..04af0647b --- /dev/null +++ b/packages/exchange-backoffice-ui/src/handlers/Group.tsx @@ -0,0 +1,41 @@ +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-8 sm:grid-cols-6"> + <RenderAllFieldsByUiConfig fields={fields} /> + </div> + <div class="pt-4"> + {after && ( + <LabelWithTooltipMaybeRequired label={after} tooltip={tooltipAfter} /> + )} + </div> + </div> + ); +} diff --git a/packages/exchange-backoffice-ui/src/handlers/InputChoice.tsx b/packages/exchange-backoffice-ui/src/handlers/InputChoiceStacked.tsx index b19a0d82e..b19a0d82e 100644 --- a/packages/exchange-backoffice-ui/src/handlers/InputChoice.tsx +++ b/packages/exchange-backoffice-ui/src/handlers/InputChoiceStacked.tsx diff --git a/packages/exchange-backoffice-ui/src/handlers/InputSelectMultiple.tsx b/packages/exchange-backoffice-ui/src/handlers/InputSelectMultiple.tsx index 05733fe19..dd85453e5 100644 --- a/packages/exchange-backoffice-ui/src/handlers/InputSelectMultiple.tsx +++ b/packages/exchange-backoffice-ui/src/handlers/InputSelectMultiple.tsx @@ -1,5 +1,5 @@ import { Fragment, VNode, h } from "preact"; -import { Choice } from "./InputChoice.js"; +import { Choice } from "./InputChoiceStacked.js"; import { LabelWithTooltipMaybeRequired, UIFormProps } from "./InputLine.js"; import { useField } from "./useField.js"; import { useState } from "preact/hooks"; @@ -8,9 +8,10 @@ export function InputSelectMultiple( props: { choices: Choice[]; unique?: boolean; + max?: number; } & UIFormProps<Array<string>>, ): VNode { - const { name, label, choices, placeholder, tooltip, required, unique } = + const { name, label, choices, placeholder, tooltip, required, unique, max } = props; const { value, onChange } = useField<{ [s: string]: Array<string> }>(name); @@ -113,6 +114,9 @@ export function InputSelectMultiple( if (unique && list.indexOf(v.value) !== -1) { return; } + if (max !== undefined && list.length >= max) { + return; + } const newValue = [...list]; newValue.splice(0, 0, v.value); onChange(newValue); diff --git a/packages/exchange-backoffice-ui/src/handlers/InputSelectOne.tsx b/packages/exchange-backoffice-ui/src/handlers/InputSelectOne.tsx new file mode 100644 index 000000000..a4ed1ba1c --- /dev/null +++ b/packages/exchange-backoffice-ui/src/handlers/InputSelectOne.tsx @@ -0,0 +1,136 @@ +import { Fragment, VNode, h } from "preact"; +import { Choice } from "./InputChoiceStacked.js"; +import { LabelWithTooltipMaybeRequired, UIFormProps } from "./InputLine.js"; +import { useField } from "./useField.js"; +import { useState } from "preact/hooks"; + +export function InputSelectOne( + props: { + choices: Choice[]; + } & UIFormProps<Array<string>>, +): VNode { + const { name, label, choices, placeholder, tooltip, required } = props; + const { value, onChange } = useField<{ [s: string]: string | undefined }>( + name, + ); + + const [filter, setFilter] = useState<string | undefined>(undefined); + const regex = new RegExp(`.*${filter}.*`, "i"); + const choiceMap = choices.reduce((prev, curr) => { + return { ...prev, [curr.value]: 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]} + <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); + }} + + // 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/exchange-backoffice-ui/src/handlers/Separator.tsx b/packages/exchange-backoffice-ui/src/handlers/Separator.tsx new file mode 100644 index 000000000..5fa25c3ca --- /dev/null +++ b/packages/exchange-backoffice-ui/src/handlers/Separator.tsx @@ -0,0 +1,26 @@ +import { VNode, h } from "preact"; +import { + InputLine, + LabelWithTooltipMaybeRequired, + UIFormProps, +} from "./InputLine.js"; +import { TranslatedString } from "@gnu-taler/taler-util"; + +interface Props { + label: TranslatedString; + tooltip?: TranslatedString; + help?: TranslatedString; +} + +export function Separator({ label, tooltip, help }: Props): VNode { + return ( + <div class="sm:col-span-6"> + <LabelWithTooltipMaybeRequired label={label} tooltip={tooltip} /> + {help && ( + <p class="mt-2 text-sm text-gray-500" id="email-description"> + {help} + </p> + )} + </div> + ); +} diff --git a/packages/exchange-backoffice-ui/src/handlers/forms.ts b/packages/exchange-backoffice-ui/src/handlers/forms.ts index b5d1a2b20..418754919 100644 --- a/packages/exchange-backoffice-ui/src/handlers/forms.ts +++ b/packages/exchange-backoffice-ui/src/handlers/forms.ts @@ -3,11 +3,14 @@ import { InputText } from "./InputText.js"; import { InputDate } from "./InputDate.js"; import { InputInteger } from "./InputInteger.js"; import { h as create, Fragment, VNode } from "preact"; -import { InputChoiceStacked } from "./InputChoice.js"; +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 { Separator } from "./Separator.js"; +import { Group } from "./Group.js"; +import { InputSelectOne } from "./InputSelectOne.js"; export type DoubleColumnForm = DoubleColumnFormSection[]; @@ -21,9 +24,11 @@ type DoubleColumnFormSection = { * Constrain the type with the ui props */ type FieldType = { - separator: {}; + group: Parameters<typeof Group>[0]; + separator: Parameters<typeof Separator>[0]; array: Parameters<typeof InputArray>[0]; file: Parameters<typeof InputFile>[0]; + selectOne: Parameters<typeof InputSelectOne>[0]; selectMultiple: Parameters<typeof InputSelectMultiple>[0]; text: Parameters<typeof InputText>[0]; textArea: Parameters<typeof InputTextArea>[0]; @@ -36,9 +41,11 @@ type FieldType = { * List all the form fields so typescript can type-check the form instance */ export type UIFormField = + | { type: "group"; props: FieldType["group"] } | { type: "separator"; props: FieldType["separator"] } | { type: "array"; props: FieldType["array"] } | { type: "file"; props: FieldType["file"] } + | { type: "selectOne"; props: FieldType["selectOne"] } | { type: "selectMultiple"; props: FieldType["selectMultiple"] } | { type: "text"; props: FieldType["text"] } | { type: "textArea"; props: FieldType["textArea"] } @@ -54,14 +61,11 @@ type UIFormFieldMap = { [key in keyof FieldType]: FieldComponentFunction<key>; }; -function Separator(): VNode { - return create("div", {}); -} - /** * Maps input type with component implementation */ const UIFormConfiguration: UIFormFieldMap = { + group: Group, separator: Separator, array: InputArray, text: InputText, @@ -70,6 +74,7 @@ const UIFormConfiguration: UIFormFieldMap = { date: InputDate, choiceStacked: InputChoiceStacked, integer: InputInteger, + selectOne: InputSelectOne, selectMultiple: InputSelectMultiple, }; |
