Merge pull request #118 from sartography/feature/form-styling-fixes
Feature/form styling fixes
This commit is contained in:
commit
8363bda7c9
|
@ -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}
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
|
|
|
@ -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"
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue