[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-typescript-core] 02/08: input duration
From: |
gnunet |
Subject: |
[taler-typescript-core] 02/08: input duration |
Date: |
Wed, 15 Jan 2025 22:32:48 +0100 |
This is an automated email from the git hooks/post-receive script.
sebasjm pushed a commit to branch master
in repository taler-typescript-core.
commit 411dcd6b32501c1c1c91727b428ae0e46c76460b
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Mon Jan 13 18:07:40 2025 -0300
input duration
---
packages/web-util/src/forms/field-types.ts | 9 +-
...ecret.stories.tsx => InputDuration.stories.tsx} | 21 ++-
.../web-util/src/forms/fields/InputDuration.tsx | 183 +++++++++++++++++++++
packages/web-util/src/forms/fields/InputLine.tsx | 2 +-
.../src/forms/fields/InputSecret.stories.tsx | 2 +-
packages/web-util/src/forms/forms-types.ts | 15 +-
packages/web-util/src/forms/forms-utils.ts | 13 ++
packages/web-util/src/forms/index.stories.ts | 1 +
packages/web-util/src/hooks/useForm.ts | 2 +-
9 files changed, 235 insertions(+), 13 deletions(-)
diff --git a/packages/web-util/src/forms/field-types.ts
b/packages/web-util/src/forms/field-types.ts
index 23fcf5fe7..34696b430 100644
--- a/packages/web-util/src/forms/field-types.ts
+++ b/packages/web-util/src/forms/field-types.ts
@@ -9,9 +9,10 @@ import { InputChoiceStacked } from
"./fields/InputChoiceStacked.js";
import { InputFile } from "./fields/InputFile.js";
import { InputInteger } from "./fields/InputInteger.js";
import { InputSelectMultiple } from "./fields/InputSelectMultiple.js";
+import { InputDuration } from "./fields/InputDuration.js";
+import { InputSecret } from "./fields/InputSecret.js";
import { InputSelectOne } from "./fields/InputSelectOne.js";
import { InputText } from "./fields/InputText.js";
-import { InputSecret } from "./fields/InputSecret.js";
import { InputTextArea } from "./fields/InputTextArea.js";
import { InputToggle } from "./fields/InputToggle.js";
import { Group } from "./Group.js";
@@ -37,6 +38,7 @@ type FieldType<T extends object = any, K extends keyof T =
any> = {
secret: Parameters<typeof InputSecret<T, K>>[0];
toggle: Parameters<typeof InputToggle<T, K>>[0];
amount: Parameters<typeof InputAmount<T, K>>[0];
+ duration: Parameters<typeof InputDuration<T, K>>[0];
};
/**
@@ -71,6 +73,10 @@ export type UIFormField =
| {
type: "absoluteTimeText";
properties: FieldType["absoluteTimeText"];
+ }
+ | {
+ type: "duration";
+ properties: FieldType["duration"];
};
export type FieldComponentFunction<key extends keyof FieldType> = (
@@ -111,4 +117,5 @@ export const UIFormConfiguration: UIFormFieldMap = {
toggle: InputToggle,
//@ts-ignore
amount: InputAmount,
+ duration: InputDuration,
};
diff --git a/packages/web-util/src/forms/fields/InputSecret.stories.tsx
b/packages/web-util/src/forms/fields/InputDuration.stories.tsx
similarity index 80%
copy from packages/web-util/src/forms/fields/InputSecret.stories.tsx
copy to packages/web-util/src/forms/fields/InputDuration.stories.tsx
index e143c3af8..8c0983287 100644
--- a/packages/web-util/src/forms/fields/InputSecret.stories.tsx
+++ b/packages/web-util/src/forms/fields/InputDuration.stories.tsx
@@ -19,20 +19,25 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { TranslatedString } from "@gnu-taler/taler-util";
+import { Duration, TranslatedString } from "@gnu-taler/taler-util";
import * as tests from "../../tests/hook.js";
-import { DefaultForm as TestedComponent } from "../forms-ui.js";
import { FormDesign, UIHandlerId } from "../forms-types.js";
+import { DefaultForm as TestedComponent } from "../forms-ui.js";
export default {
- title: "Input secret",
+ title: "Input duration",
};
type TargetObject = {
- pwd: string;
+ time: Duration;
};
const initial: TargetObject = {
- pwd: 5,
+ time: Duration.fromSpec({
+ days: 1,
+ hours: 2,
+ minutes: 3,
+ seconds: 4,
+ }),
};
const design: FormDesign = {
@@ -42,9 +47,9 @@ const design: FormDesign = {
title: "this is a simple form" as TranslatedString,
fields: [
{
- type: "secret",
- label: "Password" as TranslatedString,
- id: "pwd" as UIHandlerId,
+ type: "duration",
+ label: "How long?" as TranslatedString,
+ id: "time" as UIHandlerId,
},
],
},
diff --git a/packages/web-util/src/forms/fields/InputDuration.tsx
b/packages/web-util/src/forms/fields/InputDuration.tsx
new file mode 100644
index 000000000..94cd6d6aa
--- /dev/null
+++ b/packages/web-util/src/forms/fields/InputDuration.tsx
@@ -0,0 +1,183 @@
+import { Fragment, VNode, h } from "preact";
+import { useTranslationContext } from "../../index.browser.js";
+import { UIFormProps } from "../FormProvider.js";
+import { noHandlerPropsAndNoContextForField } from "./InputArray.js";
+import { InputWrapper } from "./InputLine.js";
+import { Duration } from "@gnu-taler/taler-util";
+import { useEffect, useState } from "preact/hooks";
+
+export function InputDuration<T extends object, K extends keyof T>(
+ props: UIFormProps<T, K>,
+): VNode {
+ const { name, placeholder, before, after, converter, disabled } = props;
+ const { i18n } = useTranslationContext();
+ const { value, onChange, state, error } =
+ props.handler ?? noHandlerPropsAndNoContextForField(props.name);
+
+ const sd = Duration.toSpec(value as Duration);
+ const [days, setDays] = useState(sd?.days ?? 0);
+ const [hours, setHours] = useState(sd?.hours ?? 0);
+ const [minutes, setMinutes] = useState(sd?.minutes ?? 0);
+ const [seconds, setSeconds] = useState(sd?.seconds ?? 0);
+
+ useEffect(() => {
+ onChange(
+ Duration.fromSpec({
+ days,
+ hours,
+ minutes,
+ seconds,
+ }),
+ );
+ }, [days, hours, minutes, seconds]);
+ const fromString: (s: string) => any =
+ converter?.fromStringUI ?? defaultFromString;
+ const toString: (s: any) => string = converter?.toStringUI ??
defaultToString;
+
+ 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 = value !== undefined && 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";
+ }
+ return (
+ <InputWrapper<T, K>
+ {...props}
+ help={props.help ?? state.help}
+ disabled={disabled ?? false}
+ error={showError ? error : undefined}
+ >
+ <div class="flex flex-col gap-1">
+ <div class="flex">
+ <span class="ml-2 inline-flex items-center rounded-l-md border
border-r-0 border-gray-300 px-3 text-gray-500 sm:text-sm">
+ <i18n.Translate>days</i18n.Translate>
+ </span>
+ <input
+ name={String(name)}
+ type="number"
+ onChange={(e) => {
+ setDays(fromString(e.currentTarget.value));
+ }}
+ placeholder={placeholder ? placeholder : undefined}
+ value={toString(sd?.days) ?? ""}
+ // onBlur={() => {
+ // onChange(fromString(value as any));
+ // }}
+ // defaultValue={toString(value)}
+ disabled={disabled ?? false}
+ aria-invalid={showError}
+ // aria-describedby="email-error"
+ class={clazz}
+ />
+ <span class="ml-2 inline-flex items-center rounded-l-md border
border-r-0 border-gray-300 px-3 text-gray-500 sm:text-sm">
+ <i18n.Translate>hours</i18n.Translate>
+ </span>
+ <input
+ name={String(name)}
+ type="number"
+ onChange={(e) => {
+ setHours(fromString(e.currentTarget.value));
+ }}
+ placeholder={placeholder ? placeholder : undefined}
+ value={toString(sd?.hours) ?? ""}
+ // onBlur={() => {
+ // onChange(fromString(value as any));
+ // }}
+ // defaultValue={toString(value)}
+ disabled={disabled ?? false}
+ aria-invalid={showError}
+ // aria-describedby="email-error"
+ class={clazz}
+ />
+ </div>
+ <div class="flex">
+ <span class="ml-2 inline-flex items-center rounded-l-md border
border-r-0 border-gray-300 px-3 text-gray-500 sm:text-sm">
+ <i18n.Translate>minutes</i18n.Translate>
+ </span>
+ <input
+ name={String(name)}
+ type="number"
+ onChange={(e) => {
+ setMinutes(fromString(e.currentTarget.value));
+ }}
+ placeholder={placeholder ? placeholder : undefined}
+ value={toString(sd?.minutes) ?? ""}
+ // onBlur={() => {
+ // onChange(fromString(value as any));
+ // }}
+ // defaultValue={toString(value)}
+ disabled={disabled ?? false}
+ aria-invalid={showError}
+ // aria-describedby="email-error"
+ class={clazz}
+ />
+ <span class="ml-2 inline-flex items-center rounded-l-md border
border-r-0 border-gray-300 px-3 text-gray-500 sm:text-sm">
+ <i18n.Translate>seconds</i18n.Translate>
+ </span>
+ <input
+ name={String(name)}
+ type="number"
+ onChange={(e) => {
+ setSeconds(fromString(e.currentTarget.value));
+ }}
+ placeholder={placeholder ? placeholder : undefined}
+ value={toString(sd?.seconds) ?? ""}
+ // onBlur={() => {
+ // onChange(fromString(value as any));
+ // }}
+ // defaultValue={toString(value)}
+ disabled={disabled ?? false}
+ aria-invalid={showError}
+ // aria-describedby="email-error"
+ class={clazz}
+ />
+ </div>
+ </div>
+ </InputWrapper>
+ );
+}
+
+function defaultToString(v: unknown) {
+ return v === undefined ? "" : typeof v !== "object" ? String(v) : "";
+}
+function defaultFromString(v: string) {
+ return v;
+}
diff --git a/packages/web-util/src/forms/fields/InputLine.tsx
b/packages/web-util/src/forms/fields/InputLine.tsx
index 982fd4670..bbbc871e0 100644
--- a/packages/web-util/src/forms/fields/InputLine.tsx
+++ b/packages/web-util/src/forms/fields/InputLine.tsx
@@ -104,7 +104,7 @@ export function RenderAddon({
}
}
-function InputWrapper<T extends object, K extends keyof T>({
+export function InputWrapper<T extends object, K extends keyof T>({
children,
label,
tooltip,
diff --git a/packages/web-util/src/forms/fields/InputSecret.stories.tsx
b/packages/web-util/src/forms/fields/InputSecret.stories.tsx
index e143c3af8..5179b6fbb 100644
--- a/packages/web-util/src/forms/fields/InputSecret.stories.tsx
+++ b/packages/web-util/src/forms/fields/InputSecret.stories.tsx
@@ -32,7 +32,7 @@ type TargetObject = {
pwd: string;
};
const initial: TargetObject = {
- pwd: 5,
+ pwd: "5",
};
const design: FormDesign = {
diff --git a/packages/web-util/src/forms/forms-types.ts
b/packages/web-util/src/forms/forms-types.ts
index d5bcced6c..0da107e34 100644
--- a/packages/web-util/src/forms/forms-types.ts
+++ b/packages/web-util/src/forms/forms-types.ts
@@ -17,7 +17,9 @@ import {
TranslatedString,
} from "@gnu-taler/taler-util";
-export type FormDesign = DoubleColumnFormDesign | SingleColumnFormDesign;
+export type FormDesign<T = unknown> =
+ | DoubleColumnFormDesign
+ | SingleColumnFormDesign;
/**
* form with composed by multiple sections
@@ -56,6 +58,7 @@ export type UIFormElementConfig =
| UIFormFieldInteger
| UIFormFieldSecret
| UIFormFieldSelectMultiple
+ | UIFormFieldDuration
| UIFormFieldSelectOne
| UIFormFieldText
| UIFormFieldTextArea
@@ -144,6 +147,10 @@ type UIFormFieldSelectMultiple = {
allowFreeForm?: boolean;
} & UIFormFieldBaseConfig;
+type UIFormFieldDuration = {
+ type: "duration";
+} & UIFormFieldBaseConfig;
+
type UIFormFieldSelectOne = {
type: "selectOne";
choices: Array<SelectUiChoice>;
@@ -320,6 +327,11 @@ const codecForUiFormFieldSecret = ():
Codec<UIFormFieldSecret> =>
.property("type", codecForConstString("secret"))
.build("UIFormFieldSecret");
+const codecForUiFormFieldDuration = (): Codec<UIFormFieldDuration> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldDuration>()
+ .property("type", codecForConstString("duration"))
+ .build("UiFormFieldDuration");
+
const codecForUiFormFieldSelectMultiple =
(): Codec<UIFormFieldSelectMultiple> =>
codecForUIFormFieldBaseConfigTemplate<UIFormFieldSelectMultiple>()
@@ -370,6 +382,7 @@ const codecForUiFormField = (): Codec<UIFormElementConfig>
=>
.alternative("integer", codecForUiFormFieldInteger())
.alternative("secret", codecForUiFormFieldSecret())
.alternative("selectMultiple", codecForUiFormFieldSelectMultiple())
+ .alternative("duration", codecForUiFormFieldDuration())
.alternative("selectOne", codecForUiFormFieldSelectOne())
.alternative("text", codecForUiFormFieldText())
.alternative("textArea", codecForUiFormFieldTextArea())
diff --git a/packages/web-util/src/forms/forms-utils.ts
b/packages/web-util/src/forms/forms-utils.ts
index b6797d8d1..dc7d7d22f 100644
--- a/packages/web-util/src/forms/forms-utils.ts
+++ b/packages/web-util/src/forms/forms-utils.ts
@@ -245,6 +245,19 @@ export function convertFormConfigToUiField(
},
} as UIFormField;
}
+ case "duration": {
+ return {
+ type: "duration",
+ properties: {
+ ...converBaseFieldsProps(i18n_, config),
+ ...converInputFieldsProps(
+ form,
+ config,
+ getConverterByFieldType(config.type, config),
+ ),
+ },
+ } as UIFormField;
+ }
case "toggle": {
return {
type: "toggle",
diff --git a/packages/web-util/src/forms/index.stories.ts
b/packages/web-util/src/forms/index.stories.ts
index 702114f65..5b62c512a 100644
--- a/packages/web-util/src/forms/index.stories.ts
+++ b/packages/web-util/src/forms/index.stories.ts
@@ -11,3 +11,4 @@ export * as a11 from "./fields/InputText.stories.js";
export * as a12 from "./fields/InputTextArea.stories.js";
export * as a13 from "./fields/InputToggle.stories.js";
export * as a14 from "./fields/InputSecret.stories.js";
+export * as a15 from "./fields/InputDuration.stories.js";
diff --git a/packages/web-util/src/hooks/useForm.ts
b/packages/web-util/src/hooks/useForm.ts
index a669f3007..54cbc06e5 100644
--- a/packages/web-util/src/hooks/useForm.ts
+++ b/packages/web-util/src/hooks/useForm.ts
@@ -110,7 +110,7 @@ function checkAllRequirements<T>(
* @returns
*/
export function useForm<T>(
- design: FormDesign,
+ design: FormDesign<T>,
initialValue: RecursivePartial<FormValues<T>>,
check?: (f: RecursivePartial<FormValues<T>>) => FormErrors<T> | undefined,
): FormState<T> {
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
- [taler-typescript-core] branch master updated (f119f4aae -> 35b6e5931), gnunet, 2025/01/15
- [taler-typescript-core] 02/08: input duration,
gnunet <=
- [taler-typescript-core] 01/08: new input type: secret, gnunet, 2025/01/15
- [taler-typescript-core] 03/08: fix some input array problems, gnunet, 2025/01/15
- [taler-typescript-core] 06/08: sync types with docs, gnunet, 2025/01/15
- [taler-typescript-core] 07/08: use gana for account properties, gnunet, 2025/01/15
- [taler-typescript-core] 05/08: update measures, gnunet, 2025/01/15
- [taler-typescript-core] 08/08: properties form, gnunet, 2025/01/15
- [taler-typescript-core] 04/08: tospec, gnunet, 2025/01/15