From d50c8187e9b05cc17f998797fb574c8f6a142162 Mon Sep 17 00:00:00 2001 From: Martin Oppitz Date: Tue, 26 Sep 2023 14:33:41 +0200 Subject: [PATCH 01/20] chore: push sample code for scenario --- .../complex-form/common/form/component.tsx | 25 +++ .../complex-form/common/form/types.ts | 13 ++ .../src/scenarios/complex-form/component.tsx | 163 ++++++++++++++++++ .../complex-form/kopfdaten/component.tsx | 50 ++++++ .../complex-form/location/component.tsx | 16 ++ .../complex-form/location/location.form.ts | 22 +++ .../complex-form/schedule/component.tsx | 16 ++ .../complex-form/schedule/schedule.form.ts | 34 ++++ .../samples/react/src/scenarios/routes.ts | 8 + packages/samples/react/src/shares/routes.ts | 16 +- 10 files changed, 356 insertions(+), 7 deletions(-) create mode 100644 packages/samples/react/src/scenarios/complex-form/common/form/component.tsx create mode 100644 packages/samples/react/src/scenarios/complex-form/common/form/types.ts create mode 100644 packages/samples/react/src/scenarios/complex-form/component.tsx create mode 100644 packages/samples/react/src/scenarios/complex-form/kopfdaten/component.tsx create mode 100644 packages/samples/react/src/scenarios/complex-form/location/component.tsx create mode 100644 packages/samples/react/src/scenarios/complex-form/location/location.form.ts create mode 100644 packages/samples/react/src/scenarios/complex-form/schedule/component.tsx create mode 100644 packages/samples/react/src/scenarios/complex-form/schedule/schedule.form.ts create mode 100644 packages/samples/react/src/scenarios/routes.ts diff --git a/packages/samples/react/src/scenarios/complex-form/common/form/component.tsx b/packages/samples/react/src/scenarios/complex-form/common/form/component.tsx new file mode 100644 index 0000000000..683b0fa991 --- /dev/null +++ b/packages/samples/react/src/scenarios/complex-form/common/form/component.tsx @@ -0,0 +1,25 @@ +import React, { FC, ReactNode } from 'react'; + +import { KolForm, KolLinkGroup } from '@public-ui/react'; + +type Props = { + submitted: boolean; + children: ReactNode; + onSubmit: (event: Event) => void; +}; + +export const FromComponent: FC = (props) => ( + <> + {props.submitted === true && ( + + )} + + {props.children} + + +); diff --git a/packages/samples/react/src/scenarios/complex-form/common/form/types.ts b/packages/samples/react/src/scenarios/complex-form/common/form/types.ts new file mode 100644 index 0000000000..61ae356df1 --- /dev/null +++ b/packages/samples/react/src/scenarios/complex-form/common/form/types.ts @@ -0,0 +1,13 @@ +export type Fehler = { + _label: string; + _selector: string; +}; + +export type FormProps = { + onSubmitted: (event: Event) => void; +}; + +export type FormState = { + loader: boolean; + touched: boolean; +}; diff --git a/packages/samples/react/src/scenarios/complex-form/component.tsx b/packages/samples/react/src/scenarios/complex-form/component.tsx new file mode 100644 index 0000000000..df98b4858a --- /dev/null +++ b/packages/samples/react/src/scenarios/complex-form/component.tsx @@ -0,0 +1,163 @@ +import React, { FC } from 'react'; + +import { KolHeading, KolProgress, KolTable, KolTabs } from '@public-ui/react'; + +import { TerminKopfdatenComponent } from './kopfdaten/component'; +import { TerminLocationComponent } from './location/component'; +import { TerminScheduleComponent } from './schedule/component'; + +type Zeiten = { + stadtteil: string; + zeiten: string; + montag: string; + dienstag: string; + mittwoch: string; + donnerstag: string; + freitag: string; +}; + +export const TerminComponent: FC = () => ( + <> +
+ Terminreservierung + Termine für Einwohnermelde- (incl. Pass- und Ausweisangelegenheiten) und Kraftfahrzeugangelegenheiten +
+

Derzeit kann generell nur mit vorheriger Terminvereinbarung bei den Bürgerdiensten vorgesprochen werden.

+

+ Die Termine für Einwohnermelde- und Kraftfahrzeugangelegenheiten werden täglich ab 07:00 Uhr für den gleichen Tag, für den gleichen Tag 7 Tage und für + den gleichen Tag 14 Tage später freigegeben. So können Sie jeden Tag spontan Termine für den gleichen Tag und planbar Termine für eine Woche oder zwei + Wochen später erhalten. +

+

+ Sofern online keine Termine innerhalb der nächsten 14 Tage mehr verfügbar sind, führt auch eine darüber hinaus gehende telefonische Kontaktaufnahme + leider zu keinem anderen Ergebnis. In diesem Fall versuchen Sie es bitte am nächsten Morgen erneut. +

+

+ Bitte achten Sie darauf, den richtigen Kalender für Ihr Anliegen auszuwählen. Hinweis: Bitte geben Sie bei Ihrer Terminvereinbarung zur lückenlosen + Kontaktverfolgung immer Ihre korrekte Telefonnummer und E-Mail Adresse an. Wir behalten uns vor, gebuchte Termine mit falschen Angaben zu löschen. +

+
+
+
+ 14:00 - 16:00', + dienstag: '08:00 - 12:00
14:00 - 15:00', + mittwoch: '08:00 - 12:00
14:00 - 15:00', + donnerstag: '08:00 - 12:00
14:00 - 18:00', + freitag: '08:00 - 12:00', + }, + { + stadtteil: 'Dorstfeld', + montag: '09:00 - 12:00
14:00 - 16:00', + dienstag: '09:00 - 12:00
14:00 - 15:00', + mittwoch: '09:00 - 12:00
14:00 - 15:00', + donnerstag: '09:00 - 12:00
14:00 - 18:00', + freitag: '09:00 - 12:00', + }, + { + stadtteil: 'Aplerbeck', + montag: '08:00 - 12:00
14:00 - 16:00', + dienstag: '08:00 - 12:00
14:00 - 15:00', + mittwoch: '08:00 - 12:00
14:00 - 15:00', + donnerstag: '08:00 - 12:00
14:00 - 18:00', + freitag: '08:00 - 12:00', + }, + { + stadtteil: 'Innenstadt Ost', + montag: '07:00 - 12:00
14:00 - 16:00', + dienstag: '07:00 - 12:00
14:00 - 15:00', + mittwoch: '07:00 - 12:00
14:00 - 15:00', + donnerstag: '07:00 - 12:00
14:00 - 18:00', + freitag: '07:00 - 12:00
13:00 - 16:00', + }, + { + stadtteil: 'Innenstadt West', + montag: '07:00 - 12:00
14:00 - 16:00', + dienstag: '07:00 - 12:00
14:00 - 15:00', + mittwoch: '07:00 - 12:00
14:00 - 15:00', + donnerstag: '07:00 - 12:00
14:00 - 18:00', + freitag: '07:00 - 12:00
13:00 - 16:00', + /*render: (el, data) => { + el.innerHTML = ``; + },*/ + }, + ] as Zeiten[] + } + _headers={{ + horizontal: [ + [ + { label: '', asTd: true }, + { label: 'Tag', colSpan: 5 }, + ], + [ + { + label: 'Stadtteil', + key: 'stadtteil', + textAlign: 'left', + sort: (data: Zeiten[]) => { + return data.sort((first, second) => { + if (first.stadtteil < second.stadtteil) { + return -1; + } + if (first.stadtteil > second.stadtteil) { + return 1; + } + return 0; + }); + }, + }, + { label: 'Montag', key: 'montag', textAlign: 'center' }, + { label: 'Dienstag', key: 'dienstag', textAlign: 'center' }, + { label: 'Mittwoch', key: 'mittwoch', textAlign: 'center' }, + { label: 'Donnerstag', key: 'donnerstag', textAlign: 'center' }, + { label: 'Freitag', key: 'freitag', textAlign: 'center' }, + ], + ], + }} + _minWidth="50em" + style={{ + display: 'inline-grid', + width: '100%', + }} + >
+ +
+ Wählen Sie einen Stadtteil aus + {}} /> +
+
+ Wählen Sie einen Termin aus + {}} /> +
+
+ Geben Sie Ihre Kontaktdaten ein + {}} /> +
+
+
+
+
+ Fortschritt + +
+ +); diff --git a/packages/samples/react/src/scenarios/complex-form/kopfdaten/component.tsx b/packages/samples/react/src/scenarios/complex-form/kopfdaten/component.tsx new file mode 100644 index 0000000000..d67468cc9e --- /dev/null +++ b/packages/samples/react/src/scenarios/complex-form/kopfdaten/component.tsx @@ -0,0 +1,50 @@ +import React, { FC } from 'react'; + +import { KolAbbr, KolButton, KolInputEmail, KolInputRadio, KolInputText, KolSpin } from '@public-ui/react'; +import { FormProps } from '../common/form/types'; +import { FromComponent } from '../common/form/component'; + +export const TerminKopfdatenComponent: FC = (props) => ( + {}}> +
+ + + + +
+ + + PLZ + + + +
+ + +
+ + +
+
+
+); diff --git a/packages/samples/react/src/scenarios/complex-form/location/component.tsx b/packages/samples/react/src/scenarios/complex-form/location/component.tsx new file mode 100644 index 0000000000..4436638727 --- /dev/null +++ b/packages/samples/react/src/scenarios/complex-form/location/component.tsx @@ -0,0 +1,16 @@ +import React, { FC } from 'react'; + +import { KolButton, KolSelect, KolSpin } from '@public-ui/react'; +import { LOCATION_OPTIONS } from './location.form'; +import { FormProps } from '../common/form/types'; +import { FromComponent } from '../common/form/component'; + +export const TerminLocationComponent: FC = () => ( + {}}> +
+ +
+ + +
+); diff --git a/packages/samples/react/src/scenarios/complex-form/location/location.form.ts b/packages/samples/react/src/scenarios/complex-form/location/location.form.ts new file mode 100644 index 0000000000..c4fdd89bd8 --- /dev/null +++ b/packages/samples/react/src/scenarios/complex-form/location/location.form.ts @@ -0,0 +1,22 @@ +export const LOCATION_OPTIONS = [ + { + value: 'Aplerbeck', + label: 'Aplerbeck', + }, + { + value: 'Brackel', + label: 'Brackel', + }, + { + value: 'Dorstfeld', + label: 'Dorstfeld', + }, + { + value: 'Innenstadt Ost', + label: 'Innenstadt Ost', + }, + { + value: 'Innenstadt West', + label: 'Innenstadt West', + }, +]; diff --git a/packages/samples/react/src/scenarios/complex-form/schedule/component.tsx b/packages/samples/react/src/scenarios/complex-form/schedule/component.tsx new file mode 100644 index 0000000000..e76d3ff1fa --- /dev/null +++ b/packages/samples/react/src/scenarios/complex-form/schedule/component.tsx @@ -0,0 +1,16 @@ +import React, { FC } from 'react'; + +import { KolButton, KolInputDate, KolSpin } from '@public-ui/react'; +import { FromComponent } from '../common/form/component'; +import { FormProps } from '../common/form/types'; + +export const TerminScheduleComponent: FC = (props) => ( + {}}> +
+ + +
+ + +
+); diff --git a/packages/samples/react/src/scenarios/complex-form/schedule/schedule.form.ts b/packages/samples/react/src/scenarios/complex-form/schedule/schedule.form.ts new file mode 100644 index 0000000000..0dff8872fe --- /dev/null +++ b/packages/samples/react/src/scenarios/complex-form/schedule/schedule.form.ts @@ -0,0 +1,34 @@ +import { FormControl, InputControl, RequiredValidator, ValidationHandler } from '@leanup/form'; + +export interface Schedule { + schedule: string; + time: string; +} + +export class ScheduleForm extends FormControl { + public constructor() { + super('schedule'); + + this.addControl( + new InputControl('schedule', { + label: 'Datum', + mandatory: true, + }) + ); + + this.addControl( + new InputControl('time', { + label: 'Uhrzeit', + mandatory: true, + }) + ); + + const validationHandler = new ValidationHandler(); + validationHandler.validators.add([new RequiredValidator('Bitte wählen Sie ein Datum aus.')]); + this.getInput('schedule')?.setValidationHandler(validationHandler); + + const timeHandler = new ValidationHandler(); + timeHandler.validators.add([new RequiredValidator('Bitte wählen Sie eine Uhrzeit aus.')]); + this.getInput('time')?.setValidationHandler(timeHandler); + } +} diff --git a/packages/samples/react/src/scenarios/routes.ts b/packages/samples/react/src/scenarios/routes.ts new file mode 100644 index 0000000000..849b25b9c1 --- /dev/null +++ b/packages/samples/react/src/scenarios/routes.ts @@ -0,0 +1,8 @@ +import { Routes } from '../shares/types'; +import { TerminComponent } from './complex-form/component'; + +export const SCENARIO_ROUTES: Routes = { + scenarios: { + 'complex-form': TerminComponent, + }, +}; diff --git a/packages/samples/react/src/shares/routes.ts b/packages/samples/react/src/shares/routes.ts index 31f6f82ccc..19b74a2f33 100644 --- a/packages/samples/react/src/shares/routes.ts +++ b/packages/samples/react/src/shares/routes.ts @@ -13,6 +13,7 @@ import { FORM_ROUTES } from '../components/form/routes'; import { HANDOUT_ROUTES } from '../components/handout/routes'; import { HEADING_ROUTES } from '../components/heading/routes'; import { ICON_ROUTES } from '../components/icon/routes'; +import { IMAGE_ROUTES } from '../components/image/routes'; import { INDENTED_ROUTES } from '../components/indented-text/routes'; import { INPUT_CHECKBOX_ROUTES } from '../components/input-checkbox/routes'; import { INPUT_COLOR_ROUTES } from '../components/input-color/routes'; @@ -24,28 +25,28 @@ import { INPUT_PASSWORD_ROUTES } from '../components/input-password/routes'; import { INPUT_RADIO_ROUTES } from '../components/input-radio/routes'; import { INPUT_RANGE_ROUTES } from '../components/input-range/routes'; import { INPUT_TEXT_ROUTES } from '../components/input-text/routes'; +import { KOLIBRI_ROUTES } from '../components/kolibri/routes'; import { LINK_BUTTON_ROUTES } from '../components/link-button/routes'; import { LINK_GROUP_ROUTES } from '../components/link-group/routes'; import { LINK_ROUTES } from '../components/link/routes'; +import { LOGO_ROUTES } from '../components/logo/routes'; +import { MODAL_ROUTES } from '../components/modal/routes'; import { NAV_ROUTES } from '../components/nav/routes'; import { PAGINATION_ROUTES } from '../components/pagination/routes'; import { POPOVER_ROUTES } from '../components/popover/routes'; import { PROGRESS_ROUTES } from '../components/progress/routes'; +import { QUOTE_ROUTES } from '../components/quote/routes'; import { SELECT_ROUTES } from '../components/select/routes'; import { SKIP_NAV_ROUTES } from '../components/skip-nav/routes'; import { SPIN_ROUTES } from '../components/spin/routes'; import { SPLIT_BUTTON_ROUTES } from '../components/split-button/routes'; import { TABLE_ROUTES } from '../components/table/routes'; +import { TABS_ROUTES } from '../components/tabs/routes'; import { TEXTAREA_ROUTES } from '../components/textarea/routes'; import { TOAST_ROUTES } from '../components/toast/routes'; import { VERSION_ROUTES } from '../components/version/routes'; -import { Routes } from './types'; -import { IMAGE_ROUTES } from '../components/image/routes'; -import { KOLIBRI_ROUTES } from '../components/kolibri/routes'; -import { LOGO_ROUTES } from '../components/logo/routes'; -import { MODAL_ROUTES } from '../components/modal/routes'; -import { QUOTE_ROUTES } from '../components/quote/routes'; -import { TABS_ROUTES } from '../components/tabs/routes'; +import { SCENARIO_ROUTES } from '../scenarios/routes'; +import { Routes } from './typ es'; export const ROUTES: Routes = { ...HANDOUT_ROUTES, @@ -96,4 +97,5 @@ export const ROUTES: Routes = { ...TEXTAREA_ROUTES, ...TOAST_ROUTES, ...VERSION_ROUTES, + ...SCENARIO_ROUTES, }; From f2b4242f60b7af14156ed9378c75d48c6998f5e9 Mon Sep 17 00:00:00 2001 From: Stefan Dietz Date: Fri, 29 Sep 2023 09:16:00 +0200 Subject: [PATCH 02/20] Complex form scenario WIP --- packages/components/src/components.d.ts | 154 +++++---- packages/components/src/dev.html | 2 +- packages/samples/react/package.json | 4 +- .../appointment-form/AppointmentForm.tsx | 67 ++++ .../appointment-form/DistrictForm.tsx | 68 ++++ .../PersonalInformationForm.tsx | 19 ++ .../scenarios/appointment-form/Summary.tsx | 14 + .../samples/react/src/scenarios/routes.ts | 2 + pnpm-lock.yaml | 321 ++++++++++++------ 9 files changed, 470 insertions(+), 181 deletions(-) create mode 100644 packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx create mode 100644 packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx create mode 100644 packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx create mode 100644 packages/samples/react/src/scenarios/appointment-form/Summary.tsx diff --git a/packages/components/src/components.d.ts b/packages/components/src/components.d.ts index 9c82b9c5eb..948e6cc431 100644 --- a/packages/components/src/components.d.ts +++ b/packages/components/src/components.d.ts @@ -45,10 +45,10 @@ import { OptionsPropType, OptionsWithOptgroupPropType } from "./types/props/opti import { Orientation } from "./types/orientation"; import { InputTextType } from "./types/input/control/text"; import { DownloadPropType } from "./types/props/download"; +import { HrefPropType } from "./types/props/href"; import { LinkOnCallbacksPropType } from "./types/props/link-on-callbacks"; import { LinkTargetPropType } from "./types/props/link-target"; import { LinkUseCase } from "./types/button-link"; -import { HrefPropType } from "./types/props/href"; import { ListStyleType } from "./components/link-group/types"; import { LinkProps } from "./components/link/types"; import { Bundesamt, Bundesanstalt, Bundesministerium } from "./enums/bund"; @@ -104,10 +104,10 @@ export { OptionsPropType, OptionsWithOptgroupPropType } from "./types/props/opti export { Orientation } from "./types/orientation"; export { InputTextType } from "./types/input/control/text"; export { DownloadPropType } from "./types/props/download"; +export { HrefPropType } from "./types/props/href"; export { LinkOnCallbacksPropType } from "./types/props/link-on-callbacks"; export { LinkTargetPropType } from "./types/props/link-target"; export { LinkUseCase } from "./types/button-link"; -export { HrefPropType } from "./types/props/href"; export { ListStyleType } from "./components/link-group/types"; export { LinkProps } from "./components/link/types"; export { Bundesamt, Bundesanstalt, Bundesministerium } from "./enums/bund"; @@ -169,7 +169,7 @@ export namespace Components { */ "_alert"?: boolean; /** - * Defines whether the card has a close button. + * Defines whether the element can be closed. * @TODO : Change type back to `HasCloserPropType` after Stencil#4663 has been resolved. */ "_hasCloser"?: boolean; @@ -331,7 +331,8 @@ export namespace Components { */ "_disabled"?: boolean; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. + * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; /** @@ -432,7 +433,7 @@ export namespace Components { */ "_disabled"?: boolean; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -531,7 +532,8 @@ export namespace Components { */ "_disabled"?: boolean; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. + * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; /** @@ -796,7 +798,7 @@ export namespace Components { */ "_hideError"?: boolean; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -890,7 +892,7 @@ export namespace Components { */ "_hideError"?: boolean; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -987,7 +989,7 @@ export namespace Components { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -1078,7 +1080,7 @@ export namespace Components { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -1200,7 +1202,7 @@ export namespace Components { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -1322,7 +1324,7 @@ export namespace Components { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -1414,7 +1416,7 @@ export namespace Components { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -1541,7 +1543,7 @@ export namespace Components { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -1645,7 +1647,7 @@ export namespace Components { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -1731,7 +1733,8 @@ export namespace Components { */ "_error"?: string; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. + * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; /** @@ -1807,7 +1810,7 @@ export namespace Components { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -1912,7 +1915,7 @@ export namespace Components { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -2055,13 +2058,14 @@ export namespace Components { */ "_download"?: DownloadPropType; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. + * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; /** - * Defines the target URI of the link. + * Sets the target URI of the link or citation source. */ - "_href": string; + "_href": HrefPropType; /** * Defines the icon classnames (e.g. `_icon="fa-solid fa-user"`). */ @@ -2164,7 +2168,7 @@ export namespace Components { */ "_download"?: DownloadPropType; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -2291,13 +2295,14 @@ export namespace Components { */ "_download"?: DownloadPropType; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. + * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; /** - * Defines the target URI of the link. + * Sets the target URI of the link or citation source. */ - "_href": string; + "_href": HrefPropType; /** * Defines the icon classnames (e.g. `_icon="fa-solid fa-user"`). */ @@ -2420,7 +2425,7 @@ export namespace Components { */ "_hasCompactButton"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -2448,7 +2453,7 @@ export namespace Components { */ "_boundaryCount"?: number; /** - * Defines the custom class attribute for the buttons. + * Defines the custom class attribute if _variant="custom" is set. */ "_customClass"?: CustomClassPropType; /** @@ -2494,7 +2499,7 @@ export namespace Components { } interface KolPopover { /** - * Defines where to show the Tooltip preferably: top, right, bottom or left. In relation to trigger element. + * Defines the alignment of the tooltip, popover or tabs in relation to the element. */ "_align"?: AlignPropType; /** @@ -2537,7 +2542,7 @@ export namespace Components { */ "_caption"?: string; /** - * Defines the link to the source of the quote. + * Sets the target URI of the link or citation source. */ "_href": HrefPropType; /** @@ -2582,7 +2587,7 @@ export namespace Components { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -2676,7 +2681,7 @@ export namespace Components { } interface KolSpan { /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -2700,7 +2705,7 @@ export namespace Components { */ "_allowMarkdown"?: boolean; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -2760,7 +2765,8 @@ export namespace Components { */ "_disabled"?: boolean; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. + * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; /** @@ -2793,7 +2799,8 @@ export namespace Components { */ "_show"?: boolean; /** - * Defines whether to show the dropdown menu. + * Deprecated: Defines whether to show the dropdown menu. + * @deprecated use _show instead */ "_showDropdown"?: boolean; /** @@ -2933,7 +2940,7 @@ export namespace Components { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -3028,7 +3035,7 @@ export namespace Components { } interface KolTooltipWc { /** - * Defines the alignment of the tooltip in relation to the parent element. + * Defines the alignment of the tooltip, popover or tabs in relation to the element. */ "_align"?: AlignPropType; /** @@ -3558,7 +3565,7 @@ declare namespace LocalJSX { */ "_alert"?: boolean; /** - * Defines whether the card has a close button. + * Defines whether the element can be closed. * @TODO : Change type back to `HasCloserPropType` after Stencil#4663 has been resolved. */ "_hasCloser"?: boolean; @@ -3720,7 +3727,8 @@ declare namespace LocalJSX { */ "_disabled"?: boolean; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. + * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; /** @@ -3821,7 +3829,7 @@ declare namespace LocalJSX { */ "_disabled"?: boolean; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -3920,7 +3928,8 @@ declare namespace LocalJSX { */ "_disabled"?: boolean; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. + * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; /** @@ -4185,7 +4194,7 @@ declare namespace LocalJSX { */ "_hideError"?: boolean; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -4279,7 +4288,7 @@ declare namespace LocalJSX { */ "_hideError"?: boolean; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -4376,7 +4385,7 @@ declare namespace LocalJSX { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -4467,7 +4476,7 @@ declare namespace LocalJSX { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -4589,7 +4598,7 @@ declare namespace LocalJSX { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -4711,7 +4720,7 @@ declare namespace LocalJSX { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -4803,7 +4812,7 @@ declare namespace LocalJSX { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -4930,7 +4939,7 @@ declare namespace LocalJSX { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -5034,7 +5043,7 @@ declare namespace LocalJSX { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -5120,7 +5129,8 @@ declare namespace LocalJSX { */ "_error"?: string; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. + * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; /** @@ -5196,7 +5206,7 @@ declare namespace LocalJSX { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -5301,7 +5311,7 @@ declare namespace LocalJSX { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -5444,13 +5454,14 @@ declare namespace LocalJSX { */ "_download"?: DownloadPropType; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. + * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; /** - * Defines the target URI of the link. + * Sets the target URI of the link or citation source. */ - "_href": string; + "_href": HrefPropType; /** * Defines the icon classnames (e.g. `_icon="fa-solid fa-user"`). */ @@ -5553,7 +5564,7 @@ declare namespace LocalJSX { */ "_download"?: DownloadPropType; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -5680,13 +5691,14 @@ declare namespace LocalJSX { */ "_download"?: DownloadPropType; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. + * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; /** - * Defines the target URI of the link. + * Sets the target URI of the link or citation source. */ - "_href": string; + "_href": HrefPropType; /** * Defines the icon classnames (e.g. `_icon="fa-solid fa-user"`). */ @@ -5809,7 +5821,7 @@ declare namespace LocalJSX { */ "_hasCompactButton"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -5837,7 +5849,7 @@ declare namespace LocalJSX { */ "_boundaryCount"?: number; /** - * Defines the custom class attribute for the buttons. + * Defines the custom class attribute if _variant="custom" is set. */ "_customClass"?: CustomClassPropType; /** @@ -5883,7 +5895,7 @@ declare namespace LocalJSX { } interface KolPopover { /** - * Defines where to show the Tooltip preferably: top, right, bottom or left. In relation to trigger element. + * Defines the alignment of the tooltip, popover or tabs in relation to the element. */ "_align"?: AlignPropType; /** @@ -5926,7 +5938,7 @@ declare namespace LocalJSX { */ "_caption"?: string; /** - * Defines the link to the source of the quote. + * Sets the target URI of the link or citation source. */ "_href": HrefPropType; /** @@ -5971,7 +5983,7 @@ declare namespace LocalJSX { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -6065,7 +6077,7 @@ declare namespace LocalJSX { } interface KolSpan { /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -6089,7 +6101,7 @@ declare namespace LocalJSX { */ "_allowMarkdown"?: boolean; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -6149,7 +6161,8 @@ declare namespace LocalJSX { */ "_disabled"?: boolean; /** - * Hides the label and shows the description in a Tooltip instead. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. + * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; /** @@ -6182,7 +6195,8 @@ declare namespace LocalJSX { */ "_show"?: boolean; /** - * Defines whether to show the dropdown menu. + * Deprecated: Defines whether to show the dropdown menu. + * @deprecated use _show instead */ "_showDropdown"?: boolean; /** @@ -6322,7 +6336,7 @@ declare namespace LocalJSX { */ "_hideError"?: boolean; /** - * Hides the label. + * Hides the caption by default and displays the caption text with a tooltip when the interactive element is focused or the mouse is over it. * @TODO : Change type back to `HideLabelPropType` after Stencil#4663 has been resolved. */ "_hideLabel"?: boolean; @@ -6416,7 +6430,7 @@ declare namespace LocalJSX { } interface KolTooltipWc { /** - * Defines the alignment of the tooltip in relation to the parent element. + * Defines the alignment of the tooltip, popover or tabs in relation to the element. */ "_align"?: AlignPropType; /** diff --git a/packages/components/src/dev.html b/packages/components/src/dev.html index 2d3d490096..b32aabbe45 100644 --- a/packages/components/src/dev.html +++ b/packages/components/src/dev.html @@ -34,7 +34,7 @@
- z.B. +
diff --git a/packages/samples/react/package.json b/packages/samples/react/package.json index 293017664e..8963091ba5 100644 --- a/packages/samples/react/package.json +++ b/packages/samples/react/package.json @@ -34,6 +34,7 @@ "cpy-cli": "5.0.0", "eslint-plugin-jsx-a11y": "6.7.1", "eslint-plugin-react": "7.33.2", + "formik": "2.4.5", "nightwatch-axe-verbose": "2.2.2", "npm-run-all": "4.1.5", "react": "18.2.0", @@ -43,7 +44,8 @@ "rimraf": "3.0.2", "ts-prune": "0.10.3", "typescript": "5.2.2", - "world_countries_lists": "2.8.2" + "world_countries_lists": "2.8.2", + "yup": "1.3.1" }, "files": [ ".eslintignore", diff --git a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx new file mode 100644 index 0000000000..bec1b6658a --- /dev/null +++ b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx @@ -0,0 +1,67 @@ +import React, { useState } from 'react'; +import { KolHeading, KolTabs } from '@public-ui/react'; +import { DistrictForm } from './DistrictForm'; +import { Summary } from './Summary'; +import { PersonalInformationForm } from './PersonalInformationForm'; +import { Formik } from 'formik'; +import * as Yup from 'yup'; + +const validationSchema = Yup.object().shape({ + district: Yup.string().required('Bitte Stadtteil wählen.'), +}); + +console.log(validationSchema.describe()); + +export interface FormProps {} +export interface FormValues { + district: string; + name: string; +} + +export function AppointmentForm() { + const [selectedTab, setSelectedTab] = useState(0); + const initialValues: FormValues = { + district: '', + name: '', + }; + + const handleSubmit = (values: FormValues) => { + console.log(`Form submitted:`, values); + }; + + return ( + + setSelectedTab(selectedTab) }} + > +
+ setSelectedTab(1)} /> +
+
+ +
+
+ +
+
+ +
+
+
+ ); +} diff --git a/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx b/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx new file mode 100644 index 0000000000..4dfcd8d9ab --- /dev/null +++ b/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { KolButton, KolForm, KolHeading, KolSelect } from '@public-ui/react'; +import { Field, FieldProps, useFormikContext } from 'formik'; +import { FormValues } from './AppointmentForm'; +import { EventCallback } from '@public-ui/components/src/types/callbacks'; + +const LOCATION_OPTIONS = [ + { + value: 'Aplerbeck', + label: 'Aplerbeck', + }, + { + value: 'Brackel', + label: 'Brackel', + }, + { + value: 'Dorstfeld', + label: 'Dorstfeld', + }, + { + value: 'Innenstadt Ost', + label: 'Innenstadt Ost', + }, + { + value: 'Innenstadt West', + label: 'Innenstadt West', + }, +]; + +export type DistrictFormProps = { + onSubmit: EventCallback; +}; +export function DistrictForm({ onSubmit }: DistrictFormProps) { + const form = useFormikContext(); + + return ( +
+ + is validating? {String(form.isValidating)} + + + {({ field }: FieldProps) => ( + <> + { + if (event.target) { + const [value] = values as [FormValues['district']]; + void form.setFieldValue('district', value); + void form.setTouched({ district: true }); + } + }, + }} + _error={form.errors.district} + _touched={form.touched.district} + /> + + )} + + + + +
+ ); +} diff --git a/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx b/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx new file mode 100644 index 0000000000..500285bf23 --- /dev/null +++ b/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { KolHeading, KolInputText } from '@public-ui/react'; +import { Field, FieldProps, Form, withFormik } from 'formik'; +import { FormProps, FormValues } from './AppointmentForm'; + +export function PersonalInformationForm() { + return ( + <> + +
+ + {({ field, form, meta }: FieldProps) => ( + + )} + +
+ + ); +} diff --git a/packages/samples/react/src/scenarios/appointment-form/Summary.tsx b/packages/samples/react/src/scenarios/appointment-form/Summary.tsx new file mode 100644 index 0000000000..8ab812269b --- /dev/null +++ b/packages/samples/react/src/scenarios/appointment-form/Summary.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { useFormikContext } from 'formik'; +import { KolHeading } from '@public-ui/react'; + +export function Summary() { + const { values } = useFormikContext(); + + return ( + <> + +
{JSON.stringify(values, null, 2)}
+ + ); +} diff --git a/packages/samples/react/src/scenarios/routes.ts b/packages/samples/react/src/scenarios/routes.ts index 849b25b9c1..9b3ba6cc40 100644 --- a/packages/samples/react/src/scenarios/routes.ts +++ b/packages/samples/react/src/scenarios/routes.ts @@ -1,8 +1,10 @@ import { Routes } from '../shares/types'; import { TerminComponent } from './complex-form/component'; +import { AppointmentForm } from './appointment-form/AppointmentForm'; export const SCENARIO_ROUTES: Routes = { scenarios: { 'complex-form': TerminComponent, + 'appointment-form': AppointmentForm, }, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c757051523..8a04320179 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: version: 17.7.0 '@types/node': specifier: ts5.1 - version: 20.6.5 + version: 20.7.0 cross-env: specifier: 7.0.3 version: 7.0.3 @@ -52,7 +52,7 @@ importers: version: 3.0.2 ts-node: specifier: 10.9.1 - version: 10.9.1(@types/node@20.6.5)(typescript@5.2.2) + version: 10.9.1(@types/node@20.7.0)(typescript@5.2.2) ts-prune: specifier: 0.10.3 version: 0.10.3 @@ -72,7 +72,7 @@ importers: specifier: 11.2.14 version: 11.2.14(rxjs@6.5.5)(zone.js@0.11.8) '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../../components '@types/minimatch': specifier: 5.1.2 @@ -117,7 +117,7 @@ importers: specifier: 12.2.17 version: 12.2.17(rxjs@7.6.0)(zone.js@0.11.8) '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../../components '@types/minimatch': specifier: 5.1.2 @@ -162,7 +162,7 @@ importers: specifier: 13.4.0 version: 13.4.0(rxjs@7.6.0)(zone.js@0.11.8) '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../../components '@types/minimatch': specifier: 5.1.2 @@ -172,7 +172,7 @@ importers: version: 1.2.2 '@types/node': specifier: ts4.6 - version: 20.6.5 + version: 20.7.0 '@types/normalize-package-data': specifier: 2.4.2 version: 2.4.2 @@ -207,7 +207,7 @@ importers: specifier: 14.3.0 version: 14.3.0(rxjs@7.6.0)(zone.js@0.12.0) '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../../components '@types/minimatch': specifier: 5.1.2 @@ -217,7 +217,7 @@ importers: version: 1.2.2 '@types/node': specifier: ts4.8 - version: 20.6.5 + version: 20.7.0 '@types/normalize-package-data': specifier: 2.4.2 version: 2.4.2 @@ -252,7 +252,7 @@ importers: specifier: 15.2.9 version: 15.2.9(rxjs@7.8.1)(zone.js@0.12.0) '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../../components '@types/minimatch': specifier: 5.1.2 @@ -262,7 +262,7 @@ importers: version: 1.2.2 '@types/node': specifier: ts4.9 - version: 20.6.5 + version: 20.7.0 '@types/normalize-package-data': specifier: 2.4.2 version: 2.4.2 @@ -297,7 +297,7 @@ importers: specifier: 16.2.6 version: 16.2.6(rxjs@7.8.1)(zone.js@0.13.3) '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../../components '@types/minimatch': specifier: 5.1.2 @@ -307,7 +307,7 @@ importers: version: 1.2.2 '@types/node': specifier: ts5.1 - version: 20.6.5 + version: 20.7.0 '@types/normalize-package-data': specifier: 2.4.2 version: 2.4.2 @@ -333,7 +333,7 @@ importers: packages/adapters/hydrate: devDependencies: '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../components rimraf: specifier: 3.0.2 @@ -342,14 +342,14 @@ importers: packages/adapters/preact: dependencies: '@public-ui/react': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../react preact: specifier: '>=10.11.3' version: 10.11.3 devDependencies: '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../components react: specifier: 18.2.0 @@ -367,7 +367,7 @@ importers: packages/adapters/react: devDependencies: '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../components '@types/minimatch': specifier: 5.1.2 @@ -377,7 +377,7 @@ importers: version: 1.2.2 '@types/node': specifier: ts5.1 - version: 20.6.5 + version: 20.7.0 '@types/normalize-package-data': specifier: 2.4.2 version: 2.4.2 @@ -403,7 +403,7 @@ importers: packages/adapters/react-standalone: dependencies: '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../components react: specifier: '>=16.14.0' @@ -413,7 +413,7 @@ importers: version: 18.2.0(react@18.2.0) devDependencies: '@public-ui/react': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../react cpy-cli: specifier: 5.0.0 @@ -425,7 +425,7 @@ importers: packages/adapters/solid: devDependencies: '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../components '@types/minimatch': specifier: 5.1.2 @@ -435,7 +435,7 @@ importers: version: 1.2.2 '@types/node': specifier: ts5.1 - version: 20.6.5 + version: 20.7.0 '@types/normalize-package-data': specifier: 2.4.2 version: 2.4.2 @@ -455,7 +455,7 @@ importers: specifier: 7.23.0 version: 7.23.0 '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../components '@types/minimatch': specifier: 5.1.2 @@ -465,7 +465,7 @@ importers: version: 1.2.2 '@types/node': specifier: ts5.1 - version: 20.6.5 + version: 20.7.0 '@types/normalize-package-data': specifier: 2.4.2 version: 2.4.2 @@ -488,7 +488,7 @@ importers: specifier: 1.5.3 version: 1.5.3 '@public-ui/schema': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../schema i18next: specifier: 23.5.1 @@ -529,7 +529,7 @@ importers: version: 4.2.2 '@types/node': specifier: ts5.1 - version: 20.6.5 + version: 20.7.0 '@types/pug': specifier: 2.0.7 version: 2.0.7 @@ -668,7 +668,7 @@ importers: devDependencies: '@types/node': specifier: ts5.1 - version: 20.6.5 + version: 20.7.0 cross-env: specifier: 7.0.3 version: 7.0.3 @@ -683,7 +683,7 @@ importers: version: 3.0.2 ts-node: specifier: 10.9.1 - version: 10.9.1(@types/node@20.6.5)(typescript@5.2.2) + version: 10.9.1(@types/node@20.7.0)(typescript@5.2.2) ts-prune: specifier: 0.10.3 version: 0.10.3 @@ -700,13 +700,13 @@ importers: specifier: 0.0.3 version: 0.0.3(@public-ui/components@packages+components) '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../components '@public-ui/solid': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../adapters/solid '@public-ui/themes': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../themes monaco-editor: specifier: 0.43.0 @@ -800,13 +800,13 @@ importers: specifier: 0.0.3 version: 0.0.3(@public-ui/components@packages+components) '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../components '@public-ui/react': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../adapters/react '@public-ui/themes': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../themes '@types/node': specifier: 20.6.5 @@ -838,6 +838,9 @@ importers: eslint-plugin-react: specifier: 7.33.2 version: 7.33.2(eslint@8.50.0) + formik: + specifier: 2.4.5 + version: 2.4.5(react@18.2.0) nightwatch-axe-verbose: specifier: 2.2.2 version: 2.2.2 @@ -868,6 +871,9 @@ importers: world_countries_lists: specifier: 2.8.2 version: 2.8.2 + yup: + specifier: 1.3.1 + version: 1.3.1 packages/schema: dependencies: @@ -877,7 +883,7 @@ importers: devDependencies: '@types/node': specifier: ts5.1 - version: 20.6.5 + version: 20.7.0 '@typescript-eslint/eslint-plugin': specifier: 6.7.2 version: 6.7.2(@typescript-eslint/parser@6.7.2)(eslint@8.50.0)(typescript@5.2.2) @@ -900,15 +906,15 @@ importers: packages/themes: dependencies: '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../components devDependencies: '@public-ui/schema': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../schema '@types/node': specifier: ts5.1 - version: 20.6.5 + version: 20.7.0 '@typescript-eslint/eslint-plugin': specifier: 6.7.2 version: 6.7.2(@typescript-eslint/parser@6.7.2)(eslint@8.50.0)(typescript@5.2.2) @@ -934,18 +940,18 @@ importers: packages/themes/bmf: dependencies: '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../components devDependencies: '@public-ui/schema': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../schema '@public-ui/visual-tests': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../tools/visual-tests '@types/node': specifier: ts5.2 - version: 20.6.5 + version: 20.7.0 typescript: specifier: 5.2.2 version: 5.2.2 @@ -953,18 +959,18 @@ importers: packages/themes/bzst: dependencies: '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../components devDependencies: '@public-ui/schema': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../schema '@public-ui/visual-tests': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../tools/visual-tests '@types/node': specifier: ts5.2 - version: 20.6.5 + version: 20.7.0 typescript: specifier: 5.2.2 version: 5.2.2 @@ -972,18 +978,18 @@ importers: packages/themes/default: dependencies: '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../components devDependencies: '@public-ui/schema': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../schema '@public-ui/visual-tests': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../tools/visual-tests '@types/node': specifier: ts5.2 - version: 20.6.5 + version: 20.7.0 typescript: specifier: 5.2.2 version: 5.2.2 @@ -991,18 +997,18 @@ importers: packages/themes/ecl: dependencies: '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../components devDependencies: '@public-ui/schema': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../schema '@public-ui/visual-tests': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../tools/visual-tests '@types/node': specifier: ts5.2 - version: 20.6.5 + version: 20.7.0 npm-run-all: specifier: 4.1.5 version: 4.1.5 @@ -1013,18 +1019,18 @@ importers: packages/themes/itzbund: dependencies: '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../components devDependencies: '@public-ui/schema': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../schema '@public-ui/visual-tests': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../tools/visual-tests '@types/node': specifier: ts5.2 - version: 20.6.5 + version: 20.7.0 typescript: specifier: 5.2.2 version: 5.2.2 @@ -1032,18 +1038,18 @@ importers: packages/themes/mfm: dependencies: '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../components devDependencies: '@public-ui/schema': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../schema '@public-ui/visual-tests': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../tools/visual-tests '@types/node': specifier: ts5.2 - version: 20.6.5 + version: 20.7.0 typescript: specifier: 5.2.2 version: 5.2.2 @@ -1051,18 +1057,18 @@ importers: packages/themes/zoll: dependencies: '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../components devDependencies: '@public-ui/schema': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../schema '@public-ui/visual-tests': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../tools/visual-tests '@types/node': specifier: ts5.2 - version: 20.6.5 + version: 20.7.0 npm-run-all: specifier: 4.1.5 version: 4.1.5 @@ -1095,7 +1101,7 @@ importers: version: 7.5.4 devDependencies: '@public-ui/components': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../components '@types/gradient-string': specifier: 1.1.3 @@ -1164,7 +1170,7 @@ importers: specifier: 1.38.1 version: 1.38.1 '@public-ui/sample-react': - specifier: 1.7.0-rc.13 + specifier: 1.7.0-rc.15 version: link:../../samples/react portfinder: specifier: 1.0.32 @@ -6056,7 +6062,7 @@ packages: engines: {node: '>= 10.14.2'} dependencies: '@jest/types': 26.6.2 - '@types/node': 20.6.5 + '@types/node': 20.7.0 chalk: 4.1.2 jest-message-util: 26.6.2 jest-util: 26.6.2 @@ -6072,7 +6078,7 @@ packages: '@jest/test-result': 26.6.2 '@jest/transform': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.6.5 + '@types/node': 20.7.0 ansi-escapes: 4.3.2 chalk: 4.1.2 exit: 0.1.2 @@ -6109,7 +6115,7 @@ packages: dependencies: '@jest/fake-timers': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.6.5 + '@types/node': 20.7.0 jest-mock: 26.6.2 dev: true @@ -6119,7 +6125,7 @@ packages: dependencies: '@jest/types': 26.6.2 '@sinonjs/fake-timers': 6.0.1 - '@types/node': 20.6.5 + '@types/node': 20.7.0 jest-message-util: 26.6.2 jest-mock: 26.6.2 jest-util: 26.6.2 @@ -6240,7 +6246,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 20.6.5 + '@types/node': 20.7.0 '@types/yargs': 15.0.15 chalk: 4.1.2 dev: true @@ -7510,12 +7516,12 @@ packages: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: '@types/connect': 3.4.35 - '@types/node': 20.6.5 + '@types/node': 20.7.0 /@types/bonjour@3.5.10: resolution: {integrity: sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw==} dependencies: - '@types/node': 20.6.5 + '@types/node': 20.7.0 /@types/chai@4.3.6: resolution: {integrity: sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==} @@ -7534,12 +7540,12 @@ packages: resolution: {integrity: sha512-4x5FkPpLipqwthjPsF7ZRbOv3uoLUFkTA9G9v583qi4pACvq0uTELrB8OLUzPWUI4IJIyvM85vzkV1nyiI2Lig==} dependencies: '@types/express-serve-static-core': 4.17.36 - '@types/node': 20.6.5 + '@types/node': 20.7.0 /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 20.6.5 + '@types/node': 20.7.0 /@types/eslint-scope@3.7.4: resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} @@ -7563,7 +7569,7 @@ packages: /@types/express-serve-static-core@4.17.36: resolution: {integrity: sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==} dependencies: - '@types/node': 20.6.5 + '@types/node': 20.7.0 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 '@types/send': 0.17.1 @@ -7579,7 +7585,7 @@ packages: /@types/graceful-fs@4.1.6: resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} dependencies: - '@types/node': 20.6.5 + '@types/node': 20.7.0 dev: true /@types/gradient-string@1.1.3: @@ -7588,6 +7594,13 @@ packages: '@types/tinycolor2': 1.4.3 dev: true + /@types/hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-YIQtIg4PKr7ZyqNPZObpxfHsHEmuB8dXCxd6qVcGuQVDK2bpsF7bYNnBJ4Nn7giuACZg+WewExgrtAJ3XnA4Xw==} + dependencies: + '@types/react': 18.2.22 + hoist-non-react-statics: 3.3.2 + dev: false + /@types/http-cache-semantics@4.0.2: resolution: {integrity: sha512-FD+nQWA2zJjh4L9+pFXqWOi0Hs1ryBCfI+985NjluQ1p8EYtoLvjLOKidXBtZ4/IcxDX4o8/E8qDS3540tNliw==} dev: true @@ -7598,7 +7611,7 @@ packages: /@types/http-proxy@1.17.11: resolution: {integrity: sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==} dependencies: - '@types/node': 20.6.5 + '@types/node': 20.7.0 /@types/istanbul-lib-coverage@2.0.4: resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} @@ -7684,6 +7697,9 @@ packages: /@types/node@20.6.5: resolution: {integrity: sha512-2qGq5LAOTh9izcc0+F+dToFigBWiK1phKPt7rNhOqJSr35y8rlIBjDwGtFSgAI6MGIhjwOVNSQZVdJsZJ2uR1w==} + /@types/node@20.7.0: + resolution: {integrity: sha512-zI22/pJW2wUZOVyguFaUL1HABdmSVxpXrzIqkjsHmyUjNhPoWM1CKfvVuXfetHhIok4RY573cqS0mZ1SJEnoTg==} + /@types/normalize-package-data@2.4.2: resolution: {integrity: sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==} dev: true @@ -7723,7 +7739,7 @@ packages: /@types/resolve@1.17.1: resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} dependencies: - '@types/node': 20.6.5 + '@types/node': 20.7.0 dev: true /@types/resolve@1.20.2: @@ -7743,7 +7759,7 @@ packages: resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} dependencies: '@types/mime': 1.3.2 - '@types/node': 20.6.5 + '@types/node': 20.7.0 /@types/serve-index@1.9.1: resolution: {integrity: sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg==} @@ -7755,7 +7771,7 @@ packages: dependencies: '@types/http-errors': 2.0.1 '@types/mime': 3.0.1 - '@types/node': 20.6.5 + '@types/node': 20.7.0 /@types/sinon@10.0.16: resolution: {integrity: sha512-j2Du5SYpXZjJVJtXBokASpPRj+e2z+VUhCPHmM6WMfe3dpHu6iVKJMU6AiBcMp/XTAYnEj6Wc1trJUWwZ0QaAQ==} @@ -7768,7 +7784,7 @@ packages: /@types/sockjs@0.3.33: resolution: {integrity: sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw==} dependencies: - '@types/node': 20.6.5 + '@types/node': 20.7.0 /@types/stack-utils@2.0.1: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} @@ -7792,7 +7808,7 @@ packages: /@types/ws@8.5.5: resolution: {integrity: sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==} dependencies: - '@types/node': 20.6.5 + '@types/node': 20.7.0 /@types/yargs-interactive@2.1.3: resolution: {integrity: sha512-bYB8ah0JPR6/lpHlxUzeHsrb3RK5OW7N8Hnth2nefnr6zQ5KFoDQ6wM5x58dTLEDYrwikFy3EPTf/O0HKLNaIg==} @@ -7819,7 +7835,7 @@ packages: resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} requiresBuild: true dependencies: - '@types/node': 20.6.5 + '@types/node': 20.7.0 optional: true /@typescript-eslint/eslint-plugin@6.7.0(@typescript-eslint/parser@6.7.0)(eslint@8.49.0)(typescript@5.2.2): @@ -9681,7 +9697,7 @@ packages: engines: {node: '>=12.13.0'} hasBin: true dependencies: - '@types/node': 20.6.5 + '@types/node': 20.7.0 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 2.0.1 @@ -10741,6 +10757,11 @@ packages: /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + /deepmerge@2.2.1: + resolution: {integrity: sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==} + engines: {node: '>=0.10.0'} + dev: false + /deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -11317,10 +11338,6 @@ packages: /es-module-lexer@1.3.0: resolution: {integrity: sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==} - dev: true - - /es-module-lexer@1.3.1: - resolution: {integrity: sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q==} /es-set-tostringtag@2.0.1: resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} @@ -12386,6 +12403,22 @@ packages: combined-stream: 1.0.8 mime-types: 2.1.35 + /formik@2.4.5(react@18.2.0): + resolution: {integrity: sha512-Gxlht0TD3vVdzMDHwkiNZqJ7Mvg77xQNfmBRrNtvzcHZs72TJppSTDKHpImCMJZwcWPBJ8jSQQ95GJzXFf1nAQ==} + peerDependencies: + react: '>=16.8.0' + dependencies: + '@types/hoist-non-react-statics': 3.3.2 + deepmerge: 2.2.1 + hoist-non-react-statics: 3.3.2 + lodash: 4.17.21 + lodash-es: 4.17.21 + react: 18.2.0 + react-fast-compare: 2.0.4 + tiny-warning: 1.0.3 + tslib: 2.6.2 + dev: false + /forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -13085,6 +13118,12 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + /hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + dependencies: + react-is: 16.13.1 + dev: false + /homedir-polyfill@1.0.3: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} engines: {node: '>=0.10.0'} @@ -14178,7 +14217,7 @@ packages: jest-validate: 26.6.2 micromatch: 4.0.5 pretty-format: 26.6.2 - ts-node: 10.9.1(@types/node@20.6.5)(typescript@5.2.2) + ts-node: 10.9.1(@types/node@20.7.0)(typescript@5.2.2) transitivePeerDependencies: - bufferutil - canvas @@ -14231,7 +14270,7 @@ packages: '@jest/environment': 26.6.2 '@jest/fake-timers': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.6.5 + '@types/node': 20.7.0 jest-mock: 26.6.2 jest-util: 26.6.2 jsdom: 16.7.0 @@ -14249,7 +14288,7 @@ packages: '@jest/environment': 26.6.2 '@jest/fake-timers': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.6.5 + '@types/node': 20.7.0 jest-mock: 26.6.2 jest-util: 26.6.2 dev: true @@ -14270,7 +14309,7 @@ packages: dependencies: '@jest/types': 26.6.2 '@types/graceful-fs': 4.1.6 - '@types/node': 20.6.5 + '@types/node': 20.7.0 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -14296,7 +14335,7 @@ packages: '@jest/source-map': 26.6.2 '@jest/test-result': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.6.5 + '@types/node': 20.7.0 chalk: 4.1.2 co: 4.6.0 expect: 26.6.2 @@ -14355,7 +14394,7 @@ packages: engines: {node: '>= 10.14.2'} dependencies: '@jest/types': 26.6.2 - '@types/node': 20.6.5 + '@types/node': 20.7.0 dev: true /jest-pnp-resolver@1.2.3(jest-resolve@26.6.2): @@ -14408,7 +14447,7 @@ packages: '@jest/environment': 26.6.2 '@jest/test-result': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.6.5 + '@types/node': 20.7.0 chalk: 4.1.2 emittery: 0.7.2 exit: 0.1.2 @@ -14476,7 +14515,7 @@ packages: resolution: {integrity: sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==} engines: {node: '>= 10.14.2'} dependencies: - '@types/node': 20.6.5 + '@types/node': 20.7.0 graceful-fs: 4.2.11 dev: true @@ -14509,7 +14548,7 @@ packages: engines: {node: '>= 10.14.2'} dependencies: '@jest/types': 26.6.2 - '@types/node': 20.6.5 + '@types/node': 20.7.0 chalk: 4.1.2 graceful-fs: 4.2.11 is-ci: 2.0.0 @@ -14534,7 +14573,7 @@ packages: dependencies: '@jest/test-result': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 20.6.5 + '@types/node': 20.7.0 ansi-escapes: 4.3.2 chalk: 4.1.2 jest-util: 26.6.2 @@ -14545,7 +14584,7 @@ packages: resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.6.5 + '@types/node': 20.7.0 merge-stream: 2.0.0 supports-color: 7.2.0 dev: true @@ -14554,7 +14593,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.6.5 + '@types/node': 20.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -15325,6 +15364,10 @@ packages: engines: {node: '>= 10'} dev: true + /lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + dev: false + /lodash._arraycopy@3.0.0: resolution: {integrity: sha512-RHShTDnPKP7aWxlvXKiDT6IX2jCs6YZLCtNhOru/OX2Q/tzX295vVBK5oX1ECtN+2r86S0Ogy8ykP1sgCZAN0A==} @@ -17541,7 +17584,7 @@ packages: optional: true dependencies: lilconfig: 2.1.0 - ts-node: 10.9.1(@types/node@20.6.5)(typescript@5.2.2) + ts-node: 10.9.1(@types/node@20.7.0)(typescript@5.2.2) yaml: 2.3.1 dev: true @@ -17979,6 +18022,10 @@ packages: object-assign: 4.1.1 react-is: 16.13.1 + /property-expr@2.0.5: + resolution: {integrity: sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==} + dev: false + /proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} dev: true @@ -18291,6 +18338,10 @@ packages: resolution: {integrity: sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==} dev: true + /react-fast-compare@2.0.4: + resolution: {integrity: sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==} + dev: false + /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -19517,7 +19568,7 @@ packages: resolution: {integrity: sha512-DI7/OuAUD+GMpR6dmu8lliO2Wg5zfeh+/xsdyJZCzd8o5JgFUjCeLsBDuZjIQJdwXS3J0L/uZYrELKYqx+PXog==} engines: {node: '>=8.0'} dependencies: - '@types/node': 20.6.5 + '@types/node': 20.7.0 image-ssim: 0.2.0 jpeg-js: 0.4.4 dev: true @@ -20123,6 +20174,14 @@ packages: next-tick: 1.1.0 dev: false + /tiny-case@1.0.3: + resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} + dev: false + + /tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + dev: false + /tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} dev: false @@ -20209,6 +20268,10 @@ packages: resolution: {integrity: sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==} dev: true + /toposort@2.0.2: + resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} + dev: false + /touch@3.1.0: resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} hasBin: true @@ -20347,6 +20410,37 @@ packages: yn: 3.1.1 dev: true + /ts-node@10.9.1(@types/node@20.7.0)(typescript@5.2.2): + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.7.0 + acorn: 8.10.0 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.2.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + /ts-prune@0.10.3: resolution: {integrity: sha512-iS47YTbdIcvN8Nh/1BFyziyUqmjXz7GVzWu02RaZXqb+e/3Qe1B7IQ4860krOeCGUeJmterAlaM2FRH0Ue0hjw==} hasBin: true @@ -20933,13 +21027,13 @@ packages: merge-anything: 5.1.7 solid-js: 1.7.12 solid-refresh: 0.5.3(solid-js@1.7.12) - vite: 4.4.9(@types/node@20.6.5)(less@4.2.0) + vite: 4.4.9(@types/node@20.7.0)(less@4.2.0) vitefu: 0.2.4(vite@4.4.9) transitivePeerDependencies: - supports-color dev: true - /vite@4.4.9(@types/node@20.6.5)(less@4.2.0): + /vite@4.4.9(@types/node@20.7.0)(less@4.2.0): resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -20967,7 +21061,7 @@ packages: terser: optional: true dependencies: - '@types/node': 20.6.5 + '@types/node': 20.7.0 esbuild: 0.18.20 less: 4.2.0 postcss: 8.4.30 @@ -20984,7 +21078,7 @@ packages: vite: optional: true dependencies: - vite: 4.4.9(@types/node@20.6.5)(less@4.2.0) + vite: 4.4.9(@types/node@20.7.0)(less@4.2.0) dev: true /vlq@0.2.3: @@ -21288,7 +21382,7 @@ packages: browserslist: 4.21.10 chrome-trace-event: 1.0.3 enhanced-resolve: 5.15.0 - es-module-lexer: 1.3.1 + es-module-lexer: 1.3.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -21935,6 +22029,15 @@ packages: engines: {node: '>=12.20'} dev: true + /yup@1.3.1: + resolution: {integrity: sha512-2stNyEF96SnPUxzRL99kt1bEHWytnvC2stwmTTqjoFXZRf63JtYK2pQt2AJvWcQvkrAzr/pcXvc6c5vrqsBzDg==} + dependencies: + property-expr: 2.0.5 + tiny-case: 1.0.3 + toposort: 2.0.2 + type-fest: 2.19.0 + dev: false + /zod-validation-error@1.5.0(zod@3.22.2): resolution: {integrity: sha512-/7eFkAI4qV0tcxMBB/3+d2c1P6jzzZYdYSlBuAklzMuCrJu5bzJfHS0yVAS87dRHVlhftd6RFJDIvv03JgkSbw==} engines: {node: '>=16.0.0'} From df51f3afbc816f727abe51a053db662b2f3b82eb Mon Sep 17 00:00:00 2001 From: Stefan Dietz Date: Mon, 2 Oct 2023 11:29:14 +0200 Subject: [PATCH 03/20] Fix props changed to `undefined` not updating. --- packages/components/src/utils/prop.validators.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/utils/prop.validators.ts b/packages/components/src/utils/prop.validators.ts index 631bba1042..341e9ebe35 100644 --- a/packages/components/src/utils/prop.validators.ts +++ b/packages/components/src/utils/prop.validators.ts @@ -99,7 +99,7 @@ export const setState = (component: Generic.Element.Component, propName: stri if (component.nextState === undefined) { component.nextState = new Map(); } - if (component.nextState.get(propName) !== value) { + if (component.nextState.get(propName) !== value || value === undefined) { const nextHooks = component.nextHooks.get(propName); if (nextHooks instanceof Map === false) { component.nextHooks.set(propName, new Map()); From 9d95d44bcbab5131f6db07d1969c23c71e8171f0 Mon Sep 17 00:00:00 2001 From: Stefan Dietz Date: Thu, 5 Oct 2023 10:12:24 +0200 Subject: [PATCH 04/20] Add form features --- .../appointment-form/AppointmentForm.tsx | 52 ++++++--- .../AvailableAppointmentsForm.tsx | 108 ++++++++++++++++++ .../appointment-form/DistrictForm.tsx | 62 +++++----- .../scenarios/appointment-form/ErrorList.tsx | 35 ++++++ .../PersonalInformationForm.tsx | 49 ++++++-- .../appointment-form/appointmentService.ts | 32 ++++++ 6 files changed, 289 insertions(+), 49 deletions(-) create mode 100644 packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx create mode 100644 packages/samples/react/src/scenarios/appointment-form/ErrorList.tsx create mode 100644 packages/samples/react/src/scenarios/appointment-form/appointmentService.ts diff --git a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx index bec1b6658a..e74b98f41f 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx @@ -1,32 +1,55 @@ import React, { useState } from 'react'; -import { KolHeading, KolTabs } from '@public-ui/react'; +import { KolTabs } from '@public-ui/react'; import { DistrictForm } from './DistrictForm'; import { Summary } from './Summary'; import { PersonalInformationForm } from './PersonalInformationForm'; import { Formik } from 'formik'; import * as Yup from 'yup'; - -const validationSchema = Yup.object().shape({ - district: Yup.string().required('Bitte Stadtteil wählen.'), -}); - -console.log(validationSchema.describe()); +import { AvailableAppointmentsForm } from './AvailableAppointmentsForm'; +import { Iso8601 } from '@public-ui/components'; export interface FormProps {} export interface FormValues { district: string; name: string; + date: Iso8601; + time: Iso8601; +} + +enum FormSection { + DISTRICT, + AVAILABLE_APPOINTMENTS, + PERSONAL_INFORMATION, + SUMMARY, } export function AppointmentForm() { - const [selectedTab, setSelectedTab] = useState(0); + const [activeFormSection, setActiveFormSection] = useState(FormSection.AVAILABLE_APPOINTMENTS); // @todo revert to District const initialValues: FormValues = { district: '', name: '', + date: '' as Iso8601, + time: '' as Iso8601, }; - const handleSubmit = (values: FormValues) => { - console.log(`Form submitted:`, values); + const districtSchema = { + district: Yup.string().required('Bitte Stadtteil wählen.'), + }; + const personalInformationSchema = { + name: Yup.string().required('Bitte Name eingeben.'), + }; + const availableAppointmentsSchema = { + date: Yup.string().required('Bitte Datum eingeben.'), + }; + + const validationSchema = Yup.object().shape({ + ...(activeFormSection === FormSection.DISTRICT ? districtSchema : {}), + ...(activeFormSection === FormSection.PERSONAL_INFORMATION ? personalInformationSchema : {}), + ...(activeFormSection === FormSection.AVAILABLE_APPOINTMENTS ? availableAppointmentsSchema : {}), + }); + + const handleSubmit = () => { + setActiveFormSection(activeFormSection + 1); //@todo }; return ( @@ -46,14 +69,15 @@ export function AppointmentForm() { _label: 'Zusammenfassung', }, ]} - _selected={selectedTab} - _on={{ onSelect: (_event, selectedTab) => setSelectedTab(selectedTab) }} + _label="Formular-Navigation" + _selected={activeFormSection} + _on={{ onSelect: (_event, selectedTab) => setActiveFormSection(selectedTab) }} >
- setSelectedTab(1)} /> +
- +
diff --git a/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx new file mode 100644 index 0000000000..a40493eef6 --- /dev/null +++ b/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx @@ -0,0 +1,108 @@ +import React, { useEffect, useState } from 'react'; +import { KolButton, KolForm, KolHeading, KolInputDate, KolInputRadio } from '@public-ui/react'; +import { FormValues } from './AppointmentForm'; +import { ErrorList } from './ErrorList'; +import { Field, FieldProps, useFormikContext } from 'formik'; +import { fetchAvailableTimes } from './appointmentService'; +import { Option } from '@public-ui/components/src'; + +export function AvailableAppointmentsForm() { + const form = useFormikContext(); + + const [sectionSubmitted, setSectionSubmitted] = useState(false); + const [availableTimes, setAvailableTimes] = useState[] | null>(null); + + useEffect(() => { + let ignoreResponse = false; + setAvailableTimes(null); + + if (form.values.date) { + fetchAvailableTimes().then( + (times) => { + if (!ignoreResponse) { + setAvailableTimes(times); + } + }, + () => {}, + ); // ignore errors + } + return () => { + ignoreResponse = true; + }; + }, [form.values.date]); + + return ( +
+ + + {sectionSubmitted && Object.keys(form.errors).length ? ( +
+ +
+ ) : null} + + { + void form.submitForm(); + setSectionSubmitted(true); + }, + }} + > + + {({ field }: FieldProps) => ( + { + if (event.target) { + void form.setFieldValue('date', value, true); + } + }, + onBlur: () => { + void form.setFieldTouched('date', true); + }, + }} + /> + )} + + + {form.values.date && ( +
+ {availableTimes ? ( + + {({ field }: FieldProps) => ( + { + if (event.target) { + void form.setFieldTouched('time', true); + void form.setFieldValue('time', value, true); + } + }, + }} + /> + )} + + ) : ( + 'Loading' + )} +
+ )} + + +
+
+ ); +} diff --git a/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx b/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx index 4dfcd8d9ab..e609942fd2 100644 --- a/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx @@ -1,8 +1,8 @@ -import React from 'react'; +import React, { useState } from 'react'; import { KolButton, KolForm, KolHeading, KolSelect } from '@public-ui/react'; import { Field, FieldProps, useFormikContext } from 'formik'; import { FormValues } from './AppointmentForm'; -import { EventCallback } from '@public-ui/components/src/types/callbacks'; +import { ErrorList } from './ErrorList'; const LOCATION_OPTIONS = [ { @@ -27,37 +27,47 @@ const LOCATION_OPTIONS = [ }, ]; -export type DistrictFormProps = { - onSubmit: EventCallback; -}; -export function DistrictForm({ onSubmit }: DistrictFormProps) { +export function DistrictForm() { const form = useFormikContext(); + const [sectionSubmitted, setSectionSubmitted] = useState(false); return (
- is validating? {String(form.isValidating)} - + + {sectionSubmitted && Object.keys(form.errors).length ? ( +
+ +
+ ) : null} + + { + void form.submitForm(); + setSectionSubmitted(true); + }, + }} + > {({ field }: FieldProps) => ( - <> - { - if (event.target) { - const [value] = values as [FormValues['district']]; - void form.setFieldValue('district', value); - void form.setTouched({ district: true }); - } - }, - }} - _error={form.errors.district} - _touched={form.touched.district} - /> - + { + if (event.target) { + const [value] = values as [FormValues['district']]; + void form.setFieldTouched('district', true); + void form.setFieldValue('district', value, true); + } + }, + }} + /> )} diff --git a/packages/samples/react/src/scenarios/appointment-form/ErrorList.tsx b/packages/samples/react/src/scenarios/appointment-form/ErrorList.tsx new file mode 100644 index 0000000000..de24115c2c --- /dev/null +++ b/packages/samples/react/src/scenarios/appointment-form/ErrorList.tsx @@ -0,0 +1,35 @@ +import React, { PointerEvent } from 'react'; +import { KolAlert, KolLink } from '@public-ui/react'; + +export type ErrorListPropType = { + errors: Record; +}; + +export function ErrorList({ errors }: ErrorListPropType) { + const handleLinkClick = (event: PointerEvent) => { + const href = (event.target as HTMLAnchorElement | undefined)?.href; + if (href) { + const hrefUrl = new URL(href); + + const targetElement = document.querySelector(hrefUrl.hash); + if (targetElement && typeof targetElement.focus === 'function') { + targetElement.focus(); + } + } + }; + + return ( + + Bitte korrigieren Sie folgende Fehler: + + + ); +} diff --git a/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx b/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx index 500285bf23..0e87629eb2 100644 --- a/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx @@ -1,19 +1,50 @@ import React from 'react'; -import { KolHeading, KolInputText } from '@public-ui/react'; -import { Field, FieldProps, Form, withFormik } from 'formik'; -import { FormProps, FormValues } from './AppointmentForm'; +import { KolButton, KolForm, KolHeading, KolInputText } from '@public-ui/react'; +import { Field, FieldProps, useFormikContext } from 'formik'; +import { FormValues } from './AppointmentForm'; export function PersonalInformationForm() { + const form = useFormikContext(); + return ( - <> +
-
+
    + {Object.entries(form.errors).map(([field, error]) => ( +
  • {error}
  • + ))} +
+ { + void form.submitForm(); + }, + }} + > - {({ field, form, meta }: FieldProps) => ( - + {({ field }: FieldProps) => ( + <> + { + if (event.target) { + const [value] = values as [FormValues['name']]; + void form.setFieldTouched('name', true); + void form.setFieldValue('name', value, true); + } + }, + }} + /> + )} - - + + +
+
); } diff --git a/packages/samples/react/src/scenarios/appointment-form/appointmentService.ts b/packages/samples/react/src/scenarios/appointment-form/appointmentService.ts new file mode 100644 index 0000000000..8ad6e2e6f6 --- /dev/null +++ b/packages/samples/react/src/scenarios/appointment-form/appointmentService.ts @@ -0,0 +1,32 @@ +import { Option } from '@public-ui/components'; + +const getRandomIntInclusive = (min: number, max: number) => { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1) + min); +}; + +const padHours = (hours: number): string => `${hours < 10 ? '0' : ''}${hours}`; + +const getRandomTimes = () => { + const earliest = 8; + const latest = 17; + const amount = getRandomIntInclusive(2, 9); + + const times = new Set(); + while (times.size !== amount) { + times.add(getRandomIntInclusive(earliest, latest)); + } + return [...times].sort((timeA, timeB) => timeA - timeB).map((hours) => `${padHours(hours)}:00`); +}; + +const sleep = (timeout: number) => { + return new Promise((resolve) => setTimeout(resolve, timeout)); +}; +export const fetchAvailableTimes = async (): Promise[]> => { + await sleep(1000); + return getRandomTimes().map((time) => ({ + label: time, + value: time, + })); +}; From 2a55093397cfc441ff47ddc7bdea8bb50b4b0200 Mon Sep 17 00:00:00 2001 From: Stefan Dietz Date: Thu, 5 Oct 2023 10:26:47 +0200 Subject: [PATCH 05/20] Fix type issues --- .../src/scenarios/appointment-form/AppointmentForm.tsx | 2 +- .../react/src/scenarios/appointment-form/ErrorList.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx index e74b98f41f..79cd84796e 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx @@ -44,8 +44,8 @@ export function AppointmentForm() { const validationSchema = Yup.object().shape({ ...(activeFormSection === FormSection.DISTRICT ? districtSchema : {}), - ...(activeFormSection === FormSection.PERSONAL_INFORMATION ? personalInformationSchema : {}), ...(activeFormSection === FormSection.AVAILABLE_APPOINTMENTS ? availableAppointmentsSchema : {}), + ...(activeFormSection === FormSection.PERSONAL_INFORMATION ? personalInformationSchema : {}), }); const handleSubmit = () => { diff --git a/packages/samples/react/src/scenarios/appointment-form/ErrorList.tsx b/packages/samples/react/src/scenarios/appointment-form/ErrorList.tsx index de24115c2c..6e7ecb314c 100644 --- a/packages/samples/react/src/scenarios/appointment-form/ErrorList.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/ErrorList.tsx @@ -1,4 +1,4 @@ -import React, { PointerEvent } from 'react'; +import React from 'react'; import { KolAlert, KolLink } from '@public-ui/react'; export type ErrorListPropType = { @@ -6,12 +6,12 @@ export type ErrorListPropType = { }; export function ErrorList({ errors }: ErrorListPropType) { - const handleLinkClick = (event: PointerEvent) => { + const handleLinkClick = (event: Event) => { const href = (event.target as HTMLAnchorElement | undefined)?.href; if (href) { const hrefUrl = new URL(href); - const targetElement = document.querySelector(hrefUrl.hash); + const targetElement = document.querySelector(hrefUrl.hash); if (targetElement && typeof targetElement.focus === 'function') { targetElement.focus(); } From 26de4b34ca19e6b407d4771dfc5732e1954110a2 Mon Sep 17 00:00:00 2001 From: Stefan Dietz Date: Thu, 5 Oct 2023 13:02:27 +0200 Subject: [PATCH 06/20] Add async validation for individual appointments --- .../appointment-form/AppointmentForm.tsx | 5 +- .../AvailableAppointmentsForm.tsx | 55 +++++++++++-------- .../appointment-form/appointmentService.ts | 7 ++- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx index 79cd84796e..65fb9c281c 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx @@ -6,7 +6,7 @@ import { PersonalInformationForm } from './PersonalInformationForm'; import { Formik } from 'formik'; import * as Yup from 'yup'; import { AvailableAppointmentsForm } from './AvailableAppointmentsForm'; -import { Iso8601 } from '@public-ui/components'; +import { Iso8601 } from '@public-ui/components'import { checkAppointmentAvailability } from './appointmentService'; export interface FormProps {} export interface FormValues { @@ -40,6 +40,9 @@ export function AppointmentForm() { }; const availableAppointmentsSchema = { date: Yup.string().required('Bitte Datum eingeben.'), + time: Yup.string().test('checkTimeAvailability', 'Termin leider nicht mehr verfügbar.', async (time?: string) => { + return await checkAppointmentAvailability(time); + }), }; const validationSchema = Yup.object().shape({ diff --git a/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx index a40493eef6..96d20ed1af 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { KolButton, KolForm, KolHeading, KolInputDate, KolInputRadio } from '@public-ui/react'; +import { KolButton, KolForm, KolHeading, KolInputDate, KolInputRadio, KolSpin } from '@public-ui/react'; import { FormValues } from './AppointmentForm'; import { ErrorList } from './ErrorList'; import { Field, FieldProps, useFormikContext } from 'formik'; @@ -21,6 +21,7 @@ export function AvailableAppointmentsForm() { (times) => { if (!ignoreResponse) { setAvailableTimes(times); + void form.setFieldValue('time', times[0].value); } }, () => {}, @@ -74,34 +75,40 @@ export function AvailableAppointmentsForm() { {form.values.date && (
{availableTimes ? ( - - {({ field }: FieldProps) => ( - { - if (event.target) { - void form.setFieldTouched('time', true); - void form.setFieldValue('time', value, true); - } - }, - }} - /> - )} - + <> + + {({ field }: FieldProps) => ( + { + if (event.target) { + void form.setFieldTouched('time', true); + void form.setFieldValue('time', value, true); + } + }, + }} + /> + )} + +

+ Aus Testzwecken sind nur die Termine zu jeder halben Stunde verfügbar. +

+ ) : ( - 'Loading' + )}
)} - + + {form.values.date && form.isValidating ? : ''}
); diff --git a/packages/samples/react/src/scenarios/appointment-form/appointmentService.ts b/packages/samples/react/src/scenarios/appointment-form/appointmentService.ts index 8ad6e2e6f6..afd412c3ac 100644 --- a/packages/samples/react/src/scenarios/appointment-form/appointmentService.ts +++ b/packages/samples/react/src/scenarios/appointment-form/appointmentService.ts @@ -17,7 +17,7 @@ const getRandomTimes = () => { while (times.size !== amount) { times.add(getRandomIntInclusive(earliest, latest)); } - return [...times].sort((timeA, timeB) => timeA - timeB).map((hours) => `${padHours(hours)}:00`); + return [...times].sort((timeA, timeB) => timeA - timeB).flatMap((hours) => [`${padHours(hours)}:00`, `${padHours(hours)}:30`]); }; const sleep = (timeout: number) => { @@ -30,3 +30,8 @@ export const fetchAvailableTimes = async (): Promise[]> => { value: time, })); }; + +export const checkAppointmentAvailability = async (time?: string): Promise => { + await sleep(500); + return time?.endsWith(':30') ?? false; +}; From 914154788a10f1ff9c0c97c28df91b51b313eb87 Mon Sep 17 00:00:00 2001 From: Stefan Dietz Date: Thu, 5 Oct 2023 13:02:27 +0200 Subject: [PATCH 07/20] Add async validation for individual appointments --- .../appointment-form/AppointmentForm.tsx | 5 +- .../AvailableAppointmentsForm.tsx | 55 +++++++++++-------- .../appointment-form/appointmentService.ts | 7 ++- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx index 79cd84796e..65fb9c281c 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx @@ -6,7 +6,7 @@ import { PersonalInformationForm } from './PersonalInformationForm'; import { Formik } from 'formik'; import * as Yup from 'yup'; import { AvailableAppointmentsForm } from './AvailableAppointmentsForm'; -import { Iso8601 } from '@public-ui/components'; +import { Iso8601 } from '@public-ui/components'import { checkAppointmentAvailability } from './appointmentService'; export interface FormProps {} export interface FormValues { @@ -40,6 +40,9 @@ export function AppointmentForm() { }; const availableAppointmentsSchema = { date: Yup.string().required('Bitte Datum eingeben.'), + time: Yup.string().test('checkTimeAvailability', 'Termin leider nicht mehr verfügbar.', async (time?: string) => { + return await checkAppointmentAvailability(time); + }), }; const validationSchema = Yup.object().shape({ diff --git a/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx index a40493eef6..96d20ed1af 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { KolButton, KolForm, KolHeading, KolInputDate, KolInputRadio } from '@public-ui/react'; +import { KolButton, KolForm, KolHeading, KolInputDate, KolInputRadio, KolSpin } from '@public-ui/react'; import { FormValues } from './AppointmentForm'; import { ErrorList } from './ErrorList'; import { Field, FieldProps, useFormikContext } from 'formik'; @@ -21,6 +21,7 @@ export function AvailableAppointmentsForm() { (times) => { if (!ignoreResponse) { setAvailableTimes(times); + void form.setFieldValue('time', times[0].value); } }, () => {}, @@ -74,34 +75,40 @@ export function AvailableAppointmentsForm() { {form.values.date && (
{availableTimes ? ( - - {({ field }: FieldProps) => ( - { - if (event.target) { - void form.setFieldTouched('time', true); - void form.setFieldValue('time', value, true); - } - }, - }} - /> - )} - + <> + + {({ field }: FieldProps) => ( + { + if (event.target) { + void form.setFieldTouched('time', true); + void form.setFieldValue('time', value, true); + } + }, + }} + /> + )} + +

+ Aus Testzwecken sind nur die Termine zu jeder halben Stunde verfügbar. +

+ ) : ( - 'Loading' + )}
)} - + + {form.values.date && form.isValidating ? : ''}
); diff --git a/packages/samples/react/src/scenarios/appointment-form/appointmentService.ts b/packages/samples/react/src/scenarios/appointment-form/appointmentService.ts index 8ad6e2e6f6..afd412c3ac 100644 --- a/packages/samples/react/src/scenarios/appointment-form/appointmentService.ts +++ b/packages/samples/react/src/scenarios/appointment-form/appointmentService.ts @@ -17,7 +17,7 @@ const getRandomTimes = () => { while (times.size !== amount) { times.add(getRandomIntInclusive(earliest, latest)); } - return [...times].sort((timeA, timeB) => timeA - timeB).map((hours) => `${padHours(hours)}:00`); + return [...times].sort((timeA, timeB) => timeA - timeB).flatMap((hours) => [`${padHours(hours)}:00`, `${padHours(hours)}:30`]); }; const sleep = (timeout: number) => { @@ -30,3 +30,8 @@ export const fetchAvailableTimes = async (): Promise[]> => { value: time, })); }; + +export const checkAppointmentAvailability = async (time?: string): Promise => { + await sleep(500); + return time?.endsWith(':30') ?? false; +}; From e11087fbaf4b9933327da4f425cce895def413b1 Mon Sep 17 00:00:00 2001 From: Stefan Dietz Date: Thu, 5 Oct 2023 13:16:20 +0200 Subject: [PATCH 08/20] Add async validation for individual appointments --- .../react/src/scenarios/appointment-form/AppointmentForm.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx index 65fb9c281c..431f12eb6e 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx @@ -6,7 +6,8 @@ import { PersonalInformationForm } from './PersonalInformationForm'; import { Formik } from 'formik'; import * as Yup from 'yup'; import { AvailableAppointmentsForm } from './AvailableAppointmentsForm'; -import { Iso8601 } from '@public-ui/components'import { checkAppointmentAvailability } from './appointmentService'; +import { Iso8601 } from '@public-ui/components'; +import { checkAppointmentAvailability } from './appointmentService'; export interface FormProps {} export interface FormValues { From fa240bd9b1cdee8cd33e5e1a7ea223fa4b9781ac Mon Sep 17 00:00:00 2001 From: Stefan Dietz Date: Thu, 5 Oct 2023 18:00:38 +0200 Subject: [PATCH 09/20] Add conditional field with conditional validation --- .../appointment-form/AppointmentForm.tsx | 15 +++- .../PersonalInformationForm.tsx | 80 ++++++++++++++++--- 2 files changed, 83 insertions(+), 12 deletions(-) diff --git a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx index 431f12eb6e..0bb97fd266 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx @@ -12,9 +12,11 @@ import { checkAppointmentAvailability } from './appointmentService'; export interface FormProps {} export interface FormValues { district: string; - name: string; date: Iso8601; time: Iso8601; + salutation: string; + name: string; + company: string; } enum FormSection { @@ -25,19 +27,26 @@ enum FormSection { } export function AppointmentForm() { - const [activeFormSection, setActiveFormSection] = useState(FormSection.AVAILABLE_APPOINTMENTS); // @todo revert to District + const [activeFormSection, setActiveFormSection] = useState(FormSection.PERSONAL_INFORMATION); // @todo revert to District const initialValues: FormValues = { district: '', - name: '', date: '' as Iso8601, time: '' as Iso8601, + salutation: '', + name: '', + company: '', }; const districtSchema = { district: Yup.string().required('Bitte Stadtteil wählen.'), }; const personalInformationSchema = { + salutation: Yup.string().required('Bitte Anrede auswählen.'), name: Yup.string().required('Bitte Name eingeben.'), + company: Yup.string().when('salutation', { + is: (salutation: string) => salutation === 'Firma', + then: (schema) => schema.required('Bitte Firmenname angeben.'), + }), }; const availableAppointmentsSchema = { date: Yup.string().required('Bitte Datum eingeben.'), diff --git a/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx b/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx index 0e87629eb2..531aa6cd3e 100644 --- a/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx @@ -1,26 +1,89 @@ -import React from 'react'; -import { KolButton, KolForm, KolHeading, KolInputText } from '@public-ui/react'; +import React, { useState } from 'react'; +import { KolButton, KolForm, KolHeading, KolInputText, KolSelect } from '@public-ui/react'; import { Field, FieldProps, useFormikContext } from 'formik'; import { FormValues } from './AppointmentForm'; +const SALUTATION_OPTIONS = [ + { + value: 'Firma', + label: 'Firma', + }, + { + value: 'Frau', + label: 'Frau', + }, + { + value: 'Herr', + label: 'Herr', + }, + { + value: 'Hallo', + label: 'Hallo', + }, +]; + export function PersonalInformationForm() { const form = useFormikContext(); + const [sectionSubmitted, setSectionSubmitted] = useState(false); return (
-
    - {Object.entries(form.errors).map(([field, error]) => ( -
  • {error}
  • - ))} -
+
    {sectionSubmitted && Object.entries(form.errors).map(([field, error]) =>
  • {error}
  • )}
{ void form.submitForm(); + setSectionSubmitted(true); }, }} > + + {({ field }: FieldProps) => ( + <> + { + if (event.target) { + const [value] = values as [FormValues['salutation']]; + void form.setFieldTouched('salutation', true); + void form.setFieldValue('salutation', value, true); + } + }, + }} + /> + + )} + + + {form.values.salutation === 'Firma' && ( + + {({ field }: FieldProps) => ( + <> + { + if (event.target) { + void form.setFieldTouched('company', true); + void form.setFieldValue('company', value, true); + } + }, + }} + /> + + )} + + )} + {({ field }: FieldProps) => ( <> @@ -30,9 +93,8 @@ export function PersonalInformationForm() { _error={form.errors.name} _touched={form.touched.name} _on={{ - onChange: (event, values: unknown) => { + onChange: (event, value: unknown) => { if (event.target) { - const [value] = values as [FormValues['name']]; void form.setFieldTouched('name', true); void form.setFieldValue('name', value, true); } From b419e54208b4ef33d9c4de52a618bc377ec5f129 Mon Sep 17 00:00:00 2001 From: Stefan Dietz Date: Fri, 6 Oct 2023 09:57:12 +0200 Subject: [PATCH 10/20] Polishing --- .../appointment-form/AppointmentForm.tsx | 10 +++- .../AvailableAppointmentsForm.tsx | 2 + .../appointment-form/DistrictForm.tsx | 1 + .../PersonalInformationForm.tsx | 57 +++++++++++++++++-- 4 files changed, 63 insertions(+), 7 deletions(-) diff --git a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx index 0bb97fd266..c1cdc03630 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx @@ -17,6 +17,8 @@ export interface FormValues { salutation: string; name: string; company: string; + email: string; + phone: string; } enum FormSection { @@ -35,6 +37,8 @@ export function AppointmentForm() { salutation: '', name: '', company: '', + email: '', + phone: '', }; const districtSchema = { @@ -47,11 +51,13 @@ export function AppointmentForm() { is: (salutation: string) => salutation === 'Firma', then: (schema) => schema.required('Bitte Firmenname angeben.'), }), + email: Yup.string().required('Bitte E-Mail-Adresse eingeben.'), }; const availableAppointmentsSchema = { date: Yup.string().required('Bitte Datum eingeben.'), - time: Yup.string().test('checkTimeAvailability', 'Termin leider nicht mehr verfügbar.', async (time?: string) => { - return await checkAppointmentAvailability(time); + time: Yup.string().when('date', { + is: (date: string) => Boolean(date), // only validate time when date is already set + then: (schema) => schema.test('checkTimeAvailability', 'Termin leider nicht mehr verfügbar.', checkAppointmentAvailability), }), }; diff --git a/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx index 96d20ed1af..d4985353d9 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx @@ -58,6 +58,7 @@ export function AvailableAppointmentsForm() { _value={field.value} _error={form.errors.date} _touched={form.touched.date} + _required _on={{ onChange: (event: Event, value: unknown): void => { if (event.target) { @@ -86,6 +87,7 @@ export function AvailableAppointmentsForm() { _value={field.value} _error={form.errors.time} _touched={form.touched.time} + _required _on={{ onChange: (event: Event, value: unknown): void => { if (event.target) { diff --git a/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx b/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx index e609942fd2..2ec2110e0f 100644 --- a/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx @@ -58,6 +58,7 @@ export function DistrictForm() { _value={[field.value]} _error={form.errors.district} _touched={form.touched.district} + _required _on={{ onChange: (event, values: unknown) => { if (event.target) { diff --git a/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx b/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx index 531aa6cd3e..d10af1ed3e 100644 --- a/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { KolButton, KolForm, KolHeading, KolInputText, KolSelect } from '@public-ui/react'; +import { KolButton, KolForm, KolHeading, KolInputEmail, KolInputText, KolSelect } from '@public-ui/react'; import { Field, FieldProps, useFormikContext } from 'formik'; import { FormValues } from './AppointmentForm'; @@ -47,6 +47,7 @@ export function PersonalInformationForm() { _error={form.errors.salutation} _touched={form.touched.salutation} _options={[{ label: 'Bitte wählen…', value: '' }, ...SALUTATION_OPTIONS]} + _required _on={{ onChange: (event, values: unknown) => { if (event.target) { @@ -64,12 +65,13 @@ export function PersonalInformationForm() { {form.values.salutation === 'Firma' && ( {({ field }: FieldProps) => ( - <> +
{ if (event.target) { @@ -79,19 +81,20 @@ export function PersonalInformationForm() { }, }} /> - +
)}
)} {({ field }: FieldProps) => ( - <> +
{ if (event.target) { @@ -101,7 +104,51 @@ export function PersonalInformationForm() { }, }} /> - +
+ )} +
+ + + {({ field }: FieldProps) => ( +
+ { + if (event.target) { + void form.setFieldTouched('email', true); + void form.setFieldValue('email', value, true); + } + }, + }} + /> +
+ )} +
+ + + {({ field }: FieldProps) => ( +
+ { + if (event.target) { + void form.setFieldTouched('phone', true); + void form.setFieldValue('phone', value, true); + } + }, + }} + /> +
)}
From d3ef5ac1ad8fd8259fa680b715040aa0cc1cdee3 Mon Sep 17 00:00:00 2001 From: Stefan Dietz Date: Fri, 6 Oct 2023 14:04:05 +0200 Subject: [PATCH 11/20] Add summary --- .../scenarios/appointment-form/Summary.tsx | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/packages/samples/react/src/scenarios/appointment-form/Summary.tsx b/packages/samples/react/src/scenarios/appointment-form/Summary.tsx index 8ab812269b..2358b803a6 100644 --- a/packages/samples/react/src/scenarios/appointment-form/Summary.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/Summary.tsx @@ -1,14 +1,55 @@ import React from 'react'; import { useFormikContext } from 'formik'; import { KolHeading } from '@public-ui/react'; +import { FormValues } from './AppointmentForm'; + +const ValueFallback = () => Nicht angegeben; +const ValueWithFallback = ({ value }: { value: string }) => (value ? value : ); export function Summary() { - const { values } = useFormikContext(); + const { values } = useFormikContext(); return ( <> -
{JSON.stringify(values, null, 2)}
+ +
+
Stadtteil
+
+ +
+
Termin
+
{values.date && values.time ? `${values.date} ${values.time} Uhr` : }
+ + {values.salutation === 'Firma' ? ( + <> +
Firma
+
+ +
+ + ) : ( + <> +
Anrede
+
+ +
+ + )} + +
Name
+
+ +
+
E-Mail
+
+ +
+
Telefon
+
+ +
+
); } From 4d247b26dc7de9c4d11ae2860bf3bf51947046fc Mon Sep 17 00:00:00 2001 From: Stefan Dietz Date: Fri, 6 Oct 2023 14:46:43 +0200 Subject: [PATCH 12/20] Fix minor validation bug --- .../react/src/scenarios/appointment-form/AppointmentForm.tsx | 2 +- .../scenarios/appointment-form/AvailableAppointmentsForm.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx index c1cdc03630..8edd6990df 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx @@ -29,7 +29,7 @@ enum FormSection { } export function AppointmentForm() { - const [activeFormSection, setActiveFormSection] = useState(FormSection.PERSONAL_INFORMATION); // @todo revert to District + const [activeFormSection, setActiveFormSection] = useState(FormSection.DISTRICT); const initialValues: FormValues = { district: '', date: '' as Iso8601, diff --git a/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx index d4985353d9..37b68ad1e1 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx @@ -22,6 +22,7 @@ export function AvailableAppointmentsForm() { if (!ignoreResponse) { setAvailableTimes(times); void form.setFieldValue('time', times[0].value); + void form.setFieldTouched('time'); } }, () => {}, From d7c71fdfe5136a4213d38dc07518ffabfc041e9a Mon Sep 17 00:00:00 2001 From: Stefan Dietz Date: Fri, 6 Oct 2023 15:08:10 +0200 Subject: [PATCH 13/20] Restructure component and fix setActiveFormSection --- .../appointment-form/AppointmentForm.tsx | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx index 8edd6990df..624f8302a2 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx @@ -28,38 +28,41 @@ enum FormSection { SUMMARY, } +const formSectionSequence = [FormSection.DISTRICT, FormSection.AVAILABLE_APPOINTMENTS, FormSection.PERSONAL_INFORMATION, FormSection.SUMMARY] as const; + +const initialValues: FormValues = { + district: '', + date: '' as Iso8601, + time: '' as Iso8601, + salutation: '', + name: '', + company: '', + email: '', + phone: '', +}; + +const districtSchema = { + district: Yup.string().required('Bitte Stadtteil wählen.'), +}; +const personalInformationSchema = { + salutation: Yup.string().required('Bitte Anrede auswählen.'), + name: Yup.string().required('Bitte Name eingeben.'), + company: Yup.string().when('salutation', { + is: (salutation: string) => salutation === 'Firma', + then: (schema) => schema.required('Bitte Firmenname angeben.'), + }), + email: Yup.string().required('Bitte E-Mail-Adresse eingeben.'), +}; +const availableAppointmentsSchema = { + date: Yup.string().required('Bitte Datum eingeben.'), + time: Yup.string().when('date', { + is: (date: string) => Boolean(date), // only validate time when date is already set + then: (schema) => schema.test('checkTimeAvailability', 'Termin leider nicht mehr verfügbar.', checkAppointmentAvailability), + }), +}; + export function AppointmentForm() { const [activeFormSection, setActiveFormSection] = useState(FormSection.DISTRICT); - const initialValues: FormValues = { - district: '', - date: '' as Iso8601, - time: '' as Iso8601, - salutation: '', - name: '', - company: '', - email: '', - phone: '', - }; - - const districtSchema = { - district: Yup.string().required('Bitte Stadtteil wählen.'), - }; - const personalInformationSchema = { - salutation: Yup.string().required('Bitte Anrede auswählen.'), - name: Yup.string().required('Bitte Name eingeben.'), - company: Yup.string().when('salutation', { - is: (salutation: string) => salutation === 'Firma', - then: (schema) => schema.required('Bitte Firmenname angeben.'), - }), - email: Yup.string().required('Bitte E-Mail-Adresse eingeben.'), - }; - const availableAppointmentsSchema = { - date: Yup.string().required('Bitte Datum eingeben.'), - time: Yup.string().when('date', { - is: (date: string) => Boolean(date), // only validate time when date is already set - then: (schema) => schema.test('checkTimeAvailability', 'Termin leider nicht mehr verfügbar.', checkAppointmentAvailability), - }), - }; const validationSchema = Yup.object().shape({ ...(activeFormSection === FormSection.DISTRICT ? districtSchema : {}), @@ -68,7 +71,11 @@ export function AppointmentForm() { }); const handleSubmit = () => { - setActiveFormSection(activeFormSection + 1); //@todo + const currentSectionIndex = formSectionSequence.indexOf(activeFormSection); + const nextSection = formSectionSequence[currentSectionIndex + 1]; + if (nextSection) { + setActiveFormSection(nextSection); + } }; return ( From d70003e083288e36ec2983a6f0db2db0eb8c6fed Mon Sep 17 00:00:00 2001 From: Stefan Dietz Date: Sun, 8 Oct 2023 15:11:55 +0200 Subject: [PATCH 14/20] Revert/replace prop validators bugfix --- packages/components/src/utils/prop.validators.ts | 2 +- .../appointment-form/AvailableAppointmentsForm.tsx | 4 ++-- .../src/scenarios/appointment-form/DistrictForm.tsx | 2 +- .../appointment-form/PersonalInformationForm.tsx | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/components/src/utils/prop.validators.ts b/packages/components/src/utils/prop.validators.ts index 341e9ebe35..631bba1042 100644 --- a/packages/components/src/utils/prop.validators.ts +++ b/packages/components/src/utils/prop.validators.ts @@ -99,7 +99,7 @@ export const setState = (component: Generic.Element.Component, propName: stri if (component.nextState === undefined) { component.nextState = new Map(); } - if (component.nextState.get(propName) !== value || value === undefined) { + if (component.nextState.get(propName) !== value) { const nextHooks = component.nextHooks.get(propName); if (nextHooks instanceof Map === false) { component.nextHooks.set(propName, new Map()); diff --git a/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx index 37b68ad1e1..a87bccc8b6 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AvailableAppointmentsForm.tsx @@ -57,7 +57,7 @@ export function AvailableAppointmentsForm() { id="field-date" _label="Datum" _value={field.value} - _error={form.errors.date} + _error={form.errors.date || ''} _touched={form.touched.date} _required _on={{ @@ -86,7 +86,7 @@ export function AvailableAppointmentsForm() { _orientation="horizontal" _options={availableTimes} _value={field.value} - _error={form.errors.time} + _error={form.errors.time || ''} _touched={form.touched.time} _required _on={{ diff --git a/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx b/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx index 2ec2110e0f..ea429af07d 100644 --- a/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx @@ -56,7 +56,7 @@ export function DistrictForm() { _label="Stadtteil" _options={[{ label: 'Bitte wählen…', value: '' }, ...LOCATION_OPTIONS]} _value={[field.value]} - _error={form.errors.district} + _error={form.errors.district || ''} _touched={form.touched.district} _required _on={{ diff --git a/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx b/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx index d10af1ed3e..2abd6f9cb0 100644 --- a/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx @@ -44,7 +44,7 @@ export function PersonalInformationForm() { { From b543bcf2e42496c9dd9b5d21406ae0c3633a34d8 Mon Sep 17 00:00:00 2001 From: Stefan Dietz Date: Sun, 8 Oct 2023 15:41:35 +0200 Subject: [PATCH 15/20] Reset touched fields on form submit --- .../src/scenarios/appointment-form/AppointmentForm.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx index 624f8302a2..f09c8e8dfe 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx @@ -3,7 +3,7 @@ import { KolTabs } from '@public-ui/react'; import { DistrictForm } from './DistrictForm'; import { Summary } from './Summary'; import { PersonalInformationForm } from './PersonalInformationForm'; -import { Formik } from 'formik'; +import { Formik, FormikHelpers } from 'formik'; import * as Yup from 'yup'; import { AvailableAppointmentsForm } from './AvailableAppointmentsForm'; import { Iso8601 } from '@public-ui/components'; @@ -70,10 +70,11 @@ export function AppointmentForm() { ...(activeFormSection === FormSection.PERSONAL_INFORMATION ? personalInformationSchema : {}), }); - const handleSubmit = () => { + const handleSubmit = async (_values: FormValues, formik: FormikHelpers) => { const currentSectionIndex = formSectionSequence.indexOf(activeFormSection); const nextSection = formSectionSequence[currentSectionIndex + 1]; - if (nextSection) { + if (nextSection !== undefined) { + await formik.setTouched({}); setActiveFormSection(nextSection); } }; From 17fa9e67445805da257ff1d6317e17e19631cbdc Mon Sep 17 00:00:00 2001 From: Stefan Dietz Date: Sun, 8 Oct 2023 15:57:47 +0200 Subject: [PATCH 16/20] Remove stray tab character --- packages/samples/react/src/shares/routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/samples/react/src/shares/routes.ts b/packages/samples/react/src/shares/routes.ts index 19b74a2f33..4c8d0cebba 100644 --- a/packages/samples/react/src/shares/routes.ts +++ b/packages/samples/react/src/shares/routes.ts @@ -46,7 +46,7 @@ import { TEXTAREA_ROUTES } from '../components/textarea/routes'; import { TOAST_ROUTES } from '../components/toast/routes'; import { VERSION_ROUTES } from '../components/version/routes'; import { SCENARIO_ROUTES } from '../scenarios/routes'; -import { Routes } from './typ es'; +import { Routes } from './types'; export const ROUTES: Routes = { ...HANDOUT_ROUTES, From 86a37fbc440db5ee0aae0ff3f3043c922ee5134a Mon Sep 17 00:00:00 2001 From: Martin Oppitz Date: Tue, 17 Oct 2023 08:22:51 +0200 Subject: [PATCH 17/20] fix: unused --- packages/samples/react/.ts-prunerc.js | 2 +- .../react/src/scenarios/appointment-form/AppointmentForm.tsx | 2 +- .../samples/react/src/scenarios/appointment-form/ErrorList.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/samples/react/.ts-prunerc.js b/packages/samples/react/.ts-prunerc.js index 9eb0a6acb1..e34042f3d3 100644 --- a/packages/samples/react/.ts-prunerc.js +++ b/packages/samples/react/.ts-prunerc.js @@ -1,4 +1,4 @@ // https://github.com/nadeesha/ts-prune https: module.exports = { - ignore: 'node_modules', + ignore: 'node_modules|complex-form', }; diff --git a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx index f09c8e8dfe..1c9d032450 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx @@ -9,7 +9,7 @@ import { AvailableAppointmentsForm } from './AvailableAppointmentsForm'; import { Iso8601 } from '@public-ui/components'; import { checkAppointmentAvailability } from './appointmentService'; -export interface FormProps {} +// export interface FormProps {} export interface FormValues { district: string; date: Iso8601; diff --git a/packages/samples/react/src/scenarios/appointment-form/ErrorList.tsx b/packages/samples/react/src/scenarios/appointment-form/ErrorList.tsx index 6e7ecb314c..a10d1804aa 100644 --- a/packages/samples/react/src/scenarios/appointment-form/ErrorList.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/ErrorList.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { KolAlert, KolLink } from '@public-ui/react'; -export type ErrorListPropType = { +type ErrorListPropType = { errors: Record; }; From aac10733ebb8e4af6ba450352bef02391314a062 Mon Sep 17 00:00:00 2001 From: Martin Oppitz Date: Tue, 17 Oct 2023 10:04:03 +0200 Subject: [PATCH 18/20] fix: format --- .../src/scenarios/complex-form/schedule/schedule.form.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/samples/react/src/scenarios/complex-form/schedule/schedule.form.ts b/packages/samples/react/src/scenarios/complex-form/schedule/schedule.form.ts index 0dff8872fe..6476b791f5 100644 --- a/packages/samples/react/src/scenarios/complex-form/schedule/schedule.form.ts +++ b/packages/samples/react/src/scenarios/complex-form/schedule/schedule.form.ts @@ -13,14 +13,14 @@ export class ScheduleForm extends FormControl { new InputControl('schedule', { label: 'Datum', mandatory: true, - }) + }), ); this.addControl( new InputControl('time', { label: 'Uhrzeit', mandatory: true, - }) + }), ); const validationHandler = new ValidationHandler(); From 1da16fdf7ecf2ee01ae46f0fe3ff75e5cc25a5d4 Mon Sep 17 00:00:00 2001 From: Martin Oppitz Date: Tue, 17 Oct 2023 12:27:09 +0200 Subject: [PATCH 19/20] fix: lint --- packages/samples/react/.eslintignore | 3 ++- packages/tools/kolibri-cli/src/migrate/index.ts | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/samples/react/.eslintignore b/packages/samples/react/.eslintignore index d38162f5c9..e2b5d24146 100644 --- a/packages/samples/react/.eslintignore +++ b/packages/samples/react/.eslintignore @@ -1 +1,2 @@ -**/assets/** \ No newline at end of file +**/assets/** +**/complex-form/** diff --git a/packages/tools/kolibri-cli/src/migrate/index.ts b/packages/tools/kolibri-cli/src/migrate/index.ts index ecb206d335..ccc7175f5b 100644 --- a/packages/tools/kolibri-cli/src/migrate/index.ts +++ b/packages/tools/kolibri-cli/src/migrate/index.ts @@ -96,8 +96,8 @@ Source folder to migrate: ${baseDir} /** * Creates a replacer function for the package.json file. - * @param version The version to replace - * @returns The replacer function + * @param {string} version Version to set + * @returns {string} The replacer function */ function createVersionReplacer(version: string) { return (...args: string[]) => { @@ -110,8 +110,8 @@ Source folder to migrate: ${baseDir} /** * Sets the version of the @public-ui/* packages in the package.json file. - * @param version Version to set - * @param cb Callback function + * @param {string} version Version to set + * @param {function} cb Callback function */ function setVersionOfPublicUiPackages(version: string, cb: () => void) { let packageJson = getContentOfProjectPkgJson(); From 2bab3b53f7568f7c75c0132230520f4da3577470 Mon Sep 17 00:00:00 2001 From: Martin Oppitz Date: Tue, 17 Oct 2023 12:27:17 +0200 Subject: [PATCH 20/20] chore: tiny optimization --- .../appointment-form/AppointmentForm.tsx | 17 ++++++--- .../appointment-form/DistrictForm.tsx | 1 + .../PersonalInformationForm.tsx | 36 +++++++++---------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx index 1c9d032450..a26c8a676e 100644 --- a/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/AppointmentForm.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { KolTabs } from '@public-ui/react'; import { DistrictForm } from './DistrictForm'; import { Summary } from './Summary'; @@ -63,6 +63,7 @@ const availableAppointmentsSchema = { export function AppointmentForm() { const [activeFormSection, setActiveFormSection] = useState(FormSection.DISTRICT); + const [selectedTab, setSelectedTab] = useState(activeFormSection); const validationSchema = Yup.object().shape({ ...(activeFormSection === FormSection.DISTRICT ? districtSchema : {}), @@ -70,17 +71,22 @@ export function AppointmentForm() { ...(activeFormSection === FormSection.PERSONAL_INFORMATION ? personalInformationSchema : {}), }); + useEffect(() => { + setSelectedTab(activeFormSection); + }, [activeFormSection]); + const handleSubmit = async (_values: FormValues, formik: FormikHelpers) => { + console.log(_values, formik); const currentSectionIndex = formSectionSequence.indexOf(activeFormSection); const nextSection = formSectionSequence[currentSectionIndex + 1]; if (nextSection !== undefined) { await formik.setTouched({}); - setActiveFormSection(nextSection); + setTimeout(() => setActiveFormSection(nextSection), 1000); } }; return ( - + initialValues={initialValues} validationSchema={validationSchema} onSubmit={handleSubmit}> setActiveFormSection(selectedTab) }} >
diff --git a/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx b/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx index ea429af07d..2f8fe84e0f 100644 --- a/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/DistrictForm.tsx @@ -61,6 +61,7 @@ export function DistrictForm() { _required _on={{ onChange: (event, values: unknown) => { + // Select und Radio setzen den Wert immer initial. if (event.target) { const [value] = values as [FormValues['district']]; void form.setFieldTouched('district', true); diff --git a/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx b/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx index 2abd6f9cb0..28cd06d633 100644 --- a/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx +++ b/packages/samples/react/src/scenarios/appointment-form/PersonalInformationForm.tsx @@ -40,25 +40,23 @@ export function PersonalInformationForm() { > {({ field }: FieldProps) => ( - <> - { - if (event.target) { - const [value] = values as [FormValues['salutation']]; - void form.setFieldTouched('salutation', true); - void form.setFieldValue('salutation', value, true); - } - }, - }} - /> - + { + if (event.target) { + const [value] = values as [FormValues['salutation']]; + void form.setFieldTouched('salutation', true); + void form.setFieldValue('salutation', value, true); + } + }, + }} + /> )}