updated checkbox widget to use carbon and added tooltip options w/ burnettk (#466)

Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
jasquat 2023-09-07 13:41:06 -04:00 committed by GitHub
parent f9e4cf577a
commit 69e2fef0b1
7 changed files with 149 additions and 168 deletions

View File

@ -1,4 +1,3 @@
// @ts-ignore
import { DatePicker, DatePickerInput, TextInput } from '@carbon/react';
import {
getInputProps,
@ -11,6 +10,7 @@ import {
import { useCallback } from 'react';
import { DATE_FORMAT_CARBON, DATE_FORMAT_FOR_DISPLAY } from '../../../config';
import { ymdDateStringToConfiguredFormat } from '../../../helpers';
import { getCommonAttributes } from '../../helpers';
/** The `BaseInputTemplate` is the template to use to render the basic `<input>` component for the `core` theme.
* It is used as the template for rendering many of the <input> based widgets that differ by `type` and callbacks only.
@ -70,28 +70,12 @@ export default function BaseInputTemplate<
[onFocus, id]
);
let labelToUse = label;
if (uiSchema && uiSchema['ui:title']) {
labelToUse = uiSchema['ui:title'];
} else if (schema && schema.title) {
labelToUse = schema.title;
}
let helperText = null;
if (uiSchema && uiSchema['ui:help']) {
helperText = uiSchema['ui:help'];
}
let invalid = false;
let errorMessageForField = null;
if (rawErrors && rawErrors.length > 0) {
invalid = true;
if ('validationErrorMessage' in schema) {
errorMessageForField = (schema as any).validationErrorMessage;
} else {
errorMessageForField = `${labelToUse.replace(/\*$/, '')} ${rawErrors[0]}`;
}
}
const commonAttributes = getCommonAttributes(
label,
schema,
uiSchema,
rawErrors
);
let component = null;
if (type === 'date') {
@ -118,15 +102,15 @@ export default function BaseInputTemplate<
<DatePickerInput
id={id}
placeholder={DATE_FORMAT_FOR_DISPLAY}
helperText={helperText}
helperText={commonAttributes.helperText}
type="text"
size="md"
value={dateValue}
autocomplete="off"
allowInput={false}
onChange={_onChange}
invalid={invalid}
invalidText={errorMessageForField}
invalid={commonAttributes.invalid}
invalidText={commonAttributes.errorMessageForField}
autoFocus={autofocus}
disabled={disabled || readonly}
onBlur={_onBlur}
@ -141,9 +125,9 @@ export default function BaseInputTemplate<
<TextInput
id={id}
className="text-input"
helperText={helperText}
invalid={invalid}
invalidText={errorMessageForField}
helperText={commonAttributes.helperText}
invalid={commonAttributes.invalid}
invalidText={commonAttributes.errorMessageForField}
autoFocus={autofocus}
disabled={disabled || readonly}
value={value || value === 0 ? value : ''}

View File

@ -1,7 +1,7 @@
import React from 'react';
import Checkbox from '@mui/material/Checkbox';
import FormControlLabel from '@mui/material/FormControlLabel';
import { schemaRequiresTrueValue, WidgetProps } from '@rjsf/utils';
import { Checkbox } from '@carbon/react';
import { WidgetProps } from '@rjsf/utils';
import { getCommonAttributes } from '../../helpers';
function CheckboxWidget(props: WidgetProps) {
const {
@ -15,13 +15,19 @@ function CheckboxWidget(props: WidgetProps) {
onChange,
onBlur,
onFocus,
uiSchema,
rawErrors,
required,
} = props;
// Because an unchecked checkbox will cause html5 validation to fail, only add
// the "required" attribute if the field value must be "true", due to the
// "const" or "enum" keywords
const required = schemaRequiresTrueValue(schema);
const _onChange = (_: any, checked: boolean) => onChange(checked);
const _onChange = (_: any, newValue: any) => {
// if this field is required and it is not checked then change the value to undefined
// otherwise rjsf will not flag this field as invalid
if (required && !newValue.checked) {
onChange(undefined);
} else {
onChange(newValue.checked);
}
};
const _onBlur = ({
target: { value },
}: React.FocusEvent<HTMLButtonElement>) => onBlur(id, value);
@ -29,23 +35,33 @@ function CheckboxWidget(props: WidgetProps) {
target: { value },
}: React.FocusEvent<HTMLButtonElement>) => onFocus(id, value);
const commonAttributes = getCommonAttributes(
label,
schema,
uiSchema,
rawErrors
);
return (
<FormControlLabel
control={
<Checkbox
id={id}
name={id}
checked={typeof value === 'undefined' ? false : Boolean(value)}
required={required}
disabled={disabled || readonly}
title={commonAttributes.tooltipText}
autoFocus={autofocus}
invalid={commonAttributes.invalid}
invalidText={commonAttributes.errorMessageForField}
helperText={commonAttributes.helperText}
labelText={
required
? commonAttributes.labelWithRequiredIndicator
: commonAttributes.label
}
onChange={_onChange}
onBlur={_onBlur}
onFocus={_onFocus}
/>
}
label={label || ''}
/>
);
}

View File

@ -1,5 +1,6 @@
import { Select, SelectItem } from '@carbon/react';
import { WidgetProps, processSelectValue } from '@rjsf/utils';
import { getCommonAttributes } from '../../helpers';
function SelectWidget({
schema,
@ -35,30 +36,12 @@ function SelectWidget({
}: React.FocusEvent<HTMLInputElement>) =>
onFocus(id, processSelectValue(schema, value, options));
let labelToUse = label;
if (uiSchema && uiSchema['ui:title']) {
labelToUse = uiSchema['ui:title'];
} else if (schema && schema.title) {
labelToUse = schema.title;
}
let helperText = null;
if (uiSchema && uiSchema['ui:help']) {
helperText = uiSchema['ui:help'];
}
if (required) {
labelToUse = `${labelToUse}*`;
}
let invalid = false;
let errorMessageForField = null;
if (rawErrors && rawErrors.length > 0) {
invalid = true;
if ('validationErrorMessage' in schema) {
errorMessageForField = (schema as any).validationErrorMessage;
} else {
errorMessageForField = rawErrors[0];
}
}
const commonAttributes = getCommonAttributes(
label,
schema,
uiSchema,
rawErrors
);
// ok. so in safari, the select widget showed the first option, whereas in chrome it forced you to select an option.
// this change causes causes safari to act a little bit more like chrome, but it's different because we are actually adding
@ -99,7 +82,7 @@ function SelectWidget({
name={id}
labelText=""
select
helperText={helperText}
helperText={commonAttributes.helperText}
value={typeof value === 'undefined' ? emptyValue : value}
disabled={disabled || readonly}
autoFocus={autofocus}
@ -107,8 +90,8 @@ function SelectWidget({
onChange={_onChange}
onBlur={_onBlur}
onFocus={_onFocus}
invalid={invalid}
invalidText={errorMessageForField}
invalid={commonAttributes.invalid}
invalidText={commonAttributes.errorMessageForField}
InputLabelProps={{
shrink: true,
}}
@ -116,7 +99,7 @@ function SelectWidget({
multiple: typeof multiple === 'undefined' ? false : multiple,
}}
>
{(enumOptions as any).map(({ value, label }: any, i: number) => {
{(enumOptions as any).map(({ value, label }: any, _i: number) => {
const disabled: any =
enumDisabled && (enumDisabled as any).indexOf(value) != -1;
return <SelectItem text={label} value={value} disabled={disabled} />;

View File

@ -7,6 +7,7 @@ import {
StrictRJSFSchema,
WidgetProps,
} from '@rjsf/utils';
import { getCommonAttributes } from '../../helpers';
/** The `TextareaWidget` is a widget for rendering input fields as textarea.
*
@ -51,34 +52,19 @@ function TextareaWidget<
[id, onFocus]
);
let labelToUse = label;
if (uiSchema && uiSchema['ui:title']) {
labelToUse = uiSchema['ui:title'];
} else if (schema && schema.title) {
labelToUse = schema.title;
}
if (required) {
labelToUse = `${labelToUse}*`;
}
let helperText = null;
if (uiSchema && uiSchema['ui:help']) {
helperText = uiSchema['ui:help'];
}
let invalid = false;
let errorMessageForField = null;
if (rawErrors && rawErrors.length > 0) {
invalid = true;
errorMessageForField = rawErrors[0];
}
const commonAttributes = getCommonAttributes(
label,
schema,
uiSchema,
rawErrors
);
return (
<TextArea
id={id}
name={id}
className="text-input"
helperText={helperText}
helperText={commonAttributes.helperText}
value={value || ''}
labelText=""
placeholder={placeholder}
@ -90,8 +76,8 @@ function TextareaWidget<
onBlur={handleBlur}
onFocus={handleFocus}
onChange={handleChange}
invalid={invalid}
invalidText={errorMessageForField}
invalid={commonAttributes.invalid}
invalidText={commonAttributes.errorMessageForField}
/>
);
}

View File

@ -10,10 +10,12 @@ import {
convertStringToDate,
dateStringToYMDFormat,
} from '../../../helpers';
import { getCommonAttributes } from '../../helpers';
interface widgetArgs {
id: string;
value: any;
label: string;
schema?: any;
uiSchema?: any;
disabled?: boolean;
@ -21,7 +23,6 @@ interface widgetArgs {
rawErrors?: any;
onChange?: any;
autofocus?: any;
label?: string;
}
// NOTE: To properly validate that both start and end dates are specified
@ -41,15 +42,12 @@ export default function DateRangePickerWidget({
label,
rawErrors = [],
}: widgetArgs) {
let invalid = false;
let errorMessageForField = null;
let labelToUse = label;
if (uiSchema && uiSchema['ui:title']) {
labelToUse = uiSchema['ui:title'];
} else if (schema && schema.title) {
labelToUse = schema.title;
}
const commonAttributes = getCommonAttributes(
label,
schema,
uiSchema,
rawErrors
);
const onChangeLocal = useCallback(
(dateRange: Date[]) => {
@ -69,22 +67,6 @@ export default function DateRangePickerWidget({
[onChange]
);
let helperText = null;
if (uiSchema && uiSchema['ui:help']) {
helperText = uiSchema['ui:help'];
}
if (!invalid && rawErrors && rawErrors.length > 0) {
invalid = true;
if ('validationErrorMessage' in schema) {
errorMessageForField = (schema as any).validationErrorMessage;
} else {
errorMessageForField = `${(labelToUse || '').replace(/\*$/, '')} ${
rawErrors[0]
}`;
}
}
let dateValue: (Date | null)[] | null = value;
if (value) {
const [startDateString, endDateString] = value.split(DATE_RANGE_DELIMITER);
@ -113,12 +95,12 @@ export default function DateRangePickerWidget({
<DatePickerInput
id={`${id}-start`}
placeholder={DATE_FORMAT_FOR_DISPLAY}
helperText={helperText}
helperText={commonAttributes.helperText}
type="text"
size="md"
disabled={disabled || readonly}
invalid={invalid}
invalidText={errorMessageForField}
invalid={commonAttributes.invalid}
invalidText={commonAttributes.errorMessageForField}
autoFocus={autofocus}
pattern={null}
/>
@ -128,7 +110,7 @@ export default function DateRangePickerWidget({
type="text"
size="md"
disabled={disabled || readonly}
invalid={invalid}
invalid={commonAttributes.invalid}
autoFocus={autofocus}
pattern={null}
/>

View File

@ -1,19 +1,20 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { ComboBox } from '@carbon/react';
import HttpService from '../../../services/HttpService';
import { getCommonAttributes } from '../../helpers';
interface typeaheadArgs {
id: string;
onChange: any;
options: any;
value: any;
label: string;
schema?: any;
uiSchema?: any;
disabled?: boolean;
readonly?: boolean;
rawErrors?: any;
placeholder?: string;
label?: string;
}
// eslint-disable-next-line sonarjs/cognitive-complexity
@ -36,30 +37,23 @@ export default function TypeaheadWidget({
const itemFormatRegex = /[^{}]+(?=})/g;
let itemFormatSubstitutions: string[] | null = null;
let invalid = false;
let errorMessageForField = null;
const commonAttributes = getCommonAttributes(
label,
schema,
uiSchema,
rawErrors
);
if (itemFormat) {
try {
itemFormatSubstitutions = itemFormat.match(itemFormatRegex);
} catch (e) {
errorMessageForField = `itemFormat does not contain replacement keys in curly braces. It should be like: "{key1} ({key2} - {key3})"`;
invalid = true;
commonAttributes.errorMessageForField = `itemFormat does not contain replacement keys in curly braces. It should be like: "{key1} ({key2} - {key3})"`;
commonAttributes.invalid = true;
}
}
let labelToUse = label;
if (uiSchema && uiSchema['ui:title']) {
labelToUse = uiSchema['ui:title'];
} else if (schema && schema.title) {
labelToUse = schema.title;
}
if (!category) {
errorMessageForField = `category is not set in the ui:options for this field: ${labelToUse}`;
invalid = true;
}
const typeaheadSearch = useCallback(
(inputText: string) => {
const pathForCategory = (text: string) => {
@ -109,20 +103,9 @@ export default function TypeaheadWidget({
placeholderText = placeholder;
}
let helperText = null;
if (uiSchema && uiSchema['ui:help']) {
helperText = uiSchema['ui:help'];
}
if (!invalid && rawErrors && rawErrors.length > 0) {
invalid = true;
if ('validationErrorMessage' in schema) {
errorMessageForField = (schema as any).validationErrorMessage;
} else {
errorMessageForField = `${(labelToUse || '').replace(/\*$/, '')} ${
rawErrors[0]
}`;
}
if (!category) {
commonAttributes.errorMessageForField = `category is not set in the ui:options for this field: ${commonAttributes.label}`;
commonAttributes.invalid = true;
}
return (
@ -145,11 +128,11 @@ export default function TypeaheadWidget({
itemToString={itemToString}
placeholder={placeholderText}
selectedItem={selectedItem}
helperText={helperText}
helperText={commonAttributes.helperText}
disabled={disabled}
readOnly={readonly}
invalid={invalid}
invalidText={errorMessageForField}
invalid={commonAttributes.invalid}
invalidText={commonAttributes.errorMessageForField}
/>
);
}

View File

@ -0,0 +1,47 @@
const REQUIRED_FIELD_SYMBOL = '*';
export const getCommonAttributes = (
label: string,
schema: any,
uiSchema: any,
rawErrors: any
) => {
let labelToUse = label;
if (uiSchema && uiSchema['ui:title']) {
labelToUse = uiSchema['ui:title'];
} else if (schema && schema.title) {
labelToUse = schema.title;
}
const labelWithRequiredIndicator = `${labelToUse}${REQUIRED_FIELD_SYMBOL}`;
let helperText = null;
let tooltipText = null;
if (uiSchema) {
if (uiSchema['ui:help']) {
helperText = uiSchema['ui:help'];
}
if (uiSchema['ui:tooltip']) {
tooltipText = uiSchema['ui:tooltip'];
}
}
let invalid = false;
let errorMessageForField = null;
if (rawErrors && rawErrors.length > 0) {
invalid = true;
if ('validationErrorMessage' in schema) {
errorMessageForField = (schema as any).validationErrorMessage;
} else {
errorMessageForField = `${labelToUse.replace(/\*$/, '')} ${rawErrors[0]}`;
}
}
return {
helperText,
label: labelToUse,
invalid,
errorMessageForField,
labelWithRequiredIndicator,
tooltipText,
};
};