Skip to content

Commit 3d6415d

Browse files
fix(orchestrator): collapse ui:hidden custom widget gaps (#2572)
* fix(orchestrator-form): collapse ui:hidden custom fields Handle ui:hidden in the object field template so hidden custom widgets skip grid layout spacing while keeping widgets mounted. Made-with: Cursor * chore(orchestrator-form): add changeset for hidden gaps Document the ui:hidden layout fix for the orchestrator form react package. Made-with: Cursor
1 parent 3988b16 commit 3d6415d

4 files changed

Lines changed: 159 additions & 107 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-orchestrator-form-react': patch
3+
---
4+
5+
Avoid layout gaps for `ui:hidden` custom widgets by skipping grid items while
6+
keeping hidden widgets mounted.

workspaces/orchestrator/plugins/orchestrator-form-react/src/components/HiddenFieldTemplate.tsx

Lines changed: 0 additions & 97 deletions
This file was deleted.
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import { JsonObject } from '@backstage/types';
17+
18+
import Grid from '@mui/material/Grid';
19+
import {
20+
canExpand,
21+
descriptionId,
22+
getTemplate,
23+
getUiOptions,
24+
ObjectFieldTemplateProps,
25+
titleId,
26+
UiSchema,
27+
} from '@rjsf/utils';
28+
import type { JSONSchema7 } from 'json-schema';
29+
30+
import { HiddenCondition } from '../types/HiddenCondition';
31+
import { evaluateHiddenCondition } from '../utils/evaluateHiddenCondition';
32+
33+
const getHiddenCondition = (
34+
uiSchema: UiSchema<JsonObject, JSONSchema7> | undefined,
35+
name: string,
36+
): HiddenCondition | undefined => {
37+
if (!uiSchema || !(name in uiSchema)) {
38+
return undefined;
39+
}
40+
return (uiSchema as Record<string, any>)[name]?.['ui:hidden'] as
41+
| HiddenCondition
42+
| undefined;
43+
};
44+
45+
const HiddenObjectFieldTemplate = (
46+
props: ObjectFieldTemplateProps<JsonObject, JSONSchema7>,
47+
) => {
48+
const {
49+
description,
50+
title,
51+
properties,
52+
required,
53+
disabled,
54+
readonly,
55+
uiSchema,
56+
idSchema,
57+
schema,
58+
formData,
59+
onAddClick,
60+
registry,
61+
formContext,
62+
} = props;
63+
64+
const uiOptions = getUiOptions<JsonObject, JSONSchema7>(uiSchema);
65+
const TitleFieldTemplate = getTemplate(
66+
'TitleFieldTemplate',
67+
registry,
68+
uiOptions,
69+
);
70+
const DescriptionFieldTemplate = getTemplate(
71+
'DescriptionFieldTemplate',
72+
registry,
73+
uiOptions,
74+
);
75+
const {
76+
ButtonTemplates: { AddButton },
77+
} = registry.templates;
78+
79+
const rootFormData =
80+
(formContext?.formData as JsonObject) || (formData as JsonObject) || {};
81+
82+
return (
83+
<>
84+
{title && (
85+
<TitleFieldTemplate
86+
id={titleId(idSchema)}
87+
title={title}
88+
required={required}
89+
schema={schema}
90+
uiSchema={uiSchema}
91+
registry={registry}
92+
/>
93+
)}
94+
{description && (
95+
<DescriptionFieldTemplate
96+
id={descriptionId(idSchema)}
97+
description={description}
98+
schema={schema}
99+
uiSchema={uiSchema}
100+
registry={registry}
101+
/>
102+
)}
103+
<Grid container spacing={2} style={{ marginTop: '10px' }}>
104+
{properties.map(element => {
105+
const hiddenCondition = getHiddenCondition(uiSchema, element.name);
106+
const isHiddenByCondition =
107+
hiddenCondition !== undefined
108+
? evaluateHiddenCondition(hiddenCondition, rootFormData)
109+
: false;
110+
const isHidden = element.hidden || isHiddenByCondition;
111+
112+
return isHidden ? (
113+
<div
114+
key={element.name}
115+
style={{ display: 'none' }}
116+
data-hidden-field="true"
117+
>
118+
{element.content}
119+
</div>
120+
) : (
121+
<Grid
122+
item
123+
xs={12}
124+
key={element.name}
125+
style={{ marginBottom: '10px' }}
126+
>
127+
{element.content}
128+
</Grid>
129+
);
130+
})}
131+
{canExpand(schema, uiSchema, formData) && (
132+
<Grid container justifyContent="flex-end">
133+
<Grid item>
134+
<AddButton
135+
className="object-property-expand"
136+
onClick={onAddClick(schema)}
137+
disabled={disabled || readonly}
138+
uiSchema={uiSchema}
139+
registry={registry}
140+
/>
141+
</Grid>
142+
</Grid>
143+
)}
144+
</Grid>
145+
</>
146+
);
147+
};
148+
149+
export default HiddenObjectFieldTemplate;

workspaces/orchestrator/plugins/orchestrator-form-react/src/components/OrchestratorFormWrapper.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { getActiveStepKey } from '../utils/getSortedStepEntries';
3737
import { useStepperContext } from '../utils/StepperContext';
3838
import useValidator from '../utils/useValidator';
3939
import { AuthRequester } from './AuthRequester';
40-
import { createHiddenFieldTemplate } from './HiddenFieldTemplate';
40+
import HiddenObjectFieldTemplate from './HiddenObjectFieldTemplate';
4141
import StepperObjectField from './StepperObjectField';
4242

4343
const MuiForm = withTheme<
@@ -46,12 +46,6 @@ const MuiForm = withTheme<
4646
OrchestratorFormContextProps
4747
>(MuiTheme);
4848

49-
// Get the default FieldTemplate from Material-UI theme and wrap it with hidden field support
50-
const DefaultFieldTemplate = MuiTheme.templates?.FieldTemplate;
51-
const HiddenFieldTemplate = DefaultFieldTemplate
52-
? createHiddenFieldTemplate(DefaultFieldTemplate as any)
53-
: undefined;
54-
5549
const FormComponent = (decoratorProps: FormDecoratorProps) => {
5650
const formContext = decoratorProps.formContext;
5751
const [extraErrors, setExtraErrors] = useState<
@@ -151,9 +145,9 @@ const FormComponent = (decoratorProps: FormDecoratorProps) => {
151145
{...omit(decoratorProps, 'getExtraErrors')}
152146
widgets={{ AuthRequester, ...decoratorProps.widgets }}
153147
fields={isMultiStep ? { ObjectField: StepperObjectField } : {}}
154-
templates={
155-
HiddenFieldTemplate ? { FieldTemplate: HiddenFieldTemplate } : {}
156-
}
148+
templates={{
149+
ObjectFieldTemplate: HiddenObjectFieldTemplate,
150+
}}
157151
uiSchema={uiSchema}
158152
validator={validator}
159153
schema={schema}

0 commit comments

Comments
 (0)