character-counter-fixes (#1432)

* carbon supports the counter field natively so use that w/ burnettk

* added docs for character counter fields w/ burnettk

---------

Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
jasquat 2024-04-22 18:54:15 +00:00 committed by GitHub
parent a59f2777cd
commit a7a9927a5f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 86 additions and 138 deletions

View File

@ -199,10 +199,10 @@ Utilizing maximum date validation, you can prevent dates from exceeding a certai
By incorporating these validations into SpiffWorkflow forms, you can create interactive forms that automatically enforce business rules, improving data quality and user experience. By incorporating these validations into SpiffWorkflow forms, you can create interactive forms that automatically enforce business rules, improving data quality and user experience.
#### Date Validation Scenario: Enforcing Minimum and Maximum Date Constraints #### Date Validation Scenario: Enforcing Minimum and Maximum Date Constraints
#### Scenario Overview #### Scenario Overview
Workflow processes often require the enforcement of minimum and maximum date constraints to align with operational timelines or project deadlines. This scenario demonstrates the configuration of both `minimumDate` and `maximumDate` validations within a form, ensuring that selected dates fall within a specific period defined by other date fields in the workflow. Workflow processes often require the enforcement of minimum and maximum date constraints to align with operational timelines or project deadlines. This scenario demonstrates the configuration of both `minimumDate` and `maximumDate` validations within a form, ensuring that selected dates fall within a specific period defined by other date fields in the workflow.
#### JSON Schema Configuration: #### JSON Schema Configuration:
@ -242,9 +242,10 @@ The "test-maximum-date-schema.json" process model outlines a form structure that
- **Preferred Delivery Date**: A single date indicating when the delivery of a service or product is preferred, bounded by today's date and the `end_date`. - **Preferred Delivery Date**: A single date indicating when the delivery of a service or product is preferred, bounded by today's date and the `end_date`.
- **Preferred Delivery Date Range**: A span of dates indicating an acceptable window for delivery, constrained by today's date and the `end_date`. - **Preferred Delivery Date Range**: A span of dates indicating an acceptable window for delivery, constrained by today's date and the `end_date`.
### Implementation in SpiffWorkflow Forms: #### Implementation in SpiffWorkflow Forms:
The schema enforces the following rules: The schema enforces the following rules:
- The `Preferred Delivery Date` cannot be earlier than today (the `minimumDate`) and not later than the `end_date` (the `maximumDate`). - The `Preferred Delivery Date` cannot be earlier than today (the `minimumDate`) and not later than the `end_date` (the `maximumDate`).
- The `Preferred Delivery Date Range` must start no earlier than today and end no later than the `end_date`. - The `Preferred Delivery Date Range` must start no earlier than today and end no later than the `end_date`.
@ -263,9 +264,9 @@ Define your form fields in the JSON schema as follows:
"description": "Demonstrating side-by-side layout", "description": "Demonstrating side-by-side layout",
"type": "object", "type": "object",
"properties": { "properties": {
"firstName": {"type": "string"}, "firstName": { "type": "string" },
"lastName": {"type": "string"}, "lastName": { "type": "string" },
"notes": {"type": "string"} "notes": { "type": "string" }
} }
} }
``` ```
@ -279,13 +280,14 @@ Here's how to use it:
{ {
"ui:layout": [ "ui:layout": [
{ {
"firstName": {"sm": 2, "md": 2, "lg": 4}, "firstName": { "sm": 2, "md": 2, "lg": 4 },
"lastName": {"sm": 2, "md": 2, "lg": 4} "lastName": { "sm": 2, "md": 2, "lg": 4 }
}, },
{"notes": {}} { "notes": {} }
] ]
} }
``` ```
![Styling_Form](images/styling_forms.png) ![Styling_Form](images/styling_forms.png)
#### Key Points: #### Key Points:
@ -311,11 +313,8 @@ If you just specify a uiSchema like this, it will figure out the column widths f
] ]
} }
By leveraging the `ui:layout` feature, you can design form layouts that are not only functional but also enhance the user experience, making your forms well-organized and accessible across various screen sizes. By leveraging the `ui:layout` feature, you can design form layouts that are not only functional but also enhance the user experience, making your forms well-organized and accessible across various screen sizes.
### Display UI Help in Web Forms ### Display UI Help in Web Forms
When designing web forms, it's essential to provide users with contextual help to ensure they understand the purpose and requirements of each field. When designing web forms, it's essential to provide users with contextual help to ensure they understand the purpose and requirements of each field.
@ -393,17 +392,17 @@ Below is an example JSON schema that includes the numeric range field:
"numericRange": { "numericRange": {
"type": "object", "type": "object",
"title": "Numeric Range", "title": "Numeric Range",
"minimum": { "minimum": {
"type": "number", "type": "number",
"title": "Minimum Value" "title": "Minimum Value"
}, },
"maximum": { "maximum": {
"type": "number", "type": "number",
"title": "Maximum Value" "title": "Maximum Value"
}
} }
} }
} }
}
``` ```
This schema defines a numeric range object with `min` and `max` properties, both of which are required. This schema defines a numeric range object with `min` and `max` properties, both of which are required.
@ -422,7 +421,6 @@ This schema defines a numeric range object with `min` and `max` properties, both
This will automatically validate that the max value cannot be less than the min value. This will automatically validate that the max value cannot be less than the min value.
### Adding a New Button for Repeating Sections in Forms ### Adding a New Button for Repeating Sections in Forms
Nested forms or repeating sections are designed to collect an array of objects, where each object represents a set of related information. For instance, in a task management form, you might need to collect multiple tasks, each with its title and completion status. Nested forms or repeating sections are designed to collect an array of objects, where each object represents a set of related information. For instance, in a task management form, you might need to collect multiple tasks, each with its title and completion status.
@ -458,8 +456,44 @@ This structure can be represented in the form's schema as follows:
} }
} }
``` ```
**Form Preview**: **Form Preview**:
![Nested Forms](images/Nested_form_display.png) ![Nested Forms](images/Nested_form_display.png)
By usign this feature, you can effectively implement new buttons for nested forms or repeating sections improving the form's usability for collecting multiple related entries from users. By usign this feature, you can effectively implement new buttons for nested forms or repeating sections improving the form's usability for collecting multiple related entries from users.
### Character counter
To give the user feedback about how they are doing in terms of staying within the limits imposed by the field, you can display a character counter.
#### JSON Schema Configuration
To do this, your json schema must contain a string with a maxLength, like this:
```json
{
"title": "String with character counter",
"type": "object",
"properties": {
"my_hot_string": {
"type": "string",
"maxLength": 100
}
}
}
```
#### UI Schema Configuration
Your UI Schema will need a ui:options specifying counter true, like this:
```json
{
"my_hot_string": {
"ui:options": {
"counter": true
}
}
}
```

