gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]