[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-typescript-core] 01/03: input iso time
From: |
Admin |
Subject: |
[taler-typescript-core] 01/03: input iso time |
Date: |
Wed, 05 Feb 2025 18:13:22 +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 b5217432044d03224f905d5894058297fd354d8b
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Wed Feb 5 12:10:09 2025 -0300
input iso time
---
packages/web-util/src/forms/Calendar.tsx | 49 ++++++++++--
packages/web-util/src/forms/field-types.ts | 8 ++
.../src/forms/fields/InputIsoTime.stories.tsx | 63 +++++++++++++++
.../web-util/src/forms/fields/InputIsoTime.tsx | 91 ++++++++++++++++++++++
packages/web-util/src/forms/fields/InputLine.tsx | 10 ++-
packages/web-util/src/forms/forms-types.ts | 17 ++++
packages/web-util/src/forms/forms-utils.ts | 17 +++-
packages/web-util/src/forms/index.stories.ts | 1 +
8 files changed, 242 insertions(+), 14 deletions(-)
diff --git a/packages/web-util/src/forms/Calendar.tsx
b/packages/web-util/src/forms/Calendar.tsx
index b08129f56..4bdfab802 100644
--- a/packages/web-util/src/forms/Calendar.tsx
+++ b/packages/web-util/src/forms/Calendar.tsx
@@ -10,13 +10,21 @@ import {
getYear,
isSameDay,
isSameMonth,
+ isValid,
+ setYear,
startOfDay,
startOfMonth,
startOfWeek,
+ subYears,
} from "date-fns";
import { VNode, h } from "preact";
-import { useState } from "preact/hooks";
+import { useEffect, useRef, useState } from "preact/hooks";
import { useTranslationContext } from "../index.browser.js";
+import { composeRef, saveRef } from "../components/utils.js";
+
+const THIS_MONTH = getMonth(new Date());
+const THIS_YEAR = getYear(new Date());
+const TODAY = startOfDay(new Date());
export function Calendar({
value,
@@ -25,11 +33,21 @@ export function Calendar({
value: AbsoluteTime | undefined;
onChange: (v: AbsoluteTime) => void;
}): VNode {
- const today = startOfDay(new Date());
- const selected = !value ? today : new Date(AbsoluteTime.toStampMs(value));
+ const selectedMaybeInvalid = !value
+ ? TODAY
+ : new Date(AbsoluteTime.toStampMs(value));
+ const selected = isValid(selectedMaybeInvalid) ? selectedMaybeInvalid :
TODAY;
const [showingDate, setShowingDate] = useState(selected);
- const month = getMonth(showingDate);
- const year = getYear(showingDate);
+ const m = getMonth(showingDate);
+ const y = getYear(showingDate);
+ const month = Number.isNaN(m) ? THIS_MONTH : m;
+ const year = Number.isNaN(y) ? THIS_YEAR : y;
+ const input = useRef<HTMLInputElement>();
+ useEffect(() => {
+ if (!input.current) return;
+ if (input.current === document.activeElement) return;
+ input.current.value = !year ? "" : String(year);
+ }, [year]);
const start = startOfWeek(startOfMonth(showingDate));
const end = endOfWeek(endOfMonth(showingDate));
@@ -73,7 +91,24 @@ export function Calendar({
/>
</svg>
</button>
- <div class="flex-auto text-sm font-semibold">{year}</div>
+ <div class="flex-auto text-sm font-semibold">
+ <input
+ type="text"
+ ref={composeRef(saveRef(input))}
+ onChange={(e) => {
+ const text = e.currentTarget.value;
+ const num = Number.parseInt(text, 10);
+
+ if (Number.isSafeInteger(num) && num > 0) {
+ const nextYear = setYear(showingDate, num);
+
+ if (isValid(nextYear)) {
+ setShowingDate(nextYear);
+ }
+ }
+ }}
+ />
+ </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"
@@ -157,7 +192,7 @@ export function Calendar({
type="button"
key={idx}
data-month={isSameMonth(current, showingDate)}
- data-today={isSameDay(current, today)}
+ data-today={isSameDay(current, TODAY)}
data-selected={isSameDay(current, selected)}
onClick={() => {
onChange(AbsoluteTime.fromStampMs(current.getTime()));
diff --git a/packages/web-util/src/forms/field-types.ts
b/packages/web-util/src/forms/field-types.ts
index 8b0721432..eb806d6bf 100644
--- a/packages/web-util/src/forms/field-types.ts
+++ b/packages/web-util/src/forms/field-types.ts
@@ -19,6 +19,7 @@ import { Group } from "./Group.js";
import { HtmlIframe } from "./HtmlIframe.js";
import { InputDurationText } from "./fields/InputDurationText.js";
import { ExternalLink } from "./ExternalLink.js";
+import { InputIsoTime } from "./fields/InputIsoTime.js";
/**
* Constrain the type with the ui props
*/
@@ -37,6 +38,7 @@ type FieldType<T extends object = any, K extends keyof T =
any> = {
choiceStacked: Parameters<typeof InputChoiceStacked<T, K>>[0];
choiceHorizontal: Parameters<typeof InputChoiceHorizontal<T, K>>[0];
absoluteTimeText: Parameters<typeof InputAbsoluteTime<T, K>>[0];
+ isoTimeText: Parameters<typeof InputIsoTime<T, K>>[0];
integer: Parameters<typeof InputInteger<T, K>>[0];
secret: Parameters<typeof InputSecret<T, K>>[0];
toggle: Parameters<typeof InputToggle<T, K>>[0];
@@ -79,6 +81,10 @@ export type UIFormField =
type: "absoluteTimeText";
properties: FieldType["absoluteTimeText"];
}
+ | {
+ type: "isoTimeText";
+ properties: FieldType["isoTimeText"];
+ }
| {
type: "duration";
properties: FieldType["duration"];
@@ -114,6 +120,8 @@ export const UIFormConfiguration: UIFormFieldMap = {
//@ts-ignore
absoluteTimeText: InputAbsoluteTime,
//@ts-ignore
+ isoTimeText: InputIsoTime,
+ //@ts-ignore
choiceStacked: InputChoiceStacked,
//@ts-ignore
choiceHorizontal: InputChoiceHorizontal,
diff --git a/packages/web-util/src/forms/fields/InputIsoTime.stories.tsx
b/packages/web-util/src/forms/fields/InputIsoTime.stories.tsx
new file mode 100644
index 000000000..067cba18c
--- /dev/null
+++ b/packages/web-util/src/forms/fields/InputIsoTime.stories.tsx
@@ -0,0 +1,63 @@
+/*
+ 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 "../../tests/hook.js";
+import { FormDesign, UIHandlerId } from "../forms-types.js";
+import { DefaultForm as TestedComponent } from "../forms-ui.js";
+export default {
+ title: "Input Iso Time",
+};
+
+export namespace Simplest {
+ export interface Form {
+ comment: string;
+ }
+}
+
+type TargetObject = {
+ today: string;
+};
+const initial: TargetObject = {
+ today: "1/12/3333344",
+};
+
+const design: FormDesign = {
+ type: "double-column",
+ sections: [
+ {
+ title: "this is a simple form" as TranslatedString,
+ fields: [
+ {
+ type: "isoTimeText",
+ label: "label of the field" as TranslatedString,
+ id: "today" as UIHandlerId,
+ pattern: "dd/MM/yyyy",
+ },
+ ],
+ },
+ ],
+};
+
+export const SimpleComment = tests.createExample(TestedComponent, {
+ initial,
+ design,
+});
diff --git a/packages/web-util/src/forms/fields/InputIsoTime.tsx
b/packages/web-util/src/forms/fields/InputIsoTime.tsx
new file mode 100644
index 000000000..bd78b0bd4
--- /dev/null
+++ b/packages/web-util/src/forms/fields/InputIsoTime.tsx
@@ -0,0 +1,91 @@
+import { AbsoluteTime } from "@gnu-taler/taler-util";
+import { format, parse } from "date-fns";
+import { Fragment, VNode, h } from "preact";
+import { useState } from "preact/hooks";
+import { Calendar } from "../Calendar.js";
+import { Dialog } from "../Dialog.js";
+import { UIFormProps } from "../FormProvider.js";
+import { noHandlerPropsAndNoContextForField } from "./InputArray.js";
+import { InputLine } from "./InputLine.js";
+
+export function InputIsoTime<T extends object, K extends keyof T>(
+ properties: { pattern?: string } & UIFormProps<T, K>,
+): VNode {
+ const pattern = properties.pattern ?? "dd/MM/yyyy";
+ const [open, setOpen] = useState(false);
+
+ const { value, onChange } =
+ properties.handler ?? noHandlerPropsAndNoContextForField(properties.name);
+
+ const time = parse(value, pattern, Date.now()).getTime();
+ // const strTime = format(time, pattern);
+ return (
+ <Fragment>
+ <InputLine<T, K>
+ type="text"
+ {...properties}
+ 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): string | undefined => {
+ if (!v) return undefined;
+ try {
+ const t_ms = parse(v, pattern, Date.now()).getTime();
+ return format(t_ms, pattern);
+ } catch (e) {
+ return undefined;
+ }
+ },
+ }}
+ />
+ {open && (
+ <Dialog onClose={() => setOpen(false)}>
+ <Calendar
+ value={AbsoluteTime.fromMilliseconds(time)}
+ onChange={(v) => {
+ onChange(
+ v.t_ms === "never" ? undefined : format(v.t_ms, pattern),
+ );
+ setOpen(false);
+ }}
+ />
+ </Dialog>
+ )}
+ {/* {open && (
+ <Dialog onClose={() => setOpen(false)}>
+ <TimePicker
+ value={AbsoluteTime.fromMilliseconds(time)}
+ onChange={(v) => {
+ onChange(v as any);
+ }}
+ onConfirm={() => {
+ setOpen(false);
+ }}
+ />
+ </Dialog>
+ )} */}
+ </Fragment>
+ );
+}
diff --git a/packages/web-util/src/forms/fields/InputLine.tsx
b/packages/web-util/src/forms/fields/InputLine.tsx
index 8ead18dbe..b915c4bea 100644
--- a/packages/web-util/src/forms/fields/InputLine.tsx
+++ b/packages/web-util/src/forms/fields/InputLine.tsx
@@ -72,14 +72,16 @@ export function LabelWithTooltipMaybeRequired({
export function RenderAddon({
disabled,
addon,
+ reverse,
}: {
disabled?: boolean;
+ reverse?: boolean;
addon: Addon;
}): VNode {
switch (addon.type) {
case "text": {
return (
- <span class="inline-flex items-center rounded-l-md border border-r-0
border-gray-300 px-3 text-gray-500 sm:text-sm">
+ <span class="inline-flex items-center data-[right=true]:rounded-r-md
data-[left=true]:rounded-l-md border border-r-0 border-gray-300 px-3
text-gray-500 sm:text-sm">
{addon.text}
</span>
);
@@ -97,7 +99,9 @@ export function RenderAddon({
type="button"
disabled={disabled}
onClick={addon.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"
+ data-left={!reverse}
+ data-right={reverse}
+ class="relative -ml-px inline-flex items-center gap-x-1.5
data-[right=true]:rounded-r-md data-[left=true]: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"
>
{addon.children}
</button>
@@ -133,7 +137,7 @@ export function InputWrapper<T extends object, K extends
keyof T>({
{children}
- {after && <RenderAddon disabled={disabled} addon={after} />}
+ {after && <RenderAddon disabled={disabled} addon={after} reverse />}
</div>
{error && (
<p class="mt-2 text-sm text-red-600" id="email-error">
diff --git a/packages/web-util/src/forms/forms-types.ts
b/packages/web-util/src/forms/forms-types.ts
index 0a7569be9..48151f413 100644
--- a/packages/web-util/src/forms/forms-types.ts
+++ b/packages/web-util/src/forms/forms-types.ts
@@ -51,6 +51,7 @@ export type UIFormElementConfig =
| UIFormElementExternalLink
| UIFormElementHtmlIframe
| UIFormFieldAbsoluteTime
+ | UIFormFieldIsoTime
| UIFormFieldAmount
| UIFormFieldArray
| UIFormFieldChoiseHorizontal
@@ -78,6 +79,13 @@ type UIFormFieldAbsoluteTime = {
pattern: string;
} & UIFormFieldBaseConfig;
+type UIFormFieldIsoTime = {
+ type: "isoTimeText";
+ max?: TalerProtocolTimestamp;
+ min?: TalerProtocolTimestamp;
+ pattern: string;
+} & UIFormFieldBaseConfig;
+
type UIFormFieldAmount = {
type: "amount";
max?: Integer;
@@ -258,6 +266,14 @@ const codecForUiFormFieldAbsoluteTime = ():
Codec<UIFormFieldAbsoluteTime> =>
.property("min", codecOptional(codecForTimestamp))
.build("UIFormFieldAbsoluteTime");
+const codecForUiFormFieldIsoTime = (): Codec<UIFormFieldIsoTime> =>
+ codecForUIFormFieldBaseConfigTemplate<UIFormFieldIsoTime>()
+ .property("type", codecForConstString("isoTimeText"))
+ .property("pattern", codecForString())
+ .property("max", codecOptional(codecForTimestamp))
+ .property("min", codecOptional(codecForTimestamp))
+ .build("UIFormFieldIsoTime");
+
const codecForUiFormFieldAmount = (): Codec<UIFormFieldAmount> =>
codecForUIFormFieldBaseConfigTemplate<UIFormFieldAmount>()
.property("type", codecForConstString("amount"))
@@ -411,6 +427,7 @@ const codecForUiFormField = (): Codec<UIFormElementConfig>
=>
.alternative("download-link", codecForUIFormElementLink())
.alternative("external-link", codecForUIFormElementExternalLink())
.alternative("absoluteTimeText", codecForUiFormFieldAbsoluteTime())
+ .alternative("isoTimeText", codecForUiFormFieldIsoTime())
.alternative("amount", codecForUiFormFieldAmount())
.alternative("caption", codecForUiFormFieldCaption())
.alternative("htmlIframe", codecForUiFormFieldHtmlIFrame())
diff --git a/packages/web-util/src/forms/forms-utils.ts
b/packages/web-util/src/forms/forms-utils.ts
index e255f0c1b..4ac1c4aae 100644
--- a/packages/web-util/src/forms/forms-utils.ts
+++ b/packages/web-util/src/forms/forms-utils.ts
@@ -120,6 +120,19 @@ export function convertFormConfigToUiField(
},
} as UIFormField;
}
+ case "isoTimeText": {
+ return {
+ type: "isoTimeText",
+ properties: {
+ ...converBaseFieldsProps(i18n_, config),
+ ...converInputFieldsProps(
+ form,
+ config,
+ getConverterByFieldType(config.type, config),
+ ),
+ },
+ } as UIFormField;
+ }
case "amount": {
return {
type: "amount",
@@ -322,10 +335,6 @@ function getConverterByFieldType(
// @ts-expect-error check this
return amountConverter(config);
}
- if (fieldType === "TalerExchangeApi.AmlState") {
- // @ts-expect-error check this
- return amlStateConverter;
- }
return nullConverter as StringConverter<unknown>;
}
diff --git a/packages/web-util/src/forms/index.stories.ts
b/packages/web-util/src/forms/index.stories.ts
index 653bc60de..743a54ffd 100644
--- a/packages/web-util/src/forms/index.stories.ts
+++ b/packages/web-util/src/forms/index.stories.ts
@@ -14,3 +14,4 @@ export * as a14 from "./fields/InputSecret.stories.js";
export * as a15 from "./fields/InputDuration.stories.js";
export * as a16 from "./fields/InputDurationText.stories.js";
export * as a17 from "./gana/GLS_Onboarding.stories.js";
+export * as a18 from "./fields/InputIsoTime.stories.js";
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.