Merge pull request #45 from sartography/feature/form_carbon_theme

Feature/form carbon theme
This commit is contained in:
jasquat 2022-11-16 17:14:21 -05:00 committed by GitHub
commit 7bd3ead772
65 changed files with 2016 additions and 118 deletions

View File

@ -0,0 +1 @@
/src/themes/carbon

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,12 @@
"@casl/react": "^3.1.0",
"@ginkgo-bioworks/react-json-schema-form-builder": "^2.9.0",
"@monaco-editor/react": "^4.4.5",
"@rjsf/core": "^4.2.0",
"@mui/material": "^5.10.14",
"@react-icons/all-files": "^4.1.0",
"@rjsf/core": "*",
"@rjsf/mui": "^5.0.0-beta.13",
"@rjsf/utils": "^5.0.0-beta.13",
"@rjsf/validator-ajv8": "^5.0.0-beta.13",
"@tanstack/react-table": "^8.2.2",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
@ -73,9 +78,9 @@
"test": "react-scripts test --coverage",
"t": "npm test -- --watchAll=false",
"eject": "craco eject",
"format": "prettier --write src/**/*.js{,x}",
"lint": "./node_modules/.bin/eslint src *.js",
"lint:fix": "./node_modules/.bin/eslint --fix src *.js"
"format": "prettier --write src/**/*.[tj]s{,x}",
"lint": "./node_modules/.bin/eslint src *.[tj]s{,x}",
"lint:fix": "./node_modules/.bin/eslint --fix src *.[tj]s{,x}"
},
"eslintConfig": {
"extends": [

View File

@ -53,6 +53,7 @@ export default function ButtonWithConfirmation({
secondaryButtonText="Cancel"
onSecondarySubmit={handleConfirmationPromptCancel}
onRequestSubmit={handleConfirmation}
onRequestClose={handleConfirmationPromptCancel}
/>
);
};

View File

@ -165,11 +165,7 @@ export default function ProcessGroupForm({
};
const formButtons = () => {
const buttons = [
<Button kind="secondary" type="submit">
Submit
</Button>,
];
const buttons = [<Button type="submit">Submit</Button>];
if (mode === 'edit') {
buttons.push(
<ButtonWithConfirmation

View File

@ -31,7 +31,7 @@ h1{
border: 1px solid #393939;
}
.cds--btn.button-white-background:hover {
background: #525252;
background: lightgrey;
}
.cds--breadcrumb-item a.cds--link:hover {

View File

@ -3,11 +3,6 @@ import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import {
Button,
Table,
// ExpandableTile,
// TileAboveTheFoldContent,
// TileBelowTheFoldContent,
// TextInput,
// ClickableTile,
// @ts-ignore
} from '@carbon/react';
import { Can } from '@casl/react';

View File

@ -242,6 +242,7 @@ export default function ProcessModelEditDiagram() {
secondaryButtonText="Cancel"
onSecondarySubmit={handleFileNameCancel}
onRequestSubmit={handleFileNameSave}
onRequestClose={handleFileNameCancel}
>
<label>File Name:</label>
<span>
@ -634,6 +635,7 @@ export default function ProcessModelEditDiagram() {
primaryButtonText="Close"
onRequestSubmit={handleScriptEditorClose}
size="lg"
onRequestClose={handleScriptEditorClose}
>
<Editor
height={500}
@ -671,6 +673,7 @@ export default function ProcessModelEditDiagram() {
modalHeading="Edit Markdown"
primaryButtonText="Close"
onRequestSubmit={handleMarkdownEditorClose}
onRequestClose={handleMarkdownEditorClose}
size="lg"
>
<MDEditor

View File

@ -388,6 +388,7 @@ export default function ProcessModelShow() {
const handleFileUploadCancel = () => {
setShowFileUploadModal(false);
setFilesToUpload(null);
};
const handleFileUpload = (event: any) => {
@ -405,6 +406,7 @@ export default function ProcessModelShow() {
});
}
setShowFileUploadModal(false);
setFilesToUpload(null);
};
const fileUploadModal = () => {
@ -432,6 +434,7 @@ export default function ProcessModelShow() {
iconDescription="Delete file"
name=""
multiple={false}
onDelete={() => setFilesToUpload(null)}
onChange={(event: any) => setFilesToUpload(event.target.files)}
/>
</Modal>

View File

@ -132,6 +132,7 @@ export default function ReactFormEditor() {
secondaryButtonText="Cancel"
onSecondarySubmit={handleFileNameCancel}
onRequestSubmit={handleFileNameSave}
onRequestClose={handleFileNameCancel}
>
<label>File Name:</label>
<span>

View File

@ -1,11 +1,13 @@
import { useContext, useEffect, useState } from 'react';
import { Link, useNavigate, useParams } from 'react-router-dom';
import Form from '@rjsf/core';
import validator from '@rjsf/validator-ajv8';
// @ts-ignore
import { Button, Stack } from '@carbon/react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import Form from '../themes/carbon';
import HttpService from '../services/HttpService';
import ErrorContext from '../contexts/ErrorContext';
import { modifyProcessModelPath } from '../helpers';
@ -144,6 +146,7 @@ export default function TaskShow() {
onSubmit={handleFormSubmit}
schema={jsonSchema}
uiSchema={formUiSchema}
validator={validator}
>
{reactFragmentToHideSubmitButton}
</Form>

View File

@ -0,0 +1,17 @@
import React from 'react';
import AddIcon from '@mui/icons-material/Add';
import IconButton from '@mui/material/IconButton';
import { IconButtonProps } from '@rjsf/utils';
const AddButton: React.ComponentType<IconButtonProps> = ({
uiSchema,
...props
}) => {
return (
<IconButton title="Add Item" {...props} color="primary">
<AddIcon />
</IconButton>
);
};
export default AddButton;

View File

@ -0,0 +1,2 @@
export { default } from './AddButton';
export * from './AddButton';

View File

@ -0,0 +1,72 @@
import React, { CSSProperties } from 'react';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import Paper from '@mui/material/Paper';
import { ArrayFieldTemplateItemType } from '@rjsf/utils';
function ArrayFieldItemTemplate(props: ArrayFieldTemplateItemType) {
const {
children,
disabled,
hasToolbar,
hasMoveDown,
hasMoveUp,
hasRemove,
index,
onDropIndexClick,
onReorderClick,
readonly,
uiSchema,
registry,
} = props;
const { MoveDownButton, MoveUpButton, RemoveButton } =
registry.templates.ButtonTemplates;
const btnStyle: CSSProperties = {
flex: 1,
paddingLeft: 6,
paddingRight: 6,
fontWeight: 'bold',
minWidth: 0,
};
return (
<Grid container alignItems="center">
<Grid item xs style={{ overflow: 'auto' }}>
<Box mb={2}>
<Paper elevation={2}>
<Box p={2}>{children}</Box>
</Paper>
</Box>
</Grid>
{hasToolbar && (
<Grid item>
{(hasMoveUp || hasMoveDown) && (
<MoveUpButton
style={btnStyle}
disabled={disabled || readonly || !hasMoveUp}
onClick={onReorderClick(index, index - 1)}
uiSchema={uiSchema}
/>
)}
{(hasMoveUp || hasMoveDown) && (
<MoveDownButton
style={btnStyle}
disabled={disabled || readonly || !hasMoveDown}
onClick={onReorderClick(index, index + 1)}
uiSchema={uiSchema}
/>
)}
{hasRemove && (
<RemoveButton
style={btnStyle}
disabled={disabled || readonly}
onClick={onDropIndexClick(index)}
uiSchema={uiSchema}
/>
)}
</Grid>
)}
</Grid>
);
}
export default ArrayFieldItemTemplate;

View File

@ -0,0 +1,2 @@
export { default } from './ArrayFieldItemTemplate';
export * from './ArrayFieldItemTemplate';

View File

@ -0,0 +1,90 @@
import React from 'react';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import Paper from '@mui/material/Paper';
import {
ArrayFieldTemplateItemType,
ArrayFieldTemplateProps,
getTemplate,
getUiOptions,
} from '@rjsf/utils';
function ArrayFieldTemplate(props: ArrayFieldTemplateProps) {
const {
canAdd,
disabled,
idSchema,
uiSchema,
items,
onAddClick,
readonly,
registry,
required,
schema,
title,
} = props;
const uiOptions = getUiOptions(uiSchema);
const ArrayFieldDescriptionTemplate =
getTemplate<'ArrayFieldDescriptionTemplate'>(
'ArrayFieldDescriptionTemplate',
registry,
uiOptions
);
const ArrayFieldItemTemplate = getTemplate<'ArrayFieldItemTemplate'>(
'ArrayFieldItemTemplate',
registry,
uiOptions
);
const ArrayFieldTitleTemplate = getTemplate<'ArrayFieldTitleTemplate'>(
'ArrayFieldTitleTemplate',
registry,
uiOptions
);
// Button templates are not overridden in the uiSchema
const {
ButtonTemplates: { AddButton },
} = registry.templates;
return (
<Paper elevation={2}>
<Box p={2}>
<ArrayFieldTitleTemplate
idSchema={idSchema}
title={uiOptions.title || title}
schema={schema}
uiSchema={uiSchema}
required={required}
registry={registry}
/>
<ArrayFieldDescriptionTemplate
idSchema={idSchema}
description={uiOptions.description || schema.description}
schema={schema}
uiSchema={uiSchema}
registry={registry}
/>
<Grid container key={`array-item-list-${idSchema.$id}`}>
{items &&
items.map(({ key, ...itemProps }: ArrayFieldTemplateItemType) => (
<ArrayFieldItemTemplate key={key} {...itemProps} />
))}
{canAdd && (
<Grid container justifyContent="flex-end">
<Grid item>
<Box mt={2}>
<AddButton
className="array-item-add"
onClick={onAddClick}
disabled={disabled || readonly}
uiSchema={uiSchema}
/>
</Box>
</Grid>
</Grid>
)}
</Grid>
</Box>
</Paper>
);
}
export default ArrayFieldTemplate;

View File

@ -0,0 +1,2 @@
export { default } from './ArrayFieldTemplate';
export * from './ArrayFieldTemplate';

View File

@ -0,0 +1,114 @@
// @ts-ignore
import { TextInput } from '@carbon/react';
import {
getInputProps,
FormContextType,
RJSFSchema,
StrictRJSFSchema,
WidgetProps,
} from '@rjsf/utils';
import { useCallback } from 'react';
/** 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.
* It can be customized/overridden for other themes or individual implementations as needed.
*
* @param props - The `WidgetProps` for this template
*/
export default function BaseInputTemplate<
T = any,
S extends StrictRJSFSchema = RJSFSchema,
F extends FormContextType = any
>(props: WidgetProps<T, S, F>) {
const {
id,
value,
readonly,
disabled,
autofocus,
label,
onBlur,
onFocus,
onChange,
required,
options,
schema,
uiSchema,
formContext,
registry,
rawErrors,
type,
...rest
} = props;
// Note: since React 15.2.0 we can't forward unknown element attributes, so we
// exclude the "options" and "schema" ones here.
if (!id) {
console.log('No id for', props);
throw new Error(`no id for props ${JSON.stringify(props)}`);
}
const inputProps = {
...rest,
...getInputProps<T, S, F>(schema, type, options),
};
let inputValue;
if (inputProps.type === 'number' || inputProps.type === 'integer') {
inputValue = value || value === 0 ? value : '';
} else {
inputValue = value == null ? '' : value;
}
const _onChange = useCallback(
({ target: { value } }: React.ChangeEvent<HTMLInputElement>) =>
onChange(value === '' ? options.emptyValue : value),
[onChange, options]
);
const _onBlur = useCallback(
({ target: { value } }: React.FocusEvent<HTMLInputElement>) =>
onBlur(id, value),
[onBlur, id]
);
const _onFocus = useCallback(
({ target: { value } }: React.FocusEvent<HTMLInputElement>) =>
onFocus(id, value),
[onFocus, id]
);
let labelToUse = label;
if (uiSchema && uiSchema['ui:title']) {
labelToUse = uiSchema['ui:title'];
} else if (schema && schema.title) {
labelToUse = schema.title;
}
return (
<>
<TextInput
id={id}
name={id}
labelText={labelToUse}
autoFocus={autofocus}
disabled={disabled || readonly}
value={value || value === 0 ? value : ''}
onChange={_onChange}
onBlur={_onBlur}
onFocus={_onFocus}
// eslint-disable-next-line react/jsx-props-no-spreading
{...inputProps}
/>
{Array.isArray(schema.examples) && (
<datalist key={`datalist_${id}`} id={`examples_${id}`}>
{[
...new Set(
schema.examples.concat(schema.default ? [schema.default] : [])
),
].map((example: any) => (
<option key={example} value={example} />
))}
</datalist>
)}
</>
);
}

View File

@ -0,0 +1,2 @@
export { default } from './BaseInputTemplate';
export * from './BaseInputTemplate';

View File

@ -0,0 +1,8 @@
import { ComponentType } from 'react';
import { withTheme, FormProps } from '@rjsf/core';
import Theme from '../Theme';
const CarbonForm: ComponentType<FormProps> = withTheme(Theme);
export default CarbonForm;

View File

@ -0,0 +1,2 @@
export { default } from './CarbonForm';
export * from './CarbonForm';

View File

@ -0,0 +1,52 @@
import React from 'react';
import Checkbox from '@mui/material/Checkbox';
import FormControlLabel from '@mui/material/FormControlLabel';
import { schemaRequiresTrueValue, WidgetProps } from '@rjsf/utils';
function CheckboxWidget(props: WidgetProps) {
const {
schema,
id,
value,
disabled,
readonly,
label,
autofocus,
onChange,
onBlur,
onFocus,
} = 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 _onBlur = ({
target: { value },
}: React.FocusEvent<HTMLButtonElement>) => onBlur(id, value);
const _onFocus = ({
target: { value },
}: React.FocusEvent<HTMLButtonElement>) => onFocus(id, value);
return (
<FormControlLabel
control={
<Checkbox
id={id}
name={id}
checked={typeof value === 'undefined' ? false : Boolean(value)}
required={required}
disabled={disabled || readonly}
autoFocus={autofocus}
onChange={_onChange}
onBlur={_onBlur}
onFocus={_onFocus}
/>
}
label={label || ''}
/>
);
}
export default CheckboxWidget;

View File

@ -0,0 +1,2 @@
export { default } from './CheckboxWidget';
export * from './CheckboxWidget';

View File

@ -0,0 +1,93 @@
import React from 'react';
import Checkbox from '@mui/material/Checkbox';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormGroup from '@mui/material/FormGroup';
import FormLabel from '@mui/material/FormLabel';
import { WidgetProps } from '@rjsf/utils';
const selectValue = (value: any, selected: any, all: any) => {
const at = all.indexOf(value);
const updated = selected.slice(0, at).concat(value, selected.slice(at));
// As inserting values at predefined index positions doesn't work with empty
// arrays, we need to reorder the updated selection to match the initial order
return updated.sort((a: any, b: any) => all.indexOf(a) > all.indexOf(b));
};
const deselectValue = (value: any, selected: any) => {
return selected.filter((v: any) => v !== value);
};
function CheckboxesWidget({
schema,
label,
id,
disabled,
options,
value,
autofocus,
readonly,
required,
onChange,
onBlur,
onFocus,
}: WidgetProps) {
const { enumOptions, enumDisabled, inline } = options;
const _onChange =
(option: any) =>
({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) => {
const all = (enumOptions as any).map(({ value }: any) => value);
if (checked) {
onChange(selectValue(option.value, value, all));
} else {
onChange(deselectValue(option.value, value));
}
};
const _onBlur = ({
target: { value },
}: React.FocusEvent<HTMLButtonElement>) => onBlur(id, value);
const _onFocus = ({
target: { value },
}: React.FocusEvent<HTMLButtonElement>) => onFocus(id, value);
return (
<>
<FormLabel required={required} htmlFor={id}>
{label || schema.title}
</FormLabel>
<FormGroup id={id} row={!!inline}>
{Array.isArray(enumOptions) &&
enumOptions.map((option, index: number) => {
const checked = value.indexOf(option.value) !== -1;
const itemDisabled =
Array.isArray(enumDisabled) &&
enumDisabled.indexOf(option.value) !== -1;
const checkbox = (
<Checkbox
id={`${id}-${option.value}`}
name={id}
checked={checked}
disabled={disabled || itemDisabled || readonly}
autoFocus={autofocus && index === 0}
onChange={_onChange(option)}
onBlur={_onBlur}
onFocus={_onFocus}
/>
);
return (
<FormControlLabel
control={checkbox}
key={option.value}
label={option.label}
/>
);
})}
</FormGroup>
</>
);
}
export default CheckboxesWidget;

View File

@ -0,0 +1,2 @@
export { default } from './CheckboxesWidget';
export * from './CheckboxesWidget';

View File

@ -0,0 +1,29 @@
import React from 'react';
import { getTemplate, localToUTC, utcToLocal, WidgetProps } from '@rjsf/utils';
function DateTimeWidget(props: WidgetProps) {
const { options, registry } = props;
const BaseInputTemplate = getTemplate<'BaseInputTemplate'>(
'BaseInputTemplate',
registry,
options
);
const value = utcToLocal(props.value);
const onChange = (value: any) => {
props.onChange(localToUTC(value));
};
return (
<BaseInputTemplate
type="datetime-local"
InputLabelProps={{
shrink: true,
}}
{...props}
value={value}
onChange={onChange}
/>
);
}
export default DateTimeWidget;

View File

@ -0,0 +1,2 @@
export { default } from './DateTimeWidget';
export * from './DateTimeWidget';

View File

@ -0,0 +1,22 @@
import React from 'react';
import { getTemplate, WidgetProps } from '@rjsf/utils';
function DateWidget(props: WidgetProps) {
const { options, registry } = props;
const BaseInputTemplate = getTemplate<'BaseInputTemplate'>(
'BaseInputTemplate',
registry,
options
);
return (
<BaseInputTemplate
type="date"
InputLabelProps={{
shrink: true,
}}
{...props}
/>
);
}
export default DateWidget;

View File

@ -0,0 +1,2 @@
export { default } from './DateWidget';
export * from './DateWidget';

View File

@ -0,0 +1,17 @@
import React from 'react';
import Typography from '@mui/material/Typography';
import { DescriptionFieldProps } from '@rjsf/utils';
function DescriptionField({ description, id }: DescriptionFieldProps) {
if (description) {
return (
<Typography id={id} variant="subtitle2" style={{ marginTop: '5px' }}>
{description}
</Typography>
);
}
return null;
}
export default DescriptionField;

View File

@ -0,0 +1,2 @@
export { default } from './DescriptionField';
export * from './DescriptionField';

View File

@ -0,0 +1,16 @@
import { ErrorListProps } from '@rjsf/utils';
// @ts-ignore
import { Tag } from '@carbon/react';
function ErrorList({ errors }: ErrorListProps) {
if (errors) {
return (
<Tag type="red" size="md" title="Fill Required Fields">
Please fill out required fields
</Tag>
);
}
return null;
}
export default ErrorList;

View File

@ -0,0 +1,2 @@
export { default } from './ErrorList';
export * from './ErrorList';

View File

@ -0,0 +1,9 @@
import { FieldErrorProps } from '@rjsf/utils';
/** The `FieldErrorTemplate` component renders the errors local to the particular field
*
* @param props - The `FieldErrorProps` for the errors being rendered
*/
export default function FieldErrorTemplate(_props: FieldErrorProps) {
return null;
}

View File

@ -0,0 +1,2 @@
export { default } from './FieldErrorTemplate';
export * from './FieldErrorTemplate';

View File

@ -0,0 +1,16 @@
import React from 'react';
import { FieldHelpProps } from '@rjsf/utils';
import FormHelperText from '@mui/material/FormHelperText';
/** The `FieldHelpTemplate` component renders any help desired for a field
*
* @param props - The `FieldHelpProps` to be rendered
*/
export default function FieldHelpTemplate(props: FieldHelpProps) {
const { idSchema, help } = props;
if (!help) {
return null;
}
const id = `${idSchema.$id}__help`;
return <FormHelperText id={id}>{help}</FormHelperText>;
}

View File

@ -0,0 +1,2 @@
export { default } from './FieldHelpTemplate';
export * from './FieldHelpTemplate';

View File

@ -0,0 +1,64 @@
import React from 'react';
import FormControl from '@mui/material/FormControl';
import Typography from '@mui/material/Typography';
import { FieldTemplateProps, getTemplate, getUiOptions } from '@rjsf/utils';
function FieldTemplate({
id,
children,
classNames,
disabled,
displayLabel,
hidden,
label,
onDropPropertyClick,
onKeyChange,
readonly,
required,
rawErrors = [],
errors,
help,
rawDescription,
schema,
uiSchema,
registry,
}: FieldTemplateProps) {
const uiOptions = getUiOptions(uiSchema);
const WrapIfAdditionalTemplate = getTemplate<'WrapIfAdditionalTemplate'>(
'WrapIfAdditionalTemplate',
registry,
uiOptions
);
if (hidden) {
return <div style={{ display: 'none' }}>{children}</div>;
}
return (
<WrapIfAdditionalTemplate
classNames={classNames}
disabled={disabled}
id={id}
label={label}
onDropPropertyClick={onDropPropertyClick}
onKeyChange={onKeyChange}
readonly={readonly}
required={required}
schema={schema}
uiSchema={uiSchema}
registry={registry}
>
<FormControl fullWidth error={!!rawErrors.length} required={required}>
{children}
{displayLabel && rawDescription ? (
<Typography variant="caption" color="textSecondary">
{rawDescription}
</Typography>
) : null}
{errors}
{help}
</FormControl>
</WrapIfAdditionalTemplate>
);
}
export default FieldTemplate;

View File

@ -0,0 +1,2 @@
export { default } from './FieldTemplate';
export * from './FieldTemplate';

View File

@ -0,0 +1,55 @@
import React from 'react';
import IconButton, {
IconButtonProps as MuiIconButtonProps,
} from '@mui/material/IconButton';
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward';
import RemoveIcon from '@mui/icons-material/Remove';
import { IconButtonProps } from '@rjsf/utils';
export default function MuiIconButton(props: IconButtonProps) {
const { icon, color, uiSchema, ...otherProps } = props;
return (
<IconButton
{...otherProps}
size="small"
color={color as MuiIconButtonProps['color']}
>
{icon}
</IconButton>
);
}
export function MoveDownButton(props: IconButtonProps) {
return (
<MuiIconButton
title="Move down"
{...props}
icon={<ArrowDownwardIcon fontSize="small" />}
/>
);
}
export function MoveUpButton(props: IconButtonProps) {
return (
<MuiIconButton
title="Move up"
{...props}
icon={<ArrowUpwardIcon fontSize="small" />}
/>
);
}
export function RemoveButton(props: IconButtonProps) {
const { iconType, ...otherProps } = props;
return (
<MuiIconButton
title="Remove"
{...otherProps}
color="error"
icon={
<RemoveIcon fontSize={iconType === 'default' ? undefined : 'small'} />
}
/>
);
}

View File

@ -0,0 +1,2 @@
export { default } from './IconButton';
export * from './IconButton';

View File

@ -0,0 +1,89 @@
import React from 'react';
import Grid from '@mui/material/Grid';
import {
ObjectFieldTemplateProps,
canExpand,
getTemplate,
getUiOptions,
} from '@rjsf/utils';
function ObjectFieldTemplate({
description,
title,
properties,
required,
disabled,
readonly,
uiSchema,
idSchema,
schema,
formData,
onAddClick,
registry,
}: ObjectFieldTemplateProps) {
const uiOptions = getUiOptions(uiSchema);
const TitleFieldTemplate = getTemplate<'TitleFieldTemplate'>(
'TitleFieldTemplate',
registry,
uiOptions
);
const DescriptionFieldTemplate = getTemplate<'DescriptionFieldTemplate'>(
'DescriptionFieldTemplate',
registry,
uiOptions
);
// Button templates are not overridden in the uiSchema
const {
ButtonTemplates: { AddButton },
} = registry.templates;
return (
<>
{(uiOptions.title || title) && (
<TitleFieldTemplate
id={`${idSchema.$id}-title`}
title={title}
required={required}
schema={schema}
uiSchema={uiSchema}
registry={registry}
/>
)}
{(uiOptions.description || description) && (
<DescriptionFieldTemplate
id={`${idSchema.$id}-description`}
description={uiOptions.description || description!}
schema={schema}
uiSchema={uiSchema}
registry={registry}
/>
)}
<Grid container spacing={2} style={{ marginTop: '10px' }}>
{properties.map((element, index) =>
// Remove the <Grid> if the inner element is hidden as the <Grid>
// itself would otherwise still take up space.
element.hidden ? (
element.content
) : (
<Grid item xs={12} key={index} style={{ marginBottom: '10px' }}>
{element.content}
</Grid>
)
)}
{canExpand(schema, uiSchema, formData) && (
<Grid container justifyContent="flex-end">
<Grid item>
<AddButton
className="object-property-expand"
onClick={onAddClick(schema)}
disabled={disabled || readonly}
uiSchema={uiSchema}
/>
</Grid>
</Grid>
)}
</Grid>
</>
);
}
export default ObjectFieldTemplate;

View File

@ -0,0 +1,2 @@
export { default } from './ObjectFieldTemplate';
export * from './ObjectFieldTemplate';

View File

@ -0,0 +1,62 @@
import React from 'react';
// @ts-ignore
import { RadioButtonGroup, RadioButton } from '@carbon/react';
import { WidgetProps } from '@rjsf/utils';
function RadioWidget({
id,
schema,
options,
value,
required,
disabled,
readonly,
label,
onChange,
onBlur,
onFocus,
}: WidgetProps) {
const { enumOptions, enumDisabled } = options;
const localOnChange = (_: any, value: any) =>
onChange(schema.type == 'boolean' ? value !== 'false' : value);
const _onBlur = ({ target: { value } }: React.FocusEvent<HTMLInputElement>) =>
onBlur(id, value);
const _onFocus = ({
target: { value },
}: React.FocusEvent<HTMLInputElement>) => onFocus(id, value);
const row = options ? options.inline : false;
return (
<RadioButtonGroup
orientation="vertical"
id={id}
name={id}
value={`${value}`}
row={row as boolean}
legendText={label || schema.title}
onChange={localOnChange}
onBlur={_onBlur}
onFocus={_onFocus}
>
{Array.isArray(enumOptions) &&
enumOptions.map((option) => {
const itemDisabled =
Array.isArray(enumDisabled) &&
enumDisabled.indexOf(option.value) !== -1;
return (
<RadioButton
labelText={`${option.label}`}
value={`${option.value}`}
id={`${id}-${option.value}`}
key={option.value}
disabled={disabled || itemDisabled || readonly}
/>
);
})}
</RadioButtonGroup>
);
}
export default RadioWidget;

View File

@ -0,0 +1,2 @@
export { default } from './RadioWidget';
export * from './RadioWidget';

View File

@ -0,0 +1,47 @@
import React from 'react';
import FormLabel from '@mui/material/FormLabel';
import Slider from '@mui/material/Slider';
import { WidgetProps, rangeSpec } from '@rjsf/utils';
function RangeWidget({
value,
readonly,
disabled,
onBlur,
onFocus,
options,
schema,
onChange,
required,
label,
id,
}: WidgetProps) {
const sliderProps = { value, label, id, name: id, ...rangeSpec(schema) };
const _onChange = (_: any, value?: number | number[]) => {
onChange(value ? options.emptyValue : value);
};
const _onBlur = ({ target: { value } }: React.FocusEvent<HTMLInputElement>) =>
onBlur(id, value);
const _onFocus = ({
target: { value },
}: React.FocusEvent<HTMLInputElement>) => onFocus(id, value);
return (
<>
<FormLabel required={required} id={id}>
{label}
</FormLabel>
<Slider
disabled={disabled || readonly}
onChange={_onChange}
onBlur={_onBlur}
onFocus={_onFocus}
valueLabelDisplay="auto"
{...sliderProps}
/>
</>
);
}
export default RangeWidget;

View File

@ -0,0 +1,2 @@
export { default } from './RangeWidget';
export * from './RangeWidget';

View File

@ -0,0 +1,76 @@
// @ts-ignore
import { Select, SelectItem } from '@carbon/react';
import { WidgetProps, processSelectValue } from '@rjsf/utils';
function SelectWidget({
schema,
id,
options,
label,
required,
disabled,
readonly,
value,
multiple,
autofocus,
onChange,
onBlur,
onFocus,
uiSchema,
rawErrors = [],
}: WidgetProps) {
const { enumOptions, enumDisabled } = options;
const emptyValue = multiple ? [] : '';
const _onChange = ({
target: { value },
}: React.ChangeEvent<{ name?: string; value: unknown }>) =>
onChange(processSelectValue(schema, value, options));
const _onBlur = ({ target: { value } }: React.FocusEvent<HTMLInputElement>) =>
onBlur(id, processSelectValue(schema, value, options));
const _onFocus = ({
target: { value },
}: 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;
}
if (required) {
labelToUse = `${labelToUse}*`;
}
return (
<Select
id={id}
name={id}
labelText={labelToUse}
select
value={typeof value === 'undefined' ? emptyValue : value}
disabled={disabled || readonly}
autoFocus={autofocus}
error={rawErrors.length > 0}
onChange={_onChange}
onBlur={_onBlur}
onFocus={_onFocus}
InputLabelProps={{
shrink: true,
}}
SelectProps={{
multiple: typeof multiple === 'undefined' ? false : multiple,
}}
>
{(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} />;
})}
</Select>
);
}
export default SelectWidget;

View File

@ -0,0 +1,2 @@
export { default } from './SelectWidget';
export * from './SelectWidget';

View File

@ -0,0 +1,30 @@
import React from 'react';
// import Box from '@mui/material/Box';
// @ts-ignore
import { Button } from '@carbon/react';
import { SubmitButtonProps, getSubmitButtonOptions } from '@rjsf/utils';
// const SubmitButton: React.ComponentType<SubmitButtonProps> = (props) => {
function SubmitButton(props: SubmitButtonProps) {
const { uiSchema } = props;
const {
submitText,
norender,
props: submitButtonProps,
} = getSubmitButtonOptions(uiSchema);
if (norender) {
return null;
}
return (
<Button
className="react-json-schema-form-submit-button"
type="submit"
// eslint-disable-next-line react/jsx-props-no-spreading
{...submitButtonProps}
>
{submitText}
</Button>
);
}
export default SubmitButton;

View File

@ -0,0 +1,2 @@
export { default } from './SubmitButton';
export * from './SubmitButton';

View File

@ -0,0 +1,35 @@
import AddButton from '../AddButton';
import ArrayFieldItemTemplate from '../ArrayFieldItemTemplate';
import ArrayFieldTemplate from '../ArrayFieldTemplate';
import BaseInputTemplate from '../BaseInputTemplate';
import DescriptionField from '../DescriptionField';
import ErrorList from '../ErrorList';
import { MoveDownButton, MoveUpButton, RemoveButton } from '../IconButton';
import FieldErrorTemplate from '../FieldErrorTemplate';
import FieldHelpTemplate from '../FieldHelpTemplate';
import FieldTemplate from '../FieldTemplate';
import ObjectFieldTemplate from '../ObjectFieldTemplate';
import SubmitButton from '../SubmitButton';
import TitleField from '../TitleField';
import WrapIfAdditionalTemplate from '../WrapIfAdditionalTemplate';
export default {
ArrayFieldItemTemplate,
ArrayFieldTemplate,
BaseInputTemplate,
ButtonTemplates: {
AddButton,
MoveDownButton,
MoveUpButton,
RemoveButton,
SubmitButton,
},
DescriptionFieldTemplate: DescriptionField,
ErrorListTemplate: ErrorList,
FieldErrorTemplate,
FieldHelpTemplate,
FieldTemplate,
ObjectFieldTemplate,
TitleFieldTemplate: TitleField,
WrapIfAdditionalTemplate,
};

View File

@ -0,0 +1,2 @@
export { default } from './Templates';
export * from './Templates';

View File

@ -0,0 +1,20 @@
import React from 'react';
import { getTemplate, WidgetProps } from '@rjsf/utils';
function TextareaWidget(props: WidgetProps) {
const { options, registry } = props;
const BaseInputTemplate = getTemplate<'BaseInputTemplate'>(
'BaseInputTemplate',
registry,
options
);
let rows: string | number = 5;
if (typeof options.rows === 'string' || typeof options.rows === 'number') {
rows = options.rows;
}
return <BaseInputTemplate {...props} multiline rows={rows} />;
}
export default TextareaWidget;

View File

@ -0,0 +1,2 @@
export { default } from './TextareaWidget';
export * from './TextareaWidget';

View File

@ -0,0 +1,11 @@
import { ThemeProps } from '@rjsf/core';
import Templates from '../Templates';
import Widgets from '../Widgets';
const Theme: ThemeProps = {
templates: Templates,
widgets: Widgets,
};
export default Theme;

View File

@ -0,0 +1,2 @@
export { default } from './Theme';
export * from './Theme';

View File

@ -0,0 +1,16 @@
import React from 'react';
import Box from '@mui/material/Box';
import Divider from '@mui/material/Divider';
import Typography from '@mui/material/Typography';
import { TitleFieldProps } from '@rjsf/utils';
function TitleField({ id, title }: TitleFieldProps) {
return (
<Box id={id} mb={1} mt={1}>
<Typography variant="h5">{title}</Typography>
<Divider />
</Box>
);
}
export default TitleField;

View File

@ -0,0 +1,2 @@
export { default } from './TitleField';
export * from './TitleField';

View File

@ -0,0 +1,19 @@
import CheckboxWidget from '../CheckboxWidget/CheckboxWidget';
import CheckboxesWidget from '../CheckboxesWidget/CheckboxesWidget';
import DateWidget from '../DateWidget/DateWidget';
import DateTimeWidget from '../DateTimeWidget/DateTimeWidget';
import RadioWidget from '../RadioWidget/RadioWidget';
import RangeWidget from '../RangeWidget/RangeWidget';
import SelectWidget from '../SelectWidget/SelectWidget';
import TextareaWidget from '../TextareaWidget/TextareaWidget';
export default {
CheckboxWidget,
CheckboxesWidget,
DateWidget,
DateTimeWidget,
RadioWidget,
RangeWidget,
SelectWidget,
TextareaWidget,
};

View File

@ -0,0 +1,2 @@
export { default } from './Widgets';
export * from './Widgets';

View File

@ -0,0 +1,80 @@
import React, { CSSProperties } from 'react';
import FormControl from '@mui/material/FormControl';
import Grid from '@mui/material/Grid';
import InputLabel from '@mui/material/InputLabel';
import Input from '@mui/material/OutlinedInput';
import {
ADDITIONAL_PROPERTY_FLAG,
WrapIfAdditionalTemplateProps,
} from '@rjsf/utils';
function WrapIfAdditionalTemplate({
children,
classNames,
disabled,
id,
label,
onDropPropertyClick,
onKeyChange,
readonly,
required,
schema,
uiSchema,
registry,
}: WrapIfAdditionalTemplateProps) {
// Button templates are not overridden in the uiSchema
const { RemoveButton } = registry.templates.ButtonTemplates;
const keyLabel = `${label} Key`; // i18n ?
const additional = ADDITIONAL_PROPERTY_FLAG in schema;
const btnStyle: CSSProperties = {
flex: 1,
paddingLeft: 6,
paddingRight: 6,
fontWeight: 'bold',
};
if (!additional) {
return <div className={classNames}>{children}</div>;
}
const handleBlur = ({ target }: React.FocusEvent<HTMLInputElement>) =>
onKeyChange(target.value);
return (
<Grid
container
key={`${id}-key`}
alignItems="center"
spacing={2}
className={classNames}
>
<Grid item xs>
<FormControl fullWidth required={required}>
<InputLabel>{keyLabel}</InputLabel>
<Input
defaultValue={label}
disabled={disabled || readonly}
id={`${id}-key`}
name={`${id}-key`}
onBlur={!readonly ? handleBlur : undefined}
type="text"
/>
</FormControl>
</Grid>
<Grid item xs>
{children}
</Grid>
<Grid item>
<RemoveButton
iconType="default"
style={btnStyle}
disabled={disabled || readonly}
onClick={onDropPropertyClick(label)}
uiSchema={uiSchema}
/>
</Grid>
</Grid>
);
}
export default WrapIfAdditionalTemplate;

View File

@ -0,0 +1,2 @@
export { default } from './WrapIfAdditionalTemplate';
export * from './WrapIfAdditionalTemplate';

View File

@ -0,0 +1,3 @@
button.react-json-schema-form-submit-button {
margin-top: 1.5em;
}

View File

@ -0,0 +1,9 @@
import CarbonForm from './CarbonForm/CarbonForm';
import './index.css';
export { default as Form } from './CarbonForm';
export { default as Templates } from './Templates';
export { default as Theme } from './Theme';
export { default as Widgets } from './Widgets';
export default CarbonForm;