Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement error list in KolForm #5832

Merged
merged 17 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { ButtonOrLinkOrTextWithChildrenProps } from "./types/button-link-text";
import { KoliBriCardEventCallbacks } from "./components/card/types";
import { EventCallbacks } from "./components/details/types";
import { KoliBriFormCallbacks } from "./components/form/types";
import { ErrorListPropType } from "./types/props/error-list";
import { HeadingVariantPropType } from "./types/props/heading-variant";
import { Loading } from "./utils/validators/loading";
import { SuggestionsPropType } from "./types/props/suggestions";
Expand Down Expand Up @@ -88,6 +89,7 @@ export { ButtonOrLinkOrTextWithChildrenProps } from "./types/button-link-text";
export { KoliBriCardEventCallbacks } from "./components/card/types";
export { EventCallbacks } from "./components/details/types";
export { KoliBriFormCallbacks } from "./components/form/types";
export { ErrorListPropType } from "./types/props/error-list";
export { HeadingVariantPropType } from "./types/props/heading-variant";
export { Loading } from "./utils/validators/loading";
export { SuggestionsPropType } from "./types/props/suggestions";
Expand Down Expand Up @@ -541,6 +543,10 @@ export namespace Components {
"_open"?: boolean;
}
interface KolForm {
/**
* A list of error objects that each describe an issue encountered in the form. Each error object contains a message and a selector for identifying the form element related to the error.
*/
"_errorList"?: ErrorListPropType[];
/**
* Gibt die EventCallback-Funktionen für die Form-Events an.
*/
Expand Down Expand Up @@ -3339,6 +3345,10 @@ declare namespace LocalJSX {
"_open"?: boolean;
}
interface KolForm {
/**
* A list of error objects that each describe an issue encountered in the form. Each error object contains a message and a selector for identifying the form element related to the error.
*/
"_errorList"?: ErrorListPropType[];
/**
* Gibt die EventCallback-Funktionen für die Form-Events an.
*/
Expand Down
39 changes: 39 additions & 0 deletions packages/components/src/components/form/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { translate } from '../../i18n';
import { Stringified } from '../../types/common';
import { watchBoolean, watchString } from '../../utils/prop.validators';
import { API, KoliBriFormCallbacks, States } from './types';
import { ErrorListPropType, validateErrorList } from '../../types/props/error-list';

/**
* @slot - Inhalt der Form.
Expand All @@ -28,9 +29,36 @@ export class KolForm implements API {
}
};

private readonly handleLinkClick = (event: Event) => {
const href = (event.target as HTMLAnchorElement | undefined)?.href;
if (href) {
const hrefUrl = new URL(href);

const targetElement = document.querySelector<HTMLElement>(hrefUrl.hash);
if (targetElement && typeof targetElement.focus === 'function') {
targetElement.scrollIntoView({ behavior: 'smooth' });
targetElement.focus();
}
}
};

public render(): JSX.Element {
return (
<form method="post" onSubmit={this.onSubmit} onReset={this.onReset} autoComplete="off" noValidate>
{this._errorList && this._errorList.length > 0 && (
<kol-alert _type="error">
{translate('kol-error-list-message')}
<nav aria-label={translate('kol-error-list')}>
<ul>
{this._errorList.map((error, index) => (
<li key={index}>
<kol-link _href={error.selector} _label={error.message} _on={{ onClick: this.handleLinkClick }} />
</li>
))}
</ul>
</nav>
</kol-alert>
)}
{this.state._requiredText === true ? (
<p>
<kol-indented-text>{translate('kol-form-description')}</kol-indented-text>
Expand All @@ -54,6 +82,11 @@ export class KolForm implements API {
* Defines whether the mandatory-fields-hint should be shown. A string overrides the default text.
*/
@Prop() public _requiredText?: Stringified<boolean> = true;
/**
* A list of error objects that each describe an issue encountered in the form.
* Each error object contains a message and a selector for identifying the form element related to the error.
*/
@Prop() public _errorList?: ErrorListPropType[];

@State() public state: States = {};

Expand All @@ -76,8 +109,14 @@ export class KolForm implements API {
}
}

@Watch('_errorList')
public validateErrorList(value?: ErrorListPropType[]): void {
validateErrorList(this, value);
}

public componentWillLoad(): void {
this.validateOn(this._on);
this.validateRequiredText(this._requiredText);
this.validateErrorList(this._errorList);
}
}
3 changes: 2 additions & 1 deletion packages/components/src/components/form/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Generic } from 'adopted-style-sheets';

import { Events } from '../../enums/events';
import { EventCallback } from '../../types/callbacks';
import { PropErrorList } from '../../types/props/error-list';

export type KoliBriFormCallbacks = {
[Events.onSubmit]?: EventCallback<Event>;
Expand All @@ -12,7 +13,7 @@ type RequiredProps = NonNullable<unknown>;
type OptionalProps = {
on: KoliBriFormCallbacks;
requiredText: string | boolean;
};
} & PropErrorList;
export type Props = Generic.Element.Members<RequiredProps, OptionalProps>;

type RequiredStates = RequiredProps;
Expand Down
2 changes: 2 additions & 0 deletions packages/components/src/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ export default {
'table-pagination-label': 'Paginierung für die Tabelle {{label}}',
'avatar-alt': 'Avatar von {{name}}',
'toast-close-all': 'Alle schließen',
'error-list': 'Fehlerliste',
'error-list-message': 'Bitte korrigieren Sie folgende Fehler:',
};
2 changes: 2 additions & 0 deletions packages/components/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ export default {
'table-pagination-label': 'Pagination for table {{label}}',
'avatar-alt': 'Avatar of {{name}}',
'toast-close-all': 'Close all',
'error-list': 'Error list',
'error-list-message': 'Please correct the following errors',
};
17 changes: 17 additions & 0 deletions packages/components/src/types/props/error-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Generic } from 'adopted-style-sheets';
import { watchValidator } from '../../utils/prop.validators';

/* types */
export type ErrorListPropType = {
message: string;
selector: string;
};

export type PropErrorList = {
errorList: ErrorListPropType[];
};

/* validator */
export const validateErrorList = (component: Generic.Element.Component, value?: ErrorListPropType[]): void => {
watchValidator(component, 'errorList', (value): boolean => typeof value === 'object', new Set(['Object']), value);
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useState } from 'react';
import React from 'react';
import { KolButton, KolForm, KolHeading, KolSelect } from '@public-ui/react';
import { Field, FieldProps, useFormikContext } from 'formik';
import { FormValues } from './AppointmentForm';
import { ErrorList } from './ErrorList';
import { ErrorListPropType } from '@public-ui/components';

const LOCATION_OPTIONS = [
{
Expand All @@ -29,23 +29,23 @@ const LOCATION_OPTIONS = [

export function DistrictForm() {
const form = useFormikContext<FormValues>();
const [sectionSubmitted, setSectionSubmitted] = useState(false);
const errorList = createErrorList(form.errors);

function createErrorList(formikErrors: Record<string, string>): ErrorListPropType[] {
return Object.keys(formikErrors).map((fieldName) => ({
message: formikErrors[fieldName],
selector: `#field-${fieldName}`,
}));
}

return (
<div className="p-2">
<KolHeading _level={2} _label="Wählen Sie einen Stadtteil aus"></KolHeading>

{sectionSubmitted && Object.keys(form.errors).length ? (
<div className="mt-2">
<ErrorList errors={form.errors} />
</div>
) : null}

<KolForm
_errorList={errorList}
_on={{
onSubmit: () => {
void form.submitForm();
setSectionSubmitted(true);
},
}}
>
Expand Down
Loading