From 9d3bac55c93894887c49b5b657c0db44b535d305 Mon Sep 17 00:00:00 2001 From: Stefan Hauke Date: Wed, 12 Jun 2024 09:18:59 +0200 Subject: [PATCH] feat: introduce Formly number field (quantity input with + and - buttons) --- docs/guides/formly.md | 35 ++++++++-------- .../number-field/number-field.component.html | 35 ++++++++++++++++ .../number-field/number-field.component.scss | 31 ++++++++++++++ .../number-field/number-field.component.ts | 41 +++++++++++++++++++ src/app/shared/formly/types/types.module.ts | 7 ++++ src/assets/i18n/en_US.json | 2 + 6 files changed, 134 insertions(+), 17 deletions(-) create mode 100644 src/app/shared/formly/types/number-field/number-field.component.html create mode 100644 src/app/shared/formly/types/number-field/number-field.component.scss create mode 100644 src/app/shared/formly/types/number-field/number-field.component.ts diff --git a/docs/guides/formly.md b/docs/guides/formly.md index f784fc371e..c8baa29aba 100644 --- a/docs/guides/formly.md +++ b/docs/guides/formly.md @@ -61,7 +61,7 @@ A configuration for a form containing only a basic input field could be defined ```typescript const fields: FormlyFieldConfig[] = [ { - type: 'ish-input-field', + type: 'ish-text-input-field', key: 'example-input', props: { required: true, @@ -254,22 +254,23 @@ Refer to the tables below for an overview of these parts. - Template option `inputClass`: These CSS class(es) will be added to all input/select/textarea/text tags. - Template option `ariaLabel`: Adds an aria-label to all input/select/textarea tags. -| Name | Description | Relevant props | -| --------------------------- | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| ish-text-input-field | Basic input field, supports all text types | `type`: 'text (default),'email','tel','password'. `mask`: input mask for a needed pattern (see [ngx-mask](https://www.npmjs.com/package/ngx-mask) for more information) | -| ish-select-field | Basic select field | `options`: `{ value: any; label: string}[]` or Observable. `placeholder`: Translation key or string for the default selection | -| ish-textarea-field | Basic textarea field | `cols` & `rows`: Specifies the dimensions of the textarea | -| ish-checkbox-field | Basic checkbox input | `title`: Title for a checkbox | -| ish-email-field | Email input field that automatically adds an e-mail validator and error messages | ---- | -| ish-password-field | Password input field that automatically adds a password validator and error messages | ---- | -| ish-phone-field | Phone number input field that automatically adds a phone number validator and error messages | ---- | -| ish-fieldset-field | Wraps fields in a `
` tag for styling | `fieldsetClass`: Class that will be added to the fieldset tag. `childClass`: Class that will be added to the child div. `legend`: Legend element that will be added to the fieldset, use the value as the legend text. `legendClass`: Class that will be added to the legend tag. | -| ish-captcha-field | Includes the `` component and adds the relevant `formControls` to the form | `topic`: Topic that will be passed to the Captcha component. | -| ish-radio-field | Basic radio input | ---- | -| ish-plain-text-field | Only display the form value | ---- | -| ish-html-text-field | Only display the form value as html | ---- | -| ish-date-picker-field | Basic datepicker | `minDays`: Computes the minDate by adding the minimum allowed days to today. `maxDays`: Computes the maxDate by adding the maximum allowed days to today. `isSatExcluded`: Specifies if saturdays can be disabled. `isSunExcluded`: Specifies if sundays can be disabled. | -| ish-date-range-picker-field | Datepicker with range | `minDays`: Computes the minDate by adding the minimum allowed days to today. `maxDays`: Computes the maxDate by adding the maximum allowed days to today. `startDate`: The start date. `placeholder`: Placeholder that displays the date format in the input field. | +| Name | Description | Relevant props | +| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ish-text-input-field | Basic input field, supports all text types | `type`: 'text (default),'email','tel','password'. `mask`: input mask for a needed pattern (see [ngx-mask](https://www.npmjs.com/package/ngx-mask) for more information) | +| ish-select-field | Basic select field | `options`: `{ value: any; label: string}[]` or Observable. `placeholder`: Translation key or string for the default selection | +| ish-textarea-field | Basic textarea field | `cols` & `rows`: Specifies the dimensions of the textarea | +| ish-checkbox-field | Basic checkbox input | `title`: Title for a checkbox | +| ish-email-field | Email input field that automatically adds an e-mail validator and error messages | ---- | +| ish-password-field | Password input field that automatically adds a password validator and error messages | ---- | +| ish-phone-field | Phone number input field that automatically adds a phone number validator and error messages | ---- | +| ish-fieldset-field | Wraps fields in a `
` tag for styling | `fieldsetClass`: Class that will be added to the fieldset tag. `childClass`: Class that will be added to the child div. `legend`: Legend element that will be added to the fieldset, use the value as the legend text. `legendClass`: Class that will be added to the legend tag. | +| ish-captcha-field | Includes the `` component and adds the relevant `formControls` to the form | `topic`: Topic that will be passed to the Captcha component. | +| ish-radio-field | Basic radio input | ---- | +| ish-plain-text-field | Only display the form value | ---- | +| ish-html-text-field | Only display the form value as html | ---- | +| ish-date-picker-field | Basic datepicker | `minDays`: Computes the minDate by adding the minimum allowed days to today. `maxDays`: Computes the maxDate by adding the maximum allowed days to today. `isSatExcluded`: Specifies if saturdays can be disabled. `isSunExcluded`: Specifies if sundays can be disabled. | +| ish-date-range-picker-field | Datepicker with range | `minDays`: Computes the minDate by adding the minimum allowed days to today. `maxDays`: Computes the maxDate by adding the maximum allowed days to today. `startDate`: The start date. `placeholder`: Placeholder that displays the date format in the input field. | +| ish-number-field | Basic number input field for smaller Integer numbers, with `+` and `-` buttons (use `ish-text-input-field` with `mask` for larger numbers) | `min`, `max` and `step` input configuration is considered by the in-/decrease buttons | ### Wrappers diff --git a/src/app/shared/formly/types/number-field/number-field.component.html b/src/app/shared/formly/types/number-field/number-field.component.html new file mode 100644 index 0000000000..496b0c27c3 --- /dev/null +++ b/src/app/shared/formly/types/number-field/number-field.component.html @@ -0,0 +1,35 @@ +
+ + + +
diff --git a/src/app/shared/formly/types/number-field/number-field.component.scss b/src/app/shared/formly/types/number-field/number-field.component.scss new file mode 100644 index 0000000000..97d347ebca --- /dev/null +++ b/src/app/shared/formly/types/number-field/number-field.component.scss @@ -0,0 +1,31 @@ +.counter-input { + position: relative; + + button { + position: absolute; + width: auto; + margin: 0; + } + + .decrease-button { + left: 0; + } + + .increase-button { + right: 0; + } + + // https://www.w3schools.com/howto/howto_css_hide_arrow_number.asp + + /* Chrome, Safari, Edge, Opera */ + input::-webkit-outer-spin-button, + input::-webkit-inner-spin-button { + margin: 0; + appearance: none; + } + + /* Firefox */ + input[type='number'] { + appearance: textfield; + } +} diff --git a/src/app/shared/formly/types/number-field/number-field.component.ts b/src/app/shared/formly/types/number-field/number-field.component.ts new file mode 100644 index 0000000000..d13d2df3a6 --- /dev/null +++ b/src/app/shared/formly/types/number-field/number-field.component.ts @@ -0,0 +1,41 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { FieldType, FieldTypeConfig } from '@ngx-formly/core'; + +/** + * Type for a number field + * + * @defaultWrappers form-field-horizontal & validation + */ +@Component({ + selector: 'ish-number-field', + templateUrl: './number-field.component.html', + styleUrls: ['./number-field.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class NumberFieldComponent extends FieldType implements OnInit { + cannotIncrease = false; + cannotDecrease = false; + + ngOnInit(): void { + this.evaluateButtonDisabled(); + } + + increase() { + this.formControl.setValue( + Number.parseInt(this.formControl.value) + (this.field.props.step ? this.field.props.step : 1) + ); + this.evaluateButtonDisabled(); + } + + decrease() { + this.formControl.setValue( + Number.parseInt(this.formControl.value) - (this.field.props.step ? this.field.props.step : 1) + ); + this.evaluateButtonDisabled(); + } + + private evaluateButtonDisabled() { + this.cannotDecrease = this.field.props.min && this.formControl.value <= this.field.props.min; + this.cannotIncrease = this.field.props.max && this.formControl.value >= this.field.props.max; + } +} diff --git a/src/app/shared/formly/types/types.module.ts b/src/app/shared/formly/types/types.module.ts index 6d38fbea0a..e39f533479 100644 --- a/src/app/shared/formly/types/types.module.ts +++ b/src/app/shared/formly/types/types.module.ts @@ -26,6 +26,7 @@ import { LocalizedParserFormatter } from './date-picker-field/localized-parser-f import { DateRangePickerFieldComponent } from './date-range-picker-field/date-range-picker-field.component'; import { FieldsetFieldComponent } from './fieldset-field/fieldset-field.component'; import { HtmlTextFieldComponent } from './html-text-field/html-text-field.component'; +import { NumberFieldComponent } from './number-field/number-field.component'; import { PlainTextFieldComponent } from './plain-text-field/plain-text-field.component'; import { RadioFieldComponent } from './radio-field/radio-field.component'; import { SelectFieldComponent } from './select-field/select-field.component'; @@ -44,6 +45,7 @@ const fieldComponents = [ SelectFieldComponent, TextareaFieldComponent, TextInputFieldComponent, + NumberFieldComponent, ]; @NgModule({ @@ -166,6 +168,11 @@ const fieldComponents = [ component: DateRangePickerFieldComponent, wrappers: ['form-field-horizontal', 'validation'], }, + { + name: 'ish-number-field', + component: NumberFieldComponent, + wrappers: ['form-field-horizontal', 'validation'], + }, ], }), ], diff --git a/src/assets/i18n/en_US.json b/src/assets/i18n/en_US.json index 40f92a42b0..db6670f692 100644 --- a/src/assets/i18n/en_US.json +++ b/src/assets/i18n/en_US.json @@ -925,6 +925,8 @@ "navigation.paging.go_to_page.label": "Go to page {{0}}", "navigation.paging.next_page.label": "Go to next page", "navigation.paging.previous_page.label": "Go to previous page", + "number.decrease.text": "–", + "number.increase.text": "+", "order.tracking.error": "Unfortunately, we could not locate an order with the information you provided.", "order_template.create.heading": "Create order template", "payment.error.PaymentInstrumentAlreadyExists": "The payment instrument could not be created. Payment data with the given parameters already exists.",