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.
#### Date Validation Scenario: Enforcing Minimum and Maximum Date Constraints
#### 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.
#### 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 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 `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`.
@ -286,6 +287,7 @@ Here's how to use it:
]
}
```
![Styling_Form](images/styling_forms.png)
#### 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.
### 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.
@ -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.
### 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.
@ -458,8 +456,44 @@ This structure can be represented in the form's schema as follows:
}
}
```
**Form Preview**:
![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.
### 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 NumericRangeField from '../rjsf/custom_widgets/NumericRangeField/NumericRangeField';
import ObjectFieldRestrictedGridTemplate from '../rjsf/custom_templates/ObjectFieldRestrictGridTemplate';
import CharacterCounterField from '../rjsf/custom_widgets/CharacterCounterField/CharacterCounterField';
enum DateCheckType {
minimum = 'minimum',
@ -57,7 +56,6 @@ export default function CustomForm({
// set in uiSchema using the "ui:field" key for a property
const rjsfFields: RegistryFieldsType = {
'numeric-range': NumericRangeField,
'character-counter': CharacterCounterField,
};
const rjsfTemplates: any = {};

View File

@ -89,6 +89,19 @@ export default function BaseInputTemplate<
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(
label,
schema,
@ -157,6 +170,8 @@ export default function BaseInputTemplate<
onChange={_onChange}
onBlur={_onBlur}
onFocus={_onFocus}
enableCounter={enableCounter}
maxCount={maxCount}
// eslint-disable-next-line react/jsx-props-no-spreading
{...inputProps}
/>

View File

@ -59,6 +59,19 @@ function TextareaWidget<
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 (
<TextArea
id={id}
@ -78,6 +91,8 @@ function TextareaWidget<
onChange={handleChange}
invalid={commonAttributes.invalid}
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>
);
}