View File

@ -10,7 +10,6 @@ import TypeaheadWidget from '../rjsf/custom_widgets/TypeaheadWidget/TypeaheadWid
import MarkDownFieldWidget from '../rjsf/custom_widgets/MarkDownFieldWidget/MarkDownFieldWidget'; import MarkDownFieldWidget from '../rjsf/custom_widgets/MarkDownFieldWidget/MarkDownFieldWidget';
import NumericRangeField from '../rjsf/custom_widgets/NumericRangeField/NumericRangeField'; import NumericRangeField from '../rjsf/custom_widgets/NumericRangeField/NumericRangeField';
import ObjectFieldRestrictedGridTemplate from '../rjsf/custom_templates/ObjectFieldRestrictGridTemplate'; import ObjectFieldRestrictedGridTemplate from '../rjsf/custom_templates/ObjectFieldRestrictGridTemplate';
import CharacterCounterField from '../rjsf/custom_widgets/CharacterCounterField/CharacterCounterField';
enum DateCheckType { enum DateCheckType {
minimum = 'minimum', minimum = 'minimum',
@ -57,7 +56,6 @@ export default function CustomForm({
// set in uiSchema using the "ui:field" key for a property // set in uiSchema using the "ui:field" key for a property
const rjsfFields: RegistryFieldsType = { const rjsfFields: RegistryFieldsType = {
'numeric-range': NumericRangeField, 'numeric-range': NumericRangeField,
'character-counter': CharacterCounterField,
}; };
const rjsfTemplates: any = {}; const rjsfTemplates: any = {};

View File

@ -89,6 +89,19 @@ export default function BaseInputTemplate<
1000 1000
); );
let enableCounter = false;
let maxCount = undefined;
if (options && options.counter) {
enableCounter = true;
if (schema && schema.maxLength) {
maxCount = schema.maxLength;
} else {
throw new Error(
`Counter was requested but no maxLength given on the ${label}`
);
}
}
const commonAttributes = getCommonAttributes( const commonAttributes = getCommonAttributes(
label, label,
schema, schema,
@ -157,6 +170,8 @@ export default function BaseInputTemplate<
onChange={_onChange} onChange={_onChange}
onBlur={_onBlur} onBlur={_onBlur}
onFocus={_onFocus} onFocus={_onFocus}
enableCounter={enableCounter}
maxCount={maxCount}
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
{...inputProps} {...inputProps}
/> />

View File

@ -59,6 +59,19 @@ function TextareaWidget<
rawErrors rawErrors
); );
let enableCounter = false;
let maxCount = undefined;
if (options && options.counter) {
enableCounter = true;
if (schema && schema.maxLength) {
maxCount = schema.maxLength;
} else {
throw new Error(
`Counter was requested but no maxLength given on the ${label}`
);
}
}
return ( return (
<TextArea <TextArea
id={id} id={id}
@ -78,6 +91,8 @@ function TextareaWidget<
onChange={handleChange} onChange={handleChange}
invalid={commonAttributes.invalid} invalid={commonAttributes.invalid}
invalidText={commonAttributes.errorMessageForField} invalidText={commonAttributes.errorMessageForField}
enableCounter={enableCounter}
maxCount={maxCount}
/> />
); );
} }

