Merge pull request #118 from sartography/feature/form-styling-fixes

Feature/form styling fixes
This commit is contained in:
Kevin Burnett 2023-01-30 13:47:55 -08:00 committed by GitHub
commit 8363bda7c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 235 additions and 161 deletions

View File

@ -217,7 +217,7 @@ export default function TaskShow() {
return ( return (
<Grid fullWidth condensed> <Grid fullWidth condensed>
<Column md={5} lg={8} sm={4}> <Column sm={4} md={5} lg={8}>
<Form <Form
formData={taskData} formData={taskData}
onSubmit={handleFormSubmit} onSubmit={handleFormSubmit}

View File

@ -1,17 +1,36 @@
import React from 'react'; import React from 'react';
import AddIcon from '@mui/icons-material/Add'; import {
import IconButton from '@mui/material/IconButton'; FormContextType,
import { IconButtonProps } from '@rjsf/utils'; IconButtonProps,
RJSFSchema,
StrictRJSFSchema,
} from '@rjsf/utils';
const AddButton: React.ComponentType<IconButtonProps> = ({ // @ts-ignore
uiSchema, import { AddAlt } from '@carbon/icons-react';
...props
}) => { import IconButton from '../IconButton/IconButton';
/** The `AddButton` renders a button that represent the `Add` action on a form
*/
export default function AddButton<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>({ className, onClick, disabled, registry }: IconButtonProps<T, S, F>) {
return ( return (
<IconButton title="Add Item" {...props} color="primary"> <div className="row">
<AddIcon /> <p className={`col-xs-3 col-xs-offset-9 text-right ${className}`}>
</IconButton> <IconButton
iconType="info"
icon="plus"
className="btn-add col-xs-12"
title="Add"
onClick={onClick}
disabled={disabled}
registry={registry}
/>
</p>
</div>
); );
}; }
export default AddButton;

View File

@ -5,6 +5,11 @@ import {
RJSFSchema, RJSFSchema,
StrictRJSFSchema, StrictRJSFSchema,
} from '@rjsf/utils'; } from '@rjsf/utils';
import {
Grid,
Column,
// @ts-ignore
} from '@carbon/react';
/** The `ArrayFieldItemTemplate` component is the template used to render an items of an array. /** The `ArrayFieldItemTemplate` component is the template used to render an items of an array.
* *
@ -33,53 +38,57 @@ export default function ArrayFieldItemTemplate<
const { MoveDownButton, MoveUpButton, RemoveButton } = const { MoveDownButton, MoveUpButton, RemoveButton } =
registry.templates.ButtonTemplates; registry.templates.ButtonTemplates;
const btnStyle: CSSProperties = { const btnStyle: CSSProperties = {
flex: 1, marginBottom: '0.5em',
paddingLeft: 6,
paddingRight: 6,
fontWeight: 'bold',
}; };
const mainColumnWidthSmall = 3;
const mainColumnWidthMedium = 4;
const mainColumnWidthLarge = 7;
return ( return (
<div className={className}> <div className={className}>
<div className={hasToolbar ? 'col-xs-9' : 'col-xs-12'}>{children}</div> <Grid condensed fullWidth>
{hasToolbar && ( <Column
<div className="col-xs-3 array-item-toolbox"> sm={mainColumnWidthSmall}
<div md={mainColumnWidthMedium}
className="btn-group" lg={mainColumnWidthLarge}
style={{ >
display: 'flex', {children}
justifyContent: 'space-around', </Column>
}} {hasToolbar && (
> <Column sm={1} md={1} lg={1}>
{(hasMoveUp || hasMoveDown) && ( <div className="array-item-toolbox">
<MoveUpButton <div className="NOT-btn-group">
style={btnStyle} {(hasMoveUp || hasMoveDown) && (
disabled={disabled || readonly || !hasMoveUp} <MoveUpButton
onClick={onReorderClick(index, index - 1)} style={btnStyle}
uiSchema={uiSchema} disabled={disabled || readonly || !hasMoveUp}
registry={registry} onClick={onReorderClick(index, index - 1)}
/> uiSchema={uiSchema}
)} registry={registry}
{(hasMoveUp || hasMoveDown) && ( />
<MoveDownButton )}
style={btnStyle} {(hasMoveUp || hasMoveDown) && (
disabled={disabled || readonly || !hasMoveDown} <MoveDownButton
onClick={onReorderClick(index, index + 1)} style={btnStyle}
uiSchema={uiSchema} disabled={disabled || readonly || !hasMoveDown}
registry={registry} onClick={onReorderClick(index, index + 1)}
/> uiSchema={uiSchema}
)} registry={registry}
{hasRemove && ( />
<RemoveButton )}
style={btnStyle} {hasRemove && (
disabled={disabled || readonly} <RemoveButton
onClick={onDropIndexClick(index)} style={btnStyle}
uiSchema={uiSchema} disabled={disabled || readonly}
registry={registry} onClick={onDropIndexClick(index)}
/> uiSchema={uiSchema}
)} registry={registry}
</div> />
</div> )}
)} </div>
</div>
</Column>
)}
</Grid>
</div> </div>
); );
} }

View File

@ -85,6 +85,11 @@ export default function BaseInputTemplate<
labelToUse = `${labelToUse}*`; labelToUse = `${labelToUse}*`;
} }
let helperText = null;
if (uiSchema && uiSchema['ui:help']) {
helperText = uiSchema['ui:help'];
}
let invalid = false; let invalid = false;
let errorMessageForField = null; let errorMessageForField = null;
if (rawErrors && rawErrors.length > 0) { if (rawErrors && rawErrors.length > 0) {
@ -102,7 +107,7 @@ export default function BaseInputTemplate<
id={id} id={id}
name={id} name={id}
className="input" className="input"
labelText={labelToUse} helperText={helperText}
invalid={invalid} invalid={invalid}
invalidText={errorMessageForField} invalidText={errorMessageForField}
autoFocus={autofocus} autoFocus={autofocus}

View File

@ -7,10 +7,8 @@ import FormHelperText from '@mui/material/FormHelperText';
* @param props - The `FieldHelpProps` to be rendered * @param props - The `FieldHelpProps` to be rendered
*/ */
export default function FieldHelpTemplate(props: FieldHelpProps) { export default function FieldHelpTemplate(props: FieldHelpProps) {
const { idSchema, help } = props; // ui:help is handled by helperText in all carbon widgets.
if (!help) { // see BaseInputTemplate/BaseInputTemplate.tsx and
return null; // SelectWidget/SelectWidget.tsx
} return null;
const id = `${idSchema.$id}__help`;
return <FormHelperText id={id}>{help}</FormHelperText>;
} }

View File

@ -1,64 +1,57 @@
import React from 'react'; import React from 'react';
import FormControl from '@mui/material/FormControl'; import {
import Typography from '@mui/material/Typography'; FieldTemplateProps,
import { FieldTemplateProps, getTemplate, getUiOptions } from '@rjsf/utils'; FormContextType,
RJSFSchema,
StrictRJSFSchema,
getTemplate,
getUiOptions,
} from '@rjsf/utils';
function FieldTemplate({ import Label from './Label';
id,
children, /** The `FieldTemplate` component is the template used by `SchemaField` to render any field. It renders the field
classNames, * content, (label, description, children, errors and help) inside of a `WrapIfAdditional` component.
disabled, *
displayLabel, * @param props - The `FieldTemplateProps` for this component
hidden, */
label, export default function FieldTemplate<
onDropPropertyClick, T = any,
onKeyChange, S extends StrictRJSFSchema = RJSFSchema,
readonly, F extends FormContextType = any
required, >(props: FieldTemplateProps<T, S, F>) {
rawErrors = [], const {
errors, id,
help, label,
rawDescription, children,
schema, errors,
uiSchema, help,
registry, description,
}: FieldTemplateProps) { hidden,
const uiOptions = getUiOptions(uiSchema); required,
const WrapIfAdditionalTemplate = getTemplate<'WrapIfAdditionalTemplate'>( displayLabel,
'WrapIfAdditionalTemplate',
registry, registry,
uiOptions uiSchema,
); } = props;
const uiOptions = getUiOptions(uiSchema);
const WrapIfAdditionalTemplate = getTemplate<
'WrapIfAdditionalTemplate',
T,
S,
F
>('WrapIfAdditionalTemplate', registry, uiOptions);
if (hidden) { if (hidden) {
return <div style={{ display: 'none' }}>{children}</div>; return <div className="hidden">{children}</div>;
} }
return ( return (
<WrapIfAdditionalTemplate <div className="rjsf-field">
classNames={classNames} <WrapIfAdditionalTemplate {...props}>
disabled={disabled} {displayLabel && <Label label={label} required={required} id={id} />}
id={id} {displayLabel && description ? description : null}
label={label}
onDropPropertyClick={onDropPropertyClick}
onKeyChange={onKeyChange}
readonly={readonly}
required={required}
schema={schema}
uiSchema={uiSchema}
registry={registry}
>
<FormControl fullWidth error={!!rawErrors.length} required={required}>
{children} {children}
{displayLabel && rawDescription ? (
<Typography variant="caption" color="textSecondary">
{rawDescription}
</Typography>
) : null}
{errors} {errors}
{help} {help}
</FormControl> </WrapIfAdditionalTemplate>
</WrapIfAdditionalTemplate> </div>
); );
} }
export default FieldTemplate;

View File

@ -1,55 +1,96 @@
import React from 'react'; import React from 'react';
import IconButton, { import {
IconButtonProps as MuiIconButtonProps, FormContextType,
} from '@mui/material/IconButton'; IconButtonProps,
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; RJSFSchema,
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; StrictRJSFSchema,
import RemoveIcon from '@mui/icons-material/Remove'; } from '@rjsf/utils';
import { IconButtonProps } from '@rjsf/utils';
export default function MuiIconButton(props: IconButtonProps) { // @ts-ignore
const { icon, color, uiSchema, ...otherProps } = props; import { Add, TrashCan, ArrowUp, ArrowDown } from '@carbon/icons-react';
export default function IconButton<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>(props: IconButtonProps<T, S, F>) {
const {
iconType = 'default',
icon,
className,
uiSchema,
registry,
...otherProps
} = props;
// icon string optios: plus, remove, arrow-up, arrow-down
let carbonIcon = (
<p>
Add new <Add />
</p>
);
if (icon === 'remove') {
carbonIcon = <TrashCan />;
}
if (icon === 'arrow-up') {
carbonIcon = <ArrowUp />;
}
if (icon === 'arrow-down') {
carbonIcon = <ArrowDown />;
}
return (
<button
type="button"
className={`btn btn-${iconType} ${className}`}
{...otherProps}
>
{carbonIcon}
</button>
);
}
export function MoveDownButton<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>(props: IconButtonProps<T, S, F>) {
return ( return (
<IconButton <IconButton
{...otherProps}
size="small"
color={color as MuiIconButtonProps['color']}
>
{icon}
</IconButton>
);
}
export function MoveDownButton(props: IconButtonProps) {
return (
<MuiIconButton
title="Move down" title="Move down"
className="array-item-move-down"
{...props} {...props}
icon={<ArrowDownwardIcon fontSize="small" />} icon="arrow-down"
/> />
); );
} }
export function MoveUpButton(props: IconButtonProps) { export function MoveUpButton<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>(props: IconButtonProps<T, S, F>) {
return ( return (
<MuiIconButton <IconButton
title="Move up" title="Move up"
className="array-item-move-up"
{...props} {...props}
icon={<ArrowUpwardIcon fontSize="small" />} icon="arrow-up"
/> />
); );
} }
export function RemoveButton(props: IconButtonProps) { export function RemoveButton<
const { iconType, ...otherProps } = props; T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>(props: IconButtonProps<T, S, F>) {
return ( return (
<MuiIconButton <IconButton
title="Remove" title="Remove"
{...otherProps} className="array-item-remove"
color="error" {...props}
icon={ iconType="danger"
<RemoveIcon fontSize={iconType === 'default' ? undefined : 'small'} /> icon="remove"
}
/> />
); );
} }

View File

@ -41,6 +41,10 @@ function SelectWidget({
} else if (schema && schema.title) { } else if (schema && schema.title) {
labelToUse = schema.title; labelToUse = schema.title;
} }
let helperText = null;
if (uiSchema && uiSchema['ui:help']) {
helperText = uiSchema['ui:help'];
}
if (required) { if (required) {
labelToUse = `${labelToUse}*`; labelToUse = `${labelToUse}*`;
} }
@ -49,16 +53,20 @@ function SelectWidget({
let errorMessageForField = null; let errorMessageForField = null;
if (rawErrors && rawErrors.length > 0) { if (rawErrors && rawErrors.length > 0) {
invalid = true; invalid = true;
errorMessageForField = `${labelToUse.replace(/\*$/, '')} ${rawErrors[0]}`; // errorMessageForField = `${labelToUse.replace(/\*$/, '')} ${rawErrors[0]}`;
errorMessageForField = rawErrors[0];
} }
// maybe use placeholder somehow. it was previously jammed into the helperText field,
// but allowing ui:help to grab that spot seems much more appropriate.
return ( return (
<Select <Select
id={id} id={id}
name={id} name={id}
labelText={labelToUse} labelText=""
select select
helperText={placeholder} helperText={helperText}
value={typeof value === 'undefined' ? emptyValue : value} value={typeof value === 'undefined' ? emptyValue : value}
disabled={disabled || readonly} disabled={disabled || readonly}
autoFocus={autofocus} autoFocus={autofocus}

View File

@ -65,7 +65,7 @@ function TextareaWidget<
let errorMessageForField = null; let errorMessageForField = null;
if (rawErrors && rawErrors.length > 0) { if (rawErrors && rawErrors.length > 0) {
invalid = true; invalid = true;
errorMessageForField = `${labelToUse.replace(/\*$/, '')} ${rawErrors[0]}`; errorMessageForField = rawErrors[0];
} }
return ( return (
@ -74,7 +74,7 @@ function TextareaWidget<
name={id} name={id}
className="form-control" className="form-control"
value={value || ''} value={value || ''}
labelText={labelToUse} labelText=""
placeholder={placeholder} placeholder={placeholder}
required={required} required={required}
disabled={disabled} disabled={disabled}

View File

@ -1,7 +1,3 @@
button.react-json-schema-form-submit-button {
margin-top: 1.5em;
}
.rjsf .header { .rjsf .header {
font-weight: 400; font-weight: 400;
font-size: 20px; font-size: 20px;
@ -17,6 +13,11 @@ button.react-json-schema-form-submit-button {
margin-bottom: 1em; margin-bottom: 1em;
} }
.rjsf .input { /* for some reason it wraps the entire form using FieldTemplate.jsx, which is where we added the rjsf-field thing (which is only intended for fields, not entire forms. hence the double rjsf-field reference, only for rjsf-fields inside rjsf-fields, so we don't get double margin after the last field */
.rjsf .rjsf-field .rjsf-field {
margin-bottom: 2em; margin-bottom: 2em;
} }
.array-item-toolbox {
margin-left: 2em;
}