mirror of
https://github.com/sartography/spiff-arena.git
synced 2025-01-11 18:14:20 +00:00
Feature/side by side layout (#858)
* WIP: work for side-by-side layout in json forms w/ burnettk * added support in ObjectFieldTemplate to support side-by-side layout w/ burnettk * added a restricted grid template for half width forms * display the error message if field mentioned in ui:layout but the field does not exist * added docs for side-by-side layout w/ burnettk * update script --------- Co-authored-by: jasquat <jasquat@users.noreply.github.com> Co-authored-by: burnettk <burnettk@users.noreply.github.com>
This commit is contained in:
parent
4f5a590e1e
commit
d5c5bc12ae
@ -170,6 +170,59 @@ This is an example where end_date must be after start_date:
|
||||
These enhancements provide you with more flexibility and control when building forms in SpiffArena.
|
||||
By using these features, you can create dynamic, validated forms that enhance the user experience and support your business processes effectively.
|
||||
|
||||
### Display Fields Side-By-Side on Same Row
|
||||
|
||||
By default, all form fields will be laid out one on top of the other.
|
||||
In some cases, it might be more user-friendly to put two or more fields next to each other on the same conceptual "row."
|
||||
Perhaps, you want to let a user fill out a name, and have First Name and Last Name next to each other.
|
||||
Don't actually do this; use Full name as a single field. :)
|
||||
But in some other case where you actually want to have fields laid out horizontally instead of vertically, do the following:
|
||||
|
||||
Example form schema:
|
||||
|
||||
{
|
||||
"firstName": {
|
||||
"type": "string",
|
||||
},
|
||||
lastName": {
|
||||
"type": "string",
|
||||
}
|
||||
}
|
||||
|
||||
Example uiSchema:
|
||||
|
||||
{
|
||||
"ui:layout": [
|
||||
{
|
||||
"firstName": {
|
||||
"sm": 2,
|
||||
"md": 2,
|
||||
"lg": 4
|
||||
},
|
||||
"lastName": {
|
||||
"sm": 2,
|
||||
"md": 2,
|
||||
"lg": 4
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
In this case, we are saying that we want firstName and lastName in the same row, since there is only one "row" in the UI layout (one element of the array).
|
||||
We are saying that firstName should take up 4 columns when a large display is used.
|
||||
The lastName also takes up 4 columns, which fills up the whole row, which has 8 columns available for large displays.
|
||||
Medium displays have 5 columns available and small displays have 4.
|
||||
If you just specific a uiSchema like this, it will figure out the column widths for you:
|
||||
|
||||
{
|
||||
"ui:layout": [
|
||||
{
|
||||
"firstName": {},
|
||||
"lastName": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
### 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.
|
||||
|
@ -7,6 +7,6 @@ function error_handler() {
|
||||
trap 'error_handler ${LINENO} $?' ERR
|
||||
set -o errtrace -o errexit -o nounset -o pipefail
|
||||
|
||||
gitc Building_Diagrams/sub-processes_and_call_activities.md
|
||||
python bin/gpt-proofread.py Building_Diagrams/sub-processes_and_call_activities.md
|
||||
mv Building_Diagrams/sub-processes_and_call_activities.qmd Building_Diagrams/sub-processes_and_call_activities.md
|
||||
gitc Building_Diagrams/Forms.md
|
||||
python bin/gpt-proofread.py Building_Diagrams/Forms.md
|
||||
mv Building_Diagrams/Forms.qmd Building_Diagrams/Forms.md
|
||||
|
2
docs/bin/gpt-requirements.txt
Normal file
2
docs/bin/gpt-requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
langchain
|
||||
openai
|
@ -7,6 +7,7 @@ import DateRangePickerWidget from '../rjsf/custom_widgets/DateRangePicker/DateRa
|
||||
import TypeaheadWidget from '../rjsf/custom_widgets/TypeaheadWidget/TypeaheadWidget';
|
||||
import MarkDownFieldWidget from '../rjsf/custom_widgets/MarkDownFieldWidget/MarkDownFieldWidget';
|
||||
import NumericRangeField from '../rjsf/custom_widgets/NumericRangeField/NumericRangeField';
|
||||
import ObjectFieldRestrictedGridTemplate from '../rjsf/custom_templates/ObjectFieldRestrictGridTemplate';
|
||||
|
||||
enum DateCheckType {
|
||||
minimum = 'minimum',
|
||||
@ -24,6 +25,7 @@ type OwnProps = {
|
||||
onSubmit?: any;
|
||||
children?: ReactNode;
|
||||
noValidate?: boolean;
|
||||
restrictedWidth?: boolean;
|
||||
};
|
||||
|
||||
export default function CustomForm({
|
||||
@ -36,6 +38,7 @@ export default function CustomForm({
|
||||
onSubmit,
|
||||
children,
|
||||
noValidate = false,
|
||||
restrictedWidth = false,
|
||||
}: OwnProps) {
|
||||
// set in uiSchema using the "ui:widget" key for a property
|
||||
const rjsfWidgets = {
|
||||
@ -45,10 +48,15 @@ export default function CustomForm({
|
||||
};
|
||||
|
||||
// set in uiSchema using the "ui:field" key for a property
|
||||
const fields: RegistryFieldsType = {
|
||||
const rjsfFields: RegistryFieldsType = {
|
||||
'numeric-range': NumericRangeField,
|
||||
};
|
||||
|
||||
const rjsfTemplates: any = {};
|
||||
if (restrictedWidth) {
|
||||
rjsfTemplates.ObjectFieldTemplate = ObjectFieldRestrictedGridTemplate;
|
||||
}
|
||||
|
||||
const formatDateString = (dateString?: string) => {
|
||||
let dateObject = new Date();
|
||||
if (dateString) {
|
||||
@ -362,7 +370,8 @@ export default function CustomForm({
|
||||
validator={validator}
|
||||
customValidate={customValidate}
|
||||
noValidate={noValidate}
|
||||
fields={fields}
|
||||
fields={rjsfFields}
|
||||
templates={rjsfTemplates}
|
||||
omitExtraData
|
||||
>
|
||||
{children}
|
||||
|
@ -499,6 +499,7 @@ export default function ReactFormBuilder({
|
||||
onChange={(e: any) => updateData(e.formData)}
|
||||
schema={postJsonSchema}
|
||||
uiSchema={postJsonUI}
|
||||
restrictedWidth
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</Column>
|
||||
|
@ -1,26 +1,67 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
FormContextType,
|
||||
ObjectFieldTemplatePropertyType,
|
||||
ObjectFieldTemplateProps,
|
||||
RJSFSchema,
|
||||
StrictRJSFSchema,
|
||||
canExpand,
|
||||
FormContextType,
|
||||
getTemplate,
|
||||
getUiOptions,
|
||||
ObjectFieldTemplateProps,
|
||||
ObjectFieldTemplatePropertyType,
|
||||
RJSFSchema,
|
||||
StrictRJSFSchema,
|
||||
} from '@rjsf/utils';
|
||||
import { Grid, Column } from '@carbon/react';
|
||||
|
||||
/** The `ObjectFieldTemplate` is the template to use to render all the inner properties of an object along with the
|
||||
* title and description if available. If the object is expandable, then an `AddButton` is also rendered after all
|
||||
* the properties.
|
||||
/* usage: add ui:layout to an object in the uiSchema
|
||||
*
|
||||
* @param props - The `ObjectFieldTemplateProps` for this component
|
||||
* If using ui:layout then ALL fields must be specified in the desired order.
|
||||
* The sm, md, and lg options match the Column options for Carbon theme. So they
|
||||
* specify how many grid columns the field takes up.
|
||||
*
|
||||
* Example uiSchema:
|
||||
*
|
||||
* {
|
||||
* "ui:layout": [
|
||||
* {
|
||||
* "firstName": {
|
||||
* "sm": 2,
|
||||
* "md": 2,
|
||||
* "lg": 4
|
||||
* },
|
||||
* "lastName": {
|
||||
* "sm": 2,
|
||||
* "md": 2,
|
||||
* "lg": 4
|
||||
* }
|
||||
* },
|
||||
* {
|
||||
* "user": {}
|
||||
* },
|
||||
* {
|
||||
* "details": {}
|
||||
* }
|
||||
* ],
|
||||
* "user": {
|
||||
* "ui:layout": [
|
||||
* { "username": {}, "password": {} }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
||||
// these are not used by rjsf but can be passed in if calling this template directly.
|
||||
type customProps = {
|
||||
defaultSm: number;
|
||||
defaultMd: number;
|
||||
defaultLg: number;
|
||||
};
|
||||
|
||||
export default function ObjectFieldTemplate<
|
||||
T = any,
|
||||
S extends StrictRJSFSchema = RJSFSchema,
|
||||
F extends FormContextType = any
|
||||
>(props: ObjectFieldTemplateProps<T, S, F>) {
|
||||
>(
|
||||
props: ObjectFieldTemplateProps<T, S, F>,
|
||||
{ defaultSm = 4, defaultMd = 8, defaultLg = 16 }: customProps
|
||||
) {
|
||||
const {
|
||||
description,
|
||||
disabled,
|
||||
@ -51,6 +92,49 @@ export default function ObjectFieldTemplate<
|
||||
const {
|
||||
ButtonTemplates: { AddButton },
|
||||
} = registry.templates;
|
||||
|
||||
const layout = (uiSchema as any)['ui:layout'];
|
||||
const schemaToUse = schema as any;
|
||||
|
||||
let innerElements = null;
|
||||
|
||||
if (layout) {
|
||||
innerElements = layout.map((row: any) => {
|
||||
const numberOfColumns = Object.keys(row).length;
|
||||
return (
|
||||
<Grid condensed fullWidth>
|
||||
{Object.keys(row).map((name) => {
|
||||
const element: any = properties.find((property: any) => {
|
||||
return property.name === name;
|
||||
});
|
||||
if (schemaToUse.properties[name]) {
|
||||
const { sm, md, lg } = row[name];
|
||||
return (
|
||||
<Column
|
||||
className="side-by-side-column"
|
||||
sm={sm || Math.floor(defaultSm / numberOfColumns)}
|
||||
md={md || Math.floor(defaultMd / numberOfColumns)}
|
||||
lg={lg || Math.floor(defaultLg / numberOfColumns)}
|
||||
>
|
||||
{element.content}
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="error-message">
|
||||
{`ERROR: '${name}' property was specified in the UI Schema's ui:layout, but property does not exist in the json schema.`}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
);
|
||||
});
|
||||
} else {
|
||||
innerElements = properties.map(
|
||||
(prop: ObjectFieldTemplatePropertyType) => prop.content
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<fieldset id={idSchema.$id}>
|
||||
{(options.title || title) && (
|
||||
@ -72,7 +156,9 @@ export default function ObjectFieldTemplate<
|
||||
registry={registry}
|
||||
/>
|
||||
)}
|
||||
{properties.map((prop: ObjectFieldTemplatePropertyType) => prop.content)}
|
||||
|
||||
{innerElements}
|
||||
|
||||
{canExpand<T, S, F>(schema, uiSchema, formData) && (
|
||||
<AddButton
|
||||
className="object-property-expand"
|
||||
|
@ -108,3 +108,7 @@
|
||||
line-height: var(--cds-helper-text-01-line-height, 1.33333);
|
||||
letter-spacing: var(--cds-helper-text-01-letter-spacing, 0.32px);
|
||||
}
|
||||
|
||||
.rjsf .side-by-side-column:not(:last-child) {
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
import {
|
||||
FormContextType,
|
||||
ObjectFieldTemplateProps,
|
||||
RJSFSchema,
|
||||
StrictRJSFSchema,
|
||||
} from '@rjsf/utils';
|
||||
import ObjectFieldTemplate from '../carbon_theme/ObjectFieldTemplate';
|
||||
|
||||
/*
|
||||
* Returns the ObjectFieldTemplate but with restricted column count for the carbon grid.
|
||||
* Only affects rjsf object field type with the "ui:layout".
|
||||
*/
|
||||
|
||||
export default function ObjectFieldRestrictedGridTemplate<
|
||||
T = any,
|
||||
S extends StrictRJSFSchema = RJSFSchema,
|
||||
F extends FormContextType = any
|
||||
>(props: ObjectFieldTemplateProps<T, S, F>) {
|
||||
return ObjectFieldTemplate(props, {
|
||||
defaultSm: 4,
|
||||
defaultMd: 5,
|
||||
defaultLg: 8,
|
||||
});
|
||||
}
|
@ -417,6 +417,7 @@ export default function TaskShow() {
|
||||
onSubmit={handleFormSubmit}
|
||||
schema={jsonSchema}
|
||||
uiSchema={formUiSchema}
|
||||
restrictedWidth
|
||||
>
|
||||
{reactFragmentToHideSubmitButton}
|
||||
</CustomForm>
|
||||
@ -427,6 +428,7 @@ export default function TaskShow() {
|
||||
schema={jsonSchema}
|
||||
uiSchema={formUiSchema}
|
||||
noValidate
|
||||
restrictedWidth
|
||||
/>
|
||||
</Column>
|
||||
</Grid>
|
||||
|
Loading…
x
Reference in New Issue
Block a user