View File

@ -1,114 +0,0 @@
import {
descriptionId,
FieldProps,
getTemplate,
getUiOptions,
} from '@rjsf/utils';
import { TextInput } from '@carbon/react';
import React from 'react';
import { getCommonAttributes } from '../../helpers';
// Example jsonSchema - NOTE: the "min" and "max" properties are special names and must be used:
// "name":{
// "title": "Name",
// "type": "string",
// "maxLength": 999999999999,
// }
//
// Example uiSchema:
// "name": {
// "ui:field": "character-counter",
// }
// eslint-disable-next-line sonarjs/cognitive-complexity
export default function CharacterCounterField({
id,
schema,
uiSchema,
idSchema,
disabled,
readonly,
onChange,
autofocus,
label,
rawErrors = [],
formData,
registry,
required,
}: FieldProps) {
const commonAttributes = getCommonAttributes(
label,
schema,
uiSchema,
rawErrors
);
const description = schema?.description || uiSchema?.['ui:description'];
const uiOptions = getUiOptions(uiSchema || {});
const DescriptionFieldTemplate = getTemplate(
'DescriptionFieldTemplate',
registry,
uiOptions
);
if (schema.maxLength === undefined) {
throw new Error(
'CharacterCounterTextField requires a "maxLength" property to be specified in the schema'
);
}
const text = formData || '';
const onChangeLocal = (event: any) => {
event.preventDefault();
if (!disabled && !readonly) {
onChange(event.target.value);
}
};
return (
<div className="character---counter--text-field-wrapper">
<div className="character---counter--text-field-label">
<h5>
{required ? `${commonAttributes.label} *` : commonAttributes.label}
</h5>
{description && (
<div className="markdown-field-desc-text">
<DescriptionFieldTemplate
id={descriptionId(idSchema)}
description={description}
schema={schema}
uiSchema={uiSchema}
registry={registry}
/>
</div>
)}
</div>
<TextInput
id={id}
type="text"
disabled={disabled}
readonly={readonly}
value={text}
onChange={(event: any) => {
onChangeLocal(event);
}}
invalid={commonAttributes.invalid}
enableCounter
maxCount={schema.maxLength}
autoFocus={autofocus}
/>
{commonAttributes.errorMessageForField && (
<div className="error-message">
{commonAttributes.errorMessageForField}
</div>
)}
{commonAttributes.helperText && (
<p className="character---counter--text-field-help-text">
{commonAttributes.helperText}
</p>
)}
</div>
);
}