diff --git a/src/layout/Button/GoToTaskButton.test.tsx b/src/layout/Button/GoToTaskButton.test.tsx
index 8a2dfb77cd..1987f34f8d 100644
--- a/src/layout/Button/GoToTaskButton.test.tsx
+++ b/src/layout/Button/GoToTaskButton.test.tsx
@@ -6,23 +6,32 @@ import userEvent from '@testing-library/user-event';
import { getInitialStateMock } from 'src/__mocks__/mocks';
import { GoToTaskButton } from 'src/layout/Button/GoToTaskButton';
import { setupStore } from 'src/store';
-import { renderWithProviders } from 'src/testUtils';
-import type { Props as GoToTaskButtonProps } from 'src/layout/Button/GoToTaskButton';
+import { mockComponentProps, renderWithProviders } from 'src/testUtils';
+import type { IButtonProvidedProps } from 'src/layout/Button/ButtonComponent';
-const render = ({ props = {}, dispatch = jest.fn() } = {}) => {
- const allProps = {
- id: 'go-to-task-button',
- ...props,
- } as GoToTaskButtonProps;
+interface RenderProps {
+ props: Partial
;
+ dispatch: (...args: any[]) => any;
+}
+
+const render = ({ props, dispatch }: RenderProps) => {
const stateMock = getInitialStateMock();
stateMock.process.availableNextTasks = ['a', 'b'];
const store = setupStore(stateMock);
store.dispatch = dispatch;
- renderWithProviders(Go to task, {
- store,
- });
+ renderWithProviders(
+
+ Go to task
+ ,
+ {
+ store,
+ },
+ );
};
describe('GoToTaskButton', () => {
diff --git a/src/layout/Button/GoToTaskButton.tsx b/src/layout/Button/GoToTaskButton.tsx
index f489e96977..89e8328190 100644
--- a/src/layout/Button/GoToTaskButton.tsx
+++ b/src/layout/Button/GoToTaskButton.tsx
@@ -6,14 +6,12 @@ import { useAppDispatch, useAppSelector } from 'src/common/hooks';
import { WrappedButton } from 'src/layout/Button/WrappedButton';
import { ProcessActions } from 'src/shared/resources/process/processSlice';
import { ProcessTaskType } from 'src/types';
-import type { ButtonProps } from 'src/layout/Button/WrappedButton';
+import type { IButtonProvidedProps } from 'src/layout/Button/ButtonComponent';
-export type Props = Omit & { taskId: string };
-
-export const GoToTaskButton = ({ children, taskId, ...props }: Props) => {
+export const GoToTaskButton = ({ children, taskId, ...props }: React.PropsWithChildren) => {
const dispatch = useAppDispatch();
const availableProcessTasks = useAppSelector((state) => state.process.availableNextTasks);
- const canGoToTask = availableProcessTasks && availableProcessTasks.includes(taskId);
+ const canGoToTask = availableProcessTasks && availableProcessTasks.includes(taskId || '');
const navigateToTask = () => {
if (canGoToTask) {
dispatch(
diff --git a/src/layout/Button/getComponentFromMode.ts b/src/layout/Button/getComponentFromMode.ts
index 20ca1c67c7..277ce03384 100644
--- a/src/layout/Button/getComponentFromMode.ts
+++ b/src/layout/Button/getComponentFromMode.ts
@@ -1,11 +1,17 @@
+import type React from 'react';
+
import { GoToTaskButton } from 'src/layout/Button/GoToTaskButton';
import { InstantiationButton } from 'src/layout/InstantiationButton/InstantiationButton';
-export type ButtonMode = 'submit' | 'save' | 'go-to-task' | 'instantiate';
+import type { IButtonProvidedProps } from 'src/layout/Button/ButtonComponent';
+import type { ButtonMode } from 'src/layout/Button/types';
-const buttons = {
+const buttons: { [key in ButtonMode]: React.FC> | null } = {
+ save: null,
+ submit: null,
'go-to-task': GoToTaskButton,
instantiate: InstantiationButton,
};
+
export const getComponentFromMode = (mode: ButtonMode) => {
return buttons[mode];
};
diff --git a/src/layout/Button/index.tsx b/src/layout/Button/index.tsx
new file mode 100644
index 0000000000..725afb6fdd
--- /dev/null
+++ b/src/layout/Button/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { ButtonComponent } from 'src/layout/Button/ButtonComponent';
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class Button extends LayoutComponent<'Button'> {
+ render(props: PropsFromGenericComponent<'Button'>): JSX.Element | null {
+ return ;
+ }
+
+ renderWithLabel(): boolean {
+ return false;
+ }
+}
diff --git a/src/layout/Button/types.d.ts b/src/layout/Button/types.d.ts
index 44f9eb4d62..cd8fd62e8c 100644
--- a/src/layout/Button/types.d.ts
+++ b/src/layout/Button/types.d.ts
@@ -1,3 +1,13 @@
import type { ILayoutCompBase } from 'src/layout/layout';
+import type { IMapping } from 'src/types';
-export type ILayoutCompButton = ILayoutCompBase<'Button'>;
+export type ButtonMode = 'submit' | 'save' | 'go-to-task' | 'instantiate';
+
+export interface ILayoutCompButton extends ILayoutCompBase<'Button'> {
+ mode?: ButtonMode;
+
+ taskId?: string; // Required for go-to-task
+
+ busyWithId?: string;
+ mapping?: IMapping;
+}
diff --git a/src/layout/Checkboxes/CheckboxesContainerComponent.test.tsx b/src/layout/Checkboxes/CheckboxesContainerComponent.test.tsx
index f114ebe9bf..f0cc491dc6 100644
--- a/src/layout/Checkboxes/CheckboxesContainerComponent.test.tsx
+++ b/src/layout/Checkboxes/CheckboxesContainerComponent.test.tsx
@@ -35,7 +35,6 @@ const render = (props: Partial = {}, customState: Prelo
options: [],
optionsId: 'countries',
preselectedOptionIndex: undefined,
- validationMessages: {},
legend: () => legend,
handleDataChange: jest.fn(),
getTextResource: (value) => value,
diff --git a/src/layout/Checkboxes/CheckboxesContainerComponent.tsx b/src/layout/Checkboxes/CheckboxesContainerComponent.tsx
index 7e05b8a20a..e45eb03e8b 100644
--- a/src/layout/Checkboxes/CheckboxesContainerComponent.tsx
+++ b/src/layout/Checkboxes/CheckboxesContainerComponent.tsx
@@ -13,13 +13,10 @@ import { useDelayedSavedState } from 'src/components/hooks/useDelayedSavedState'
import { AltinnSpinner } from 'src/components/shared';
import { shouldUseRowLayout } from 'src/utils/layout';
import { getOptionLookupKey } from 'src/utils/options';
-import { renderValidationMessagesForComponent } from 'src/utils/render';
import type { PropsFromGenericComponent } from 'src/layout';
-import type { IComponentValidations, IOption } from 'src/types';
+import type { IOption } from 'src/types';
-export interface ICheckboxContainerProps extends PropsFromGenericComponent<'Checkboxes'> {
- validationMessages: IComponentValidations;
-}
+export type ICheckboxContainerProps = PropsFromGenericComponent<'Checkboxes'>;
interface IStyledCheckboxProps extends CheckboxProps {
label: string;
@@ -92,7 +89,6 @@ export const CheckboxContainerComponent = ({
readOnly,
getTextResourceAsString,
getTextResource,
- validationMessages,
mapping,
source,
}: ICheckboxContainerProps) => {
@@ -177,28 +173,23 @@ export const CheckboxContainerComponent = ({
) : (
<>
{calculatedOptions.map((option, index) => (
-
-
- }
- label={getTextResource(option.label)}
- />
- {validationMessages &&
- isOptionSelected(option.value) &&
- renderValidationMessagesForComponent(validationMessages.simpleBinding, id)}
-
+
+ }
+ label={getTextResource(option.label)}
+ />
))}
>
)}
diff --git a/src/layout/Checkboxes/index.tsx b/src/layout/Checkboxes/index.tsx
new file mode 100644
index 0000000000..56126d2046
--- /dev/null
+++ b/src/layout/Checkboxes/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { CheckboxContainerComponent } from 'src/layout/Checkboxes/CheckboxesContainerComponent';
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class Checkboxes extends LayoutComponent<'Checkboxes'> {
+ render(props: PropsFromGenericComponent<'Checkboxes'>): JSX.Element | null {
+ return ;
+ }
+
+ renderWithLabel(): boolean {
+ return false;
+ }
+}
diff --git a/src/layout/Custom/index.tsx b/src/layout/Custom/index.tsx
new file mode 100644
index 0000000000..f6e2f6702c
--- /dev/null
+++ b/src/layout/Custom/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { CustomWebComponent } from 'src/layout/Custom/CustomWebComponent';
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class Custom extends LayoutComponent<'Custom'> {
+ render(props: PropsFromGenericComponent<'Custom'>): JSX.Element | null {
+ return ;
+ }
+
+ renderWithLabel(): boolean {
+ return false;
+ }
+}
diff --git a/src/layout/Datepicker/index.tsx b/src/layout/Datepicker/index.tsx
new file mode 100644
index 0000000000..2100aedf74
--- /dev/null
+++ b/src/layout/Datepicker/index.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+import { DatepickerComponent } from 'src/layout/Datepicker/DatepickerComponent';
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class Datepicker extends LayoutComponent<'Datepicker'> {
+ render(props: PropsFromGenericComponent<'Datepicker'>): JSX.Element | null {
+ return ;
+ }
+}
diff --git a/src/layout/Dropdown/index.tsx b/src/layout/Dropdown/index.tsx
new file mode 100644
index 0000000000..e54e7fb842
--- /dev/null
+++ b/src/layout/Dropdown/index.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+import { DropdownComponent } from 'src/layout/Dropdown/DropdownComponent';
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class Dropdown extends LayoutComponent<'Dropdown'> {
+ render(props: PropsFromGenericComponent<'Dropdown'>): JSX.Element | null {
+ return ;
+ }
+}
diff --git a/src/layout/FileUpload/index.tsx b/src/layout/FileUpload/index.tsx
new file mode 100644
index 0000000000..ab2dddbd80
--- /dev/null
+++ b/src/layout/FileUpload/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { FileUploadComponent } from 'src/layout/FileUpload/FileUploadComponent';
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class FileUpload extends LayoutComponent<'FileUpload'> {
+ render(props: PropsFromGenericComponent<'FileUpload'>): JSX.Element | null {
+ return ;
+ }
+
+ renderDefaultValidations(): boolean {
+ return false;
+ }
+}
diff --git a/src/layout/FileUploadWithTag/index.tsx b/src/layout/FileUploadWithTag/index.tsx
new file mode 100644
index 0000000000..f73828bf87
--- /dev/null
+++ b/src/layout/FileUploadWithTag/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { FileUploadWithTagComponent } from 'src/layout/FileUploadWithTag/FileUploadWithTagComponent';
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class FileUploadWithTag extends LayoutComponent<'FileUploadWithTag'> {
+ render(props: PropsFromGenericComponent<'FileUploadWithTag'>): JSX.Element | null {
+ return ;
+ }
+
+ renderDefaultValidations(): boolean {
+ return false;
+ }
+}
diff --git a/src/layout/GenericComponent.tsx b/src/layout/GenericComponent.tsx
index ab0d838a56..414cb6707d 100644
--- a/src/layout/GenericComponent.tsx
+++ b/src/layout/GenericComponent.tsx
@@ -12,11 +12,11 @@ import Legend from 'src/features/form/components/Legend';
import { FormDataActions } from 'src/features/form/data/formDataSlice';
import { FormLayoutActions } from 'src/features/form/layout/formLayoutSlice';
import components, { FormComponentContext } from 'src/layout/index';
+import { getLayoutComponentObject } from 'src/layout/LayoutComponent';
import { makeGetFocus, makeGetHidden } from 'src/selectors/getLayoutData';
-import { LayoutStyle, Triggers } from 'src/types';
+import { Triggers } from 'src/types';
import {
componentHasValidationMessages,
- componentValidationsHandledByGenericComponent,
getFormDataForComponent,
getTextResource,
gridBreakpoints,
@@ -29,17 +29,15 @@ import type { ExprResolved } from 'src/features/expressions/types';
import type { ISingleFieldValidation } from 'src/features/form/data/formDataTypes';
import type { IComponentProps, IFormComponentContext, PropsFromGenericComponent } from 'src/layout/index';
import type {
- ComponentExceptGroup,
+ ComponentExceptGroupAndSummary,
ComponentTypes,
IGridStyling,
ILayoutCompBase,
ILayoutComponent,
} from 'src/layout/layout';
-import type { IComponentValidations, ILabelSettings } from 'src/types';
-import type { ILanguage } from 'src/types/shared';
+import type { ILabelSettings, LayoutStyle } from 'src/types';
export interface IGenericComponentProps {
- componentValidations?: IComponentValidations;
labelSettings?: ILabelSettings;
layout?: LayoutStyle;
groupContainerId?: string;
@@ -97,7 +95,9 @@ const useStyles = makeStyles((theme) => ({
},
}));
-export function GenericComponent(_props: IActualGenericComponentProps) {
+export function GenericComponent(
+ _props: IActualGenericComponentProps,
+) {
const props = useExpressionsForComponent(_props as ILayoutComponent) as ExprResolved<
IActualGenericComponentProps
> & {
@@ -201,27 +201,8 @@ export function GenericComponent(_props: IAct
);
};
- const getValidationsForInternalHandling = () => {
- if (
- props.type === 'AddressComponent' ||
- props.type === 'Datepicker' ||
- props.type === 'FileUpload' ||
- props.type === 'FileUploadWithTag' ||
- (props.type === 'Likert' && props.layout === LayoutStyle.Table)
- ) {
- return componentValidations;
- }
- return null;
- };
-
- // some components handle their validations internally (i.e merge with internal validation state)
- const internalComponentValidations = getValidationsForInternalHandling();
- if (internalComponentValidations !== null) {
- passThroughProps.componentValidations = internalComponentValidations;
- }
-
- const RenderComponent = components[props.type as keyof typeof components];
- if (!RenderComponent) {
+ const layoutComponent = getLayoutComponentObject(_props.type);
+ if (!layoutComponent) {
return (
Unknown component type: {props.type}
@@ -231,16 +212,20 @@ export function GenericComponent
(_props: IAct
);
}
- const RenderLabel = () => {
- return (
-
- );
- };
+ const RenderComponent = layoutComponent.render;
+
+ const RenderLabel = () => (
+
+ );
const RenderDescription = () => {
if (!props.textResourceBindings?.description) {
@@ -252,7 +237,6 @@ export function GenericComponent(_props: IAct
key={`description-${props.id}`}
description={texts.description}
id={id}
- {...passThroughProps}
/>
);
};
@@ -265,8 +249,10 @@ export function GenericComponent(_props: IAct
descriptionText={texts.description}
helpText={texts.help}
language={language}
- {...props}
- {...passThroughProps}
+ id={props.id}
+ required={props.required}
+ labelSettings={props.labelSettings}
+ layout={props.layout}
/>
);
};
@@ -291,32 +277,18 @@ export function GenericComponent(_props: IAct
text: texts.title,
label: RenderLabel,
legend: RenderLegend,
+ componentValidations,
...passThroughProps,
} as unknown as PropsFromGenericComponent;
- const noLabelComponents: ComponentTypes[] = [
- 'Header',
- 'Paragraph',
- 'Image',
- 'NavigationButtons',
- 'Custom',
- 'AddressComponent',
- 'Button',
- 'Checkboxes',
- 'RadioButtons',
- 'AttachmentList',
- 'InstantiationButton',
- 'NavigationBar',
- 'Likert',
- 'Panel',
- 'List',
- ];
-
- const showValidationMessages =
- componentValidationsHandledByGenericComponent(props.dataModelBindings, props.type) && hasValidationMessages;
-
- if (props.type === 'Likert' && props.layout === LayoutStyle.Table) {
- return ;
+ const showValidationMessages = hasValidationMessages && layoutComponent.renderDefaultValidations();
+
+ if (layoutComponent.directRender(componentProps)) {
+ return (
+
+
+
+ );
}
return (
@@ -331,22 +303,17 @@ export function GenericComponent(_props: IAct
'form-group',
'a-form-group',
classes.container,
- gridToHiddenProps(props.grid?.labelGrid, classes),
+ gridToClasses(props.grid?.labelGrid, classes),
)}
alignItems='baseline'
>
- {!noLabelComponents.includes(props.type) && (
+ {layoutComponent.renderWithLabel() && (
-
-
+
+
)}
(_props: IAct
);
}
-interface IRenderLabelProps {
- texts: any;
- language: ILanguage;
- props: any;
- passThroughProps: any;
-}
-
-const RenderLabelScoped = (props: IRenderLabelProps) => {
- return (
-
- );
-};
-
-const gridToHiddenProps = (labelGrid: IGridStyling | undefined, classes: ReturnType) => {
+const gridToClasses = (labelGrid: IGridStyling | undefined, classes: ReturnType) => {
if (!labelGrid) {
- return undefined;
+ return {};
}
return {
diff --git a/src/layout/Header/index.tsx b/src/layout/Header/index.tsx
new file mode 100644
index 0000000000..4068a7387c
--- /dev/null
+++ b/src/layout/Header/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { HeaderComponent } from 'src/layout/Header/HeaderComponent';
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class Header extends LayoutComponent<'Header'> {
+ render(props: PropsFromGenericComponent<'Header'>): JSX.Element | null {
+ return ;
+ }
+
+ renderWithLabel(): boolean {
+ return false;
+ }
+}
diff --git a/src/layout/Image/index.tsx b/src/layout/Image/index.tsx
new file mode 100644
index 0000000000..34835d135e
--- /dev/null
+++ b/src/layout/Image/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { ImageComponent } from 'src/layout/Image/ImageComponent';
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class Image extends LayoutComponent<'Image'> {
+ render(props: PropsFromGenericComponent<'Image'>): JSX.Element | null {
+ return ;
+ }
+
+ renderWithLabel(): boolean {
+ return false;
+ }
+}
diff --git a/src/layout/Input/index.tsx b/src/layout/Input/index.tsx
new file mode 100644
index 0000000000..c2ed32a26e
--- /dev/null
+++ b/src/layout/Input/index.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+import { InputComponent } from 'src/layout/Input/InputComponent';
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class Input extends LayoutComponent<'Input'> {
+ render(props: PropsFromGenericComponent<'Input'>): JSX.Element | null {
+ return ;
+ }
+}
diff --git a/src/layout/InstanceInformation/index.tsx b/src/layout/InstanceInformation/index.tsx
new file mode 100644
index 0000000000..d4502d3a19
--- /dev/null
+++ b/src/layout/InstanceInformation/index.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+import { InstanceInformationComponent } from 'src/layout/InstanceInformation/InstanceInformationComponent';
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class InstanceInformation extends LayoutComponent<'InstanceInformation'> {
+ render(props: PropsFromGenericComponent<'InstanceInformation'>): JSX.Element | null {
+ return ;
+ }
+}
diff --git a/src/layout/InstantiationButton/InstantiationButton.test.tsx b/src/layout/InstantiationButton/InstantiationButton.test.tsx
index d2c1a3f721..a90252355f 100644
--- a/src/layout/InstantiationButton/InstantiationButton.test.tsx
+++ b/src/layout/InstantiationButton/InstantiationButton.test.tsx
@@ -8,14 +8,9 @@ import mockAxios from 'jest-mock-axios';
import { getInitialStateMock } from 'src/__mocks__/mocks';
import { InstantiationButton } from 'src/layout/InstantiationButton/InstantiationButton';
import { setupStore } from 'src/store';
-import { renderWithProviders } from 'src/testUtils';
-import type { InstantiationButtonProps as InstantiationButtonProps } from 'src/layout/InstantiationButton/InstantiationButton';
+import { mockComponentProps, renderWithProviders } from 'src/testUtils';
-const render = ({ props = {} }) => {
- const allProps = {
- id: 'instantiate-button',
- ...props,
- } as InstantiationButtonProps;
+const render = () => {
const stateMock = getInitialStateMock();
const store = setupStore(stateMock);
@@ -26,7 +21,7 @@ const render = ({ props = {} }) => {
Instantiate}
+ element={Instantiate}
/>
{
describe('InstantiationButton', () => {
it('should show button and it should be possible to click and start loading', async () => {
mockAxios.reset();
- const dispatch = render({});
+ const dispatch = render();
expect(screen.getByText('Instantiate')).toBeInTheDocument();
expect(dispatch).toHaveBeenCalledTimes(0);
diff --git a/src/layout/InstantiationButton/InstantiationButton.tsx b/src/layout/InstantiationButton/InstantiationButton.tsx
index 69e6901f97..03cb02d9e5 100644
--- a/src/layout/InstantiationButton/InstantiationButton.tsx
+++ b/src/layout/InstantiationButton/InstantiationButton.tsx
@@ -8,12 +8,11 @@ import { useInstantiateWithPrefillMutation } from 'src/services/InstancesApi';
import { AttachmentActions } from 'src/shared/resources/attachments/attachmentSlice';
import { InstanceDataActions } from 'src/shared/resources/instanceData/instanceDataSlice';
import { mapFormData } from 'src/utils/databindings';
-import type { ButtonProps } from 'src/layout/Button/WrappedButton';
import type { IInstantiationButtonComponentProps } from 'src/layout/InstantiationButton/InstantiationButtonComponent';
-export type InstantiationButtonProps = Omit & Omit;
+type Props = Omit, 'text'>;
-export const InstantiationButton = ({ children, ...props }: InstantiationButtonProps) => {
+export const InstantiationButton = ({ children, ...props }: Props) => {
const dispatch = useAppDispatch();
const [instantiateWithPrefill, { isSuccess, data, isLoading, isError }] = useInstantiateWithPrefillMutation();
const formData = useAppSelector((state) => state.formData.formData);
diff --git a/src/layout/InstantiationButton/index.tsx b/src/layout/InstantiationButton/index.tsx
new file mode 100644
index 0000000000..24c6085fe4
--- /dev/null
+++ b/src/layout/InstantiationButton/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { InstantiationButtonComponent } from 'src/layout/InstantiationButton/InstantiationButtonComponent';
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class InstantiationButton extends LayoutComponent<'InstantiationButton'> {
+ render(props: PropsFromGenericComponent<'InstantiationButton'>): JSX.Element | null {
+ return ;
+ }
+
+ renderWithLabel(): boolean {
+ return false;
+ }
+}
diff --git a/src/layout/InstantiationButton/types.d.ts b/src/layout/InstantiationButton/types.d.ts
index 56e30892a5..96762ce18c 100644
--- a/src/layout/InstantiationButton/types.d.ts
+++ b/src/layout/InstantiationButton/types.d.ts
@@ -3,4 +3,5 @@ import type { IMapping } from 'src/types';
export interface ILayoutCompInstantiationButton extends ILayoutCompBase<'InstantiationButton'> {
mapping?: IMapping;
+ busyWithId?: string;
}
diff --git a/src/layout/LayoutComponent.ts b/src/layout/LayoutComponent.ts
new file mode 100644
index 0000000000..95a25108fd
--- /dev/null
+++ b/src/layout/LayoutComponent.ts
@@ -0,0 +1,41 @@
+import components from 'src/layout/index';
+import type { PropsFromGenericComponent } from 'src/layout/index';
+import type { ComponentExceptGroupAndSummary } from 'src/layout/layout';
+
+export abstract class LayoutComponent {
+ /**
+ * Given properties from GenericComponent, render this layout component
+ */
+ abstract render(props: PropsFromGenericComponent): JSX.Element | null;
+
+ /**
+ * Direct render? Override this and return true if you want GenericComponent to omit rendering grid,
+ * validation messages, etc.
+ */
+ directRender(_props: PropsFromGenericComponent): boolean {
+ return false;
+ }
+
+ /**
+ * Return false to render this component without the label (in GenericComponent.tsx)
+ */
+ renderWithLabel(): boolean {
+ return true;
+ }
+
+ /**
+ * Should GenericComponent render validation messages for simpleBinding outside of this component?
+ * This has no effect if:
+ * - Your component renders directly, using directRender()
+ * - Your component uses a different data binding (you should handle validations yourself)
+ */
+ renderDefaultValidations(): boolean {
+ return true;
+ }
+}
+
+export function getLayoutComponentObject(
+ type: T,
+): LayoutComponent | undefined {
+ return components[type];
+}
diff --git a/src/layout/Likert/GroupContainerLikertTestUtils.tsx b/src/layout/Likert/GroupContainerLikertTestUtils.tsx
index 37c8ae3d30..cfda8b29f7 100644
--- a/src/layout/Likert/GroupContainerLikertTestUtils.tsx
+++ b/src/layout/Likert/GroupContainerLikertTestUtils.tsx
@@ -13,7 +13,7 @@ import type { IUpdateFormData } from 'src/features/form/data/formDataTypes';
import type { ILayoutState } from 'src/features/form/layout/formLayoutSlice';
import type { IValidationState } from 'src/features/form/validation/validationSlice';
import type { ILayoutGroup } from 'src/layout/Group/types';
-import type { ILayoutComponent } from 'src/layout/layout';
+import type { ComponentInGroup, ILayoutComponent } from 'src/layout/layout';
import type { ILayoutCompLikert } from 'src/layout/Likert/types';
import type { ITextResourcesState } from 'src/shared/resources/textResources';
import type { ILayoutValidations, ITextResource } from 'src/types';
@@ -109,7 +109,11 @@ export const createFormDataUpdateAction = (index: number, optionValue: string):
};
};
-const createLayout = (container: ILayoutGroup, components: ILayoutComponent[], groupIndex: number): ILayoutState => {
+const createLayout = (
+ container: ILayoutGroup,
+ components: (ILayoutComponent | ComponentInGroup)[],
+ groupIndex: number,
+): ILayoutState => {
return {
error: null,
layoutsets: null,
@@ -210,7 +214,7 @@ export const render = ({
}: Partial = {}) => {
const mockRadioButton = createRadioButton(radioButtonProps);
const mockLikertContainer = createLikertContainer(likertContainerProps);
- const components: ILayoutComponent[] = [mockRadioButton];
+ const components: ComponentInGroup[] = [mockRadioButton];
const mockData: IFormDataState = {
formData: generateMockFormData(mockQuestions),
error: null,
diff --git a/src/layout/Likert/RepeatingGroupsLikertContainer.tsx b/src/layout/Likert/RepeatingGroupsLikertContainer.tsx
index c7cbf099ec..84ebba5fff 100644
--- a/src/layout/Likert/RepeatingGroupsLikertContainer.tsx
+++ b/src/layout/Likert/RepeatingGroupsLikertContainer.tsx
@@ -13,13 +13,13 @@ import { LayoutStyle } from 'src/types';
import { getTextResource } from 'src/utils/formComponentUtils';
import { getOptionLookupKey } from 'src/utils/options';
import type { ILayoutGroup } from 'src/layout/Group/types';
-import type { ILayoutComponent } from 'src/layout/layout';
+import type { ComponentInGroup } from 'src/layout/layout';
import type { IRadioButtonsContainerProps } from 'src/layout/RadioButtons/RadioButtonsContainerComponent';
import type { ITextResource } from 'src/types';
type RepeatingGroupsLikertContainerProps = {
id: string;
- repeatingGroupDeepCopyComponents: ILayoutComponent[];
+ repeatingGroupDeepCopyComponents: ComponentInGroup[];
textResources: ITextResource[];
container: ILayoutGroup;
};
@@ -93,10 +93,15 @@ export const RepeatingGroupsLikertContainer = ({
aria-describedby={(description && descriptionId) || undefined}
>
{repeatingGroupDeepCopyComponents.map((comp) => {
+ if (comp.type === 'Group') {
+ console.warn('Unexpected group inside likert container', comp);
+ return;
+ }
+
return (
);
})}
@@ -143,6 +148,11 @@ export const RepeatingGroupsLikertContainer = ({
padding={'dense'}
>
{repeatingGroupDeepCopyComponents.map((comp) => {
+ if (comp.type === 'Group') {
+ console.warn('Unexpected group inside likert container', comp);
+ return;
+ }
+
return (
{
+ render(props: PropsFromGenericComponent<'Likert'>): JSX.Element | null {
+ return ;
+ }
+
+ directRender(props: PropsFromGenericComponent<'Likert'>): boolean {
+ return props.layout === LayoutStyle.Table;
+ }
+
+ renderWithLabel(): boolean {
+ return false;
+ }
+}
diff --git a/src/layout/List/index.tsx b/src/layout/List/index.tsx
new file mode 100644
index 0000000000..7e05a30de4
--- /dev/null
+++ b/src/layout/List/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import { ListComponent } from 'src/layout/List/ListComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class List extends LayoutComponent<'List'> {
+ render(props: PropsFromGenericComponent<'List'>): JSX.Element | null {
+ return ;
+ }
+
+ renderWithLabel(): boolean {
+ return false;
+ }
+}
diff --git a/src/layout/Map/index.tsx b/src/layout/Map/index.tsx
new file mode 100644
index 0000000000..2b05063d45
--- /dev/null
+++ b/src/layout/Map/index.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import { MapComponent } from 'src/layout/Map/MapComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class Map extends LayoutComponent<'Map'> {
+ render(props: PropsFromGenericComponent<'Map'>): JSX.Element | null {
+ return ;
+ }
+}
diff --git a/src/layout/MultipleSelect/MultipleSelect.test.tsx b/src/layout/MultipleSelect/MultipleSelectComponent.test.tsx
similarity index 92%
rename from src/layout/MultipleSelect/MultipleSelect.test.tsx
rename to src/layout/MultipleSelect/MultipleSelectComponent.test.tsx
index d3bfc6de36..f194b71052 100644
--- a/src/layout/MultipleSelect/MultipleSelect.test.tsx
+++ b/src/layout/MultipleSelect/MultipleSelectComponent.test.tsx
@@ -4,9 +4,9 @@ import { fireEvent, screen } from '@testing-library/react';
import type { PreloadedState } from 'redux';
import { getInitialStateMock } from 'src/__mocks__/initialStateMock';
-import { MultipleSelect } from 'src/layout/MultipleSelect/MultipleSelect';
+import { MultipleSelectComponent } from 'src/layout/MultipleSelect/MultipleSelectComponent';
import { mockComponentProps, renderWithProviders } from 'src/testUtils';
-import type { IMultipleSelectProps } from 'src/layout/MultipleSelect/MultipleSelect';
+import type { IMultipleSelectProps } from 'src/layout/MultipleSelect/MultipleSelectComponent';
import type { RootState } from 'src/store';
const dummyLabel = 'dummyLabel';
@@ -33,7 +33,7 @@ const render = (props: Partial = {}, customState: Preloade
return renderWithProviders(
<>
-
+
>,
{
preloadedState: {
diff --git a/src/layout/MultipleSelect/MultipleSelect.tsx b/src/layout/MultipleSelect/MultipleSelectComponent.tsx
similarity index 98%
rename from src/layout/MultipleSelect/MultipleSelect.tsx
rename to src/layout/MultipleSelect/MultipleSelectComponent.tsx
index 5e1caf973b..3f920d7aa5 100644
--- a/src/layout/MultipleSelect/MultipleSelect.tsx
+++ b/src/layout/MultipleSelect/MultipleSelectComponent.tsx
@@ -15,7 +15,7 @@ const invalidBorderColor = '#D5203B !important';
export type IMultipleSelectProps = PropsFromGenericComponent<'MultipleSelect'>;
-export function MultipleSelect({
+export function MultipleSelectComponent({
options,
optionsId,
mapping,
diff --git a/src/layout/MultipleSelect/index.tsx b/src/layout/MultipleSelect/index.tsx
new file mode 100644
index 0000000000..b805ae6d8c
--- /dev/null
+++ b/src/layout/MultipleSelect/index.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import { MultipleSelectComponent } from 'src/layout/MultipleSelect/MultipleSelectComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class MultipleSelect extends LayoutComponent<'MultipleSelect'> {
+ render(props: PropsFromGenericComponent<'MultipleSelect'>): JSX.Element | null {
+ return ;
+ }
+}
diff --git a/src/layout/NavigationBar/NavigationBar.test.tsx b/src/layout/NavigationBar/NavigationBarComponent.test.tsx
similarity index 97%
rename from src/layout/NavigationBar/NavigationBar.test.tsx
rename to src/layout/NavigationBar/NavigationBarComponent.test.tsx
index 28f6414783..ab513c9dfc 100644
--- a/src/layout/NavigationBar/NavigationBar.test.tsx
+++ b/src/layout/NavigationBar/NavigationBarComponent.test.tsx
@@ -4,10 +4,10 @@ import { act, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { FormLayoutActions } from 'src/features/form/layout/formLayoutSlice';
-import { NavigationBar } from 'src/layout/NavigationBar/NavigationBar';
+import { NavigationBarComponent } from 'src/layout/NavigationBar/NavigationBarComponent';
import { setupStore } from 'src/store';
import { mockMediaQuery, renderWithProviders } from 'src/testUtils';
-import type { INavigationBar } from 'src/layout/NavigationBar/NavigationBar';
+import type { INavigationBar } from 'src/layout/NavigationBar/NavigationBarComponent';
const { setScreenWidth } = mockMediaQuery(600);
@@ -114,7 +114,7 @@ const render = ({ props = {}, dispatch = jest.fn() } = {}) => {
store.dispatch = dispatch;
- renderWithProviders(, {
+ renderWithProviders(, {
store,
});
};
diff --git a/src/layout/NavigationBar/NavigationBar.tsx b/src/layout/NavigationBar/NavigationBarComponent.tsx
similarity index 98%
rename from src/layout/NavigationBar/NavigationBar.tsx
rename to src/layout/NavigationBar/NavigationBarComponent.tsx
index f15eccf30b..7339b17760 100644
--- a/src/layout/NavigationBar/NavigationBar.tsx
+++ b/src/layout/NavigationBar/NavigationBarComponent.tsx
@@ -97,7 +97,7 @@ const NavigationButton = React.forwardRef(
NavigationButton.displayName = 'NavigationButton';
-export const NavigationBar = ({ triggers }: INavigationBar) => {
+export const NavigationBarComponent = ({ triggers }: INavigationBar) => {
const classes = useStyles();
const dispatch = useAppDispatch();
const pageIds = useAppSelector(selectLayoutOrder);
diff --git a/src/layout/NavigationBar/index.tsx b/src/layout/NavigationBar/index.tsx
new file mode 100644
index 0000000000..57858ff5b3
--- /dev/null
+++ b/src/layout/NavigationBar/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import { NavigationBarComponent } from 'src/layout/NavigationBar/NavigationBarComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class NavigationBar extends LayoutComponent<'NavigationBar'> {
+ render(props: PropsFromGenericComponent<'NavigationBar'>): JSX.Element | null {
+ return ;
+ }
+
+ renderWithLabel(): boolean {
+ return false;
+ }
+}
diff --git a/src/layout/NavigationButtons/NavigationButtons.test.tsx b/src/layout/NavigationButtons/NavigationButtonsComponent.test.tsx
similarity index 93%
rename from src/layout/NavigationButtons/NavigationButtons.test.tsx
rename to src/layout/NavigationButtons/NavigationButtonsComponent.test.tsx
index 3dac24abe9..b1c5ee2f53 100644
--- a/src/layout/NavigationButtons/NavigationButtons.test.tsx
+++ b/src/layout/NavigationButtons/NavigationButtonsComponent.test.tsx
@@ -5,8 +5,8 @@ import { render, screen } from '@testing-library/react';
import configureStore from 'redux-mock-store';
import { getFormLayoutStateMock, getInitialStateMock } from 'src/__mocks__/mocks';
-import { NavigationButtons } from 'src/layout/NavigationButtons/NavigationButtons';
-import type { INavigationButtons } from 'src/layout/NavigationButtons/NavigationButtons';
+import { NavigationButtonsComponent } from 'src/layout/NavigationButtons/NavigationButtonsComponent';
+import type { INavigationButtons } from 'src/layout/NavigationButtons/NavigationButtonsComponent';
describe('NavigationButton', () => {
let mockStore;
@@ -92,7 +92,7 @@ describe('NavigationButton', () => {
test('renders default NavigationButtons component', () => {
render(
- {
test('renders NavigationButtons component without back button if there is no previous page', () => {
render(
- {
const store = createStoreNew(initialState);
render(
- ;
-export function NavigationButtons(props: INavigationButtons) {
+export function NavigationButtonsComponent(props: INavigationButtons) {
const dispatch = useAppDispatch();
const refPrev = React.useRef();
diff --git a/src/layout/NavigationButtons/index.tsx b/src/layout/NavigationButtons/index.tsx
new file mode 100644
index 0000000000..a6e8d12fea
--- /dev/null
+++ b/src/layout/NavigationButtons/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import { NavigationButtonsComponent } from 'src/layout/NavigationButtons/NavigationButtonsComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class NavigationButtons extends LayoutComponent<'NavigationButtons'> {
+ render(props: PropsFromGenericComponent<'NavigationButtons'>): JSX.Element | null {
+ return ;
+ }
+
+ renderWithLabel(): boolean {
+ return false;
+ }
+}
diff --git a/src/layout/Panel/index.tsx b/src/layout/Panel/index.tsx
new file mode 100644
index 0000000000..b5bcea8f43
--- /dev/null
+++ b/src/layout/Panel/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import { PanelComponent } from 'src/layout/Panel/PanelComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class Panel extends LayoutComponent<'Panel'> {
+ render(props: PropsFromGenericComponent<'Panel'>): JSX.Element | null {
+ return ;
+ }
+
+ renderWithLabel(): boolean {
+ return false;
+ }
+}
diff --git a/src/layout/Paragraph/index.tsx b/src/layout/Paragraph/index.tsx
new file mode 100644
index 0000000000..dfc75381d0
--- /dev/null
+++ b/src/layout/Paragraph/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import { ParagraphComponent } from 'src/layout/Paragraph/ParagraphComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class Paragraph extends LayoutComponent<'Paragraph'> {
+ render(props: PropsFromGenericComponent<'Paragraph'>): JSX.Element | null {
+ return ;
+ }
+
+ renderWithLabel(): boolean {
+ return false;
+ }
+}
diff --git a/src/layout/PrintButton/index.tsx b/src/layout/PrintButton/index.tsx
new file mode 100644
index 0000000000..7afea4c10e
--- /dev/null
+++ b/src/layout/PrintButton/index.tsx
@@ -0,0 +1,10 @@
+import React from 'react';
+
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import { PrintButtonComponent } from 'src/layout/PrintButton/PrintButtonComponent';
+
+export class PrintButton extends LayoutComponent<'PrintButton'> {
+ render(): JSX.Element | null {
+ return ;
+ }
+}
diff --git a/src/layout/RadioButtons/ControlledRadioGroup.tsx b/src/layout/RadioButtons/ControlledRadioGroup.tsx
index a326bab859..a300eeb791 100644
--- a/src/layout/RadioButtons/ControlledRadioGroup.tsx
+++ b/src/layout/RadioButtons/ControlledRadioGroup.tsx
@@ -11,7 +11,6 @@ import { AltinnSpinner } from 'src/components/shared';
import { useRadioStyles } from 'src/layout/RadioButtons/radioButtonsUtils';
import { StyledRadio } from 'src/layout/RadioButtons/StyledRadio';
import { shouldUseRowLayout } from 'src/utils/layout';
-import { renderValidationMessagesForComponent } from 'src/utils/render';
import type { IRadioButtonsContainerProps } from 'src/layout/RadioButtons/RadioButtonsContainerComponent';
import type { IOption } from 'src/types';
@@ -28,7 +27,6 @@ export const ControlledRadioGroup = ({
layout,
legend,
getTextResource,
- validationMessages,
fetchingOptions,
selected,
readOnly,
@@ -72,9 +70,6 @@ export const ControlledRadioGroup = ({
value={option.value}
classes={{ root: cn(classes.formControl) }}
/>
- {validationMessages &&
- selected === option.value &&
- renderValidationMessagesForComponent(validationMessages.simpleBinding, id)}
))}
diff --git a/src/layout/RadioButtons/RadioButtonsContainerComponent.tsx b/src/layout/RadioButtons/RadioButtonsContainerComponent.tsx
index 95f748fc11..21052eca75 100644
--- a/src/layout/RadioButtons/RadioButtonsContainerComponent.tsx
+++ b/src/layout/RadioButtons/RadioButtonsContainerComponent.tsx
@@ -4,9 +4,7 @@ import { ControlledRadioGroup } from 'src/layout/RadioButtons/ControlledRadioGro
import { useRadioButtons } from 'src/layout/RadioButtons/radioButtonsUtils';
import type { PropsFromGenericComponent } from 'src/layout';
-export interface IRadioButtonsContainerProps extends PropsFromGenericComponent<'RadioButtons'> {
- validationMessages?: any;
-}
+export type IRadioButtonsContainerProps = PropsFromGenericComponent<'RadioButtons'>;
export const RadioButtonContainerComponent = (props: IRadioButtonsContainerProps) => {
const useRadioProps = useRadioButtons(props);
diff --git a/src/layout/RadioButtons/index.tsx b/src/layout/RadioButtons/index.tsx
new file mode 100644
index 0000000000..7cc14da22f
--- /dev/null
+++ b/src/layout/RadioButtons/index.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import { RadioButtonContainerComponent } from 'src/layout/RadioButtons/RadioButtonsContainerComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class RadioButtons extends LayoutComponent<'RadioButtons'> {
+ render(props: PropsFromGenericComponent<'RadioButtons'>): JSX.Element | null {
+ return ;
+ }
+
+ renderWithLabel(): boolean {
+ return false;
+ }
+}
diff --git a/src/layout/TextArea/index.tsx b/src/layout/TextArea/index.tsx
new file mode 100644
index 0000000000..06b07d6f07
--- /dev/null
+++ b/src/layout/TextArea/index.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+import { LayoutComponent } from 'src/layout/LayoutComponent';
+import { TextAreaComponent } from 'src/layout/TextArea/TextAreaComponent';
+import type { PropsFromGenericComponent } from 'src/layout';
+
+export class TextArea extends LayoutComponent<'TextArea'> {
+ render(props: PropsFromGenericComponent<'TextArea'>): JSX.Element | null {
+ return ;
+ }
+}
diff --git a/src/layout/index.ts b/src/layout/index.ts
index ac7deb15f3..fb64dfb1a4 100644
--- a/src/layout/index.ts
+++ b/src/layout/index.ts
@@ -1,65 +1,67 @@
import { createContext } from 'react';
import type React from 'react';
-import { AddressComponent as Address } from 'src/layout/Address/AddressComponent';
-import { AttachmentListComponent } from 'src/layout/AttachmentList/AttachmentListComponent';
-import { ButtonComponent } from 'src/layout/Button/ButtonComponent';
-import { CheckboxContainerComponent } from 'src/layout/Checkboxes/CheckboxesContainerComponent';
-import { CustomWebComponent } from 'src/layout/Custom/CustomWebComponent';
-import { DatepickerComponent } from 'src/layout/Datepicker/DatepickerComponent';
-import { DropdownComponent } from 'src/layout/Dropdown/DropdownComponent';
-import { FileUploadComponent } from 'src/layout/FileUpload/FileUploadComponent';
-import { FileUploadWithTagComponent } from 'src/layout/FileUploadWithTag/FileUploadWithTagComponent';
-import { HeaderComponent } from 'src/layout/Header/HeaderComponent';
-import { ImageComponent } from 'src/layout/Image/ImageComponent';
-import { InputComponent } from 'src/layout/Input/InputComponent';
-import { InstanceInformationComponent } from 'src/layout/InstanceInformation/InstanceInformationComponent';
-import { InstantiationButtonComponent } from 'src/layout/InstantiationButton/InstantiationButtonComponent';
-import { LikertComponent } from 'src/layout/Likert/LikertComponent';
-import { ListComponent } from 'src/layout/List/ListComponent';
-import { MapComponent } from 'src/layout/Map/MapComponent';
-import { MultipleSelect } from 'src/layout/MultipleSelect/MultipleSelect';
-import { NavigationBar as NavigationBarComponent } from 'src/layout/NavigationBar/NavigationBar';
-import { NavigationButtons as NavigationButtonsComponent } from 'src/layout/NavigationButtons/NavigationButtons';
-import { PanelComponent } from 'src/layout/Panel/PanelComponent';
-import { ParagraphComponent } from 'src/layout/Paragraph/ParagraphComponent';
-import { PrintButtonComponent } from 'src/layout/PrintButton/PrintButtonComponent';
-import { RadioButtonContainerComponent } from 'src/layout/RadioButtons/RadioButtonsContainerComponent';
-import { TextAreaComponent } from 'src/layout/TextArea/TextAreaComponent';
+import { Address } from 'src/layout/Address/index';
+import { AttachmentList } from 'src/layout/AttachmentList/index';
+import { Button } from 'src/layout/Button/index';
+import { Checkboxes } from 'src/layout/Checkboxes/index';
+import { Custom } from 'src/layout/Custom/index';
+import { Datepicker } from 'src/layout/Datepicker/index';
+import { Dropdown } from 'src/layout/Dropdown/index';
+import { FileUpload } from 'src/layout/FileUpload/index';
+import { FileUploadWithTag } from 'src/layout/FileUploadWithTag/index';
+import { Header } from 'src/layout/Header/index';
+import { Image } from 'src/layout/Image/index';
+import { Input } from 'src/layout/Input/index';
+import { InstanceInformation } from 'src/layout/InstanceInformation/index';
+import { InstantiationButton } from 'src/layout/InstantiationButton/index';
+import { Likert } from 'src/layout/Likert/index';
+import { List } from 'src/layout/List/index';
+import { Map } from 'src/layout/Map/index';
+import { MultipleSelect } from 'src/layout/MultipleSelect/index';
+import { NavigationBar } from 'src/layout/NavigationBar/index';
+import { NavigationButtons } from 'src/layout/NavigationButtons/index';
+import { Panel } from 'src/layout/Panel/index';
+import { Paragraph } from 'src/layout/Paragraph/index';
+import { PrintButton } from 'src/layout/PrintButton/index';
+import { RadioButtons } from 'src/layout/RadioButtons/index';
+import { TextArea } from 'src/layout/TextArea/index';
import type { ExprResolved } from 'src/features/expressions/types';
import type { IGenericComponentProps } from 'src/layout/GenericComponent';
import type { ComponentExceptGroup, ComponentExceptGroupAndSummary, IGrid, ILayoutComponent } from 'src/layout/layout';
+import type { LayoutComponent } from 'src/layout/LayoutComponent';
+import type { IComponentValidations } from 'src/types';
import type { ILanguage } from 'src/types/shared';
import type { IComponentFormData } from 'src/utils/formComponentUtils';
const components: {
- [Type in ComponentExceptGroupAndSummary]: (props: any) => JSX.Element | null;
+ [Type in ComponentExceptGroupAndSummary]: LayoutComponent;
} = {
- AddressComponent: Address,
- AttachmentList: AttachmentListComponent,
- Button: ButtonComponent,
- Checkboxes: CheckboxContainerComponent,
- Custom: CustomWebComponent,
- Datepicker: DatepickerComponent,
- Dropdown: DropdownComponent,
- FileUpload: FileUploadComponent,
- FileUploadWithTag: FileUploadWithTagComponent,
- Header: HeaderComponent,
- Image: ImageComponent,
- Input: InputComponent,
- InstanceInformation: InstanceInformationComponent,
- InstantiationButton: InstantiationButtonComponent,
- Likert: LikertComponent,
- Map: MapComponent,
- MultipleSelect: MultipleSelect,
- NavigationBar: NavigationBarComponent,
- NavigationButtons: NavigationButtonsComponent,
- Panel: PanelComponent,
- Paragraph: ParagraphComponent,
- PrintButton: PrintButtonComponent,
- RadioButtons: RadioButtonContainerComponent,
- TextArea: TextAreaComponent,
- List: ListComponent,
+ AddressComponent: new Address(),
+ AttachmentList: new AttachmentList(),
+ Button: new Button(),
+ Checkboxes: new Checkboxes(),
+ Custom: new Custom(),
+ Datepicker: new Datepicker(),
+ Dropdown: new Dropdown(),
+ FileUpload: new FileUpload(),
+ FileUploadWithTag: new FileUploadWithTag(),
+ Header: new Header(),
+ Image: new Image(),
+ Input: new Input(),
+ InstanceInformation: new InstanceInformation(),
+ InstantiationButton: new InstantiationButton(),
+ Likert: new Likert(),
+ Map: new Map(),
+ MultipleSelect: new MultipleSelect(),
+ NavigationBar: new NavigationBar(),
+ NavigationButtons: new NavigationButtons(),
+ Panel: new Panel(),
+ Paragraph: new Paragraph(),
+ PrintButton: new PrintButton(),
+ RadioButtons: new RadioButtons(),
+ TextArea: new TextArea(),
+ List: new List(),
};
export interface IComponentProps extends IGenericComponentProps {
@@ -79,6 +81,7 @@ export interface IComponentProps extends IGenericComponentProps {
legend: () => JSX.Element;
formData: IComponentFormData;
isValid?: boolean;
+ componentValidations?: IComponentValidations;
}
export type PropsFromGenericComponent = IComponentProps &
diff --git a/src/layout/layout.d.ts b/src/layout/layout.d.ts
index f3e037dc29..1e34a92d3a 100644
--- a/src/layout/layout.d.ts
+++ b/src/layout/layout.d.ts
@@ -128,6 +128,8 @@ type AllComponents = Map[ComponentTypes];
export type ComponentExceptGroup = Exclude;
export type ComponentExceptGroupAndSummary = Exclude;
+export type RenderableGenericComponent = ILayoutComponent;
+export type ComponentInGroup = RenderableGenericComponent | ILayoutGroup;
/**
* This type can be used to reference the layout declaration for a component. You can either use it to specify
diff --git a/src/utils/formComponentUtils.test.ts b/src/utils/formComponentUtils.test.ts
index dc23f75ded..27ae790472 100644
--- a/src/utils/formComponentUtils.test.ts
+++ b/src/utils/formComponentUtils.test.ts
@@ -5,7 +5,6 @@ import { AsciiUnitSeparator } from 'src/utils/attachment';
import {
atleastOneTagExists,
componentHasValidationMessages,
- componentValidationsHandledByGenericComponent,
getDisplayFormData,
getFieldName,
getFileUploadComponentValidations,
@@ -422,23 +421,6 @@ describe('formComponentUtils', () => {
});
});
- describe('componentValidationsHandledByGenericComponent', () => {
- it('should return false when dataModelBinding is undefined', () => {
- const result = componentValidationsHandledByGenericComponent(undefined, 'FileUpload');
- expect(result).toEqual(false);
- });
-
- it('should return true when component type is Datepicker', () => {
- const result = componentValidationsHandledByGenericComponent({ simpleBinding: 'group.superdate' }, 'Datepicker');
- expect(result).toEqual(true);
- });
-
- it('should return true when component type is Input', () => {
- const result = componentValidationsHandledByGenericComponent({ simpleBinding: 'group.secretnumber' }, 'Input');
- expect(result).toEqual(true);
- });
- });
-
describe('selectComponentTexts', () => {
it('should return value of mapped textResourceBinding', () => {
const textResourceBindings = {
diff --git a/src/utils/formComponentUtils.ts b/src/utils/formComponentUtils.ts
index 95bcc36095..ed8b07ed98 100644
--- a/src/utils/formComponentUtils.ts
+++ b/src/utils/formComponentUtils.ts
@@ -15,13 +15,7 @@ import {
import { getTextFromAppOrDefault } from 'src/utils/textResource';
import type { IFormData } from 'src/features/form/data';
import type { ILayoutGroup } from 'src/layout/Group/types';
-import type {
- IDataModelBindings,
- IGridStyling,
- ILayoutComponent,
- ILayoutEntry,
- ISelectionComponentProps,
-} from 'src/layout/layout';
+import type { IDataModelBindings, IGridStyling, ILayoutComponent, ISelectionComponentProps } from 'src/layout/layout';
import type { IAttachment, IAttachments } from 'src/shared/resources/attachments';
import type {
IComponentValidations,
@@ -34,13 +28,6 @@ import type {
} from 'src/types';
import type { ILanguage } from 'src/types/shared';
-export const componentValidationsHandledByGenericComponent = (
- dataModelBindings: IDataModelBindings | undefined,
- type: ILayoutEntry['type'],
-): boolean => {
- return !!dataModelBindings?.simpleBinding && type !== 'FileUpload' && type !== 'FileUploadWithTag';
-};
-
export const componentHasValidationMessages = (componentValidations: IComponentValidations | undefined) => {
if (!componentValidations) {
return false;
diff --git a/src/utils/formLayout.ts b/src/utils/formLayout.ts
index 83138108bc..eaaaf4809f 100644
--- a/src/utils/formLayout.ts
+++ b/src/utils/formLayout.ts
@@ -1,7 +1,7 @@
import { INDEX_KEY_INDICATOR_REGEX } from 'src/utils/databindings';
import type { IFormData } from 'src/features/form/data';
import type { IGroupEditProperties, IGroupFilter, ILayoutGroup } from 'src/layout/Group/types';
-import type { ComponentTypes, ILayout, ILayoutComponent } from 'src/layout/layout';
+import type { ComponentInGroup, ComponentTypes, ILayout, ILayoutComponent } from 'src/layout/layout';
import type { IAttachmentState } from 'src/shared/resources/attachments';
import type {
IFileUploadersWithTag,
@@ -273,8 +273,8 @@ export function createRepeatingGroupComponents(
repeatingGroupIndex: number,
textResources: ITextResource[],
hiddenFields?: string[],
-): Array> {
- const componentArray: Array> = [];
+): ComponentInGroup[][] {
+ const componentArray: ComponentInGroup[][] = [];
const { startIndex, stopIndex } = getRepeatingGroupStartStopIndex(repeatingGroupIndex, container.edit);
for (let index = startIndex; index <= stopIndex; index++) {
componentArray.push(
@@ -305,7 +305,7 @@ export function createRepeatingGroupComponentsForIndex({
index,
hiddenFields,
}: ICreateRepeatingGroupComponentsForIndexProps) {
- return renderComponents.map((component: ILayoutComponent | ILayoutGroup) => {
+ return renderComponents.map((component: ComponentInGroup) => {
if (component.type === 'Group' && component.panel?.groupReference) {
// Do not treat as a regular group child as this is merely an option
// to add elements for another group from this group context
@@ -344,7 +344,7 @@ export function createRepeatingGroupComponentsForIndex({
baseComponentId: componentDeepCopy.baseComponentId || componentDeepCopy.id,
hidden,
mapping,
- };
+ } as ComponentInGroup;
});
}
diff --git a/src/utils/layout/index.tsx b/src/utils/layout/index.tsx
index d350654bb0..a029a4cffe 100644
--- a/src/utils/layout/index.tsx
+++ b/src/utils/layout/index.tsx
@@ -6,7 +6,14 @@ import { PanelGroupContainer } from 'src/layout/Panel/PanelGroupContainer';
import { LayoutStyle } from 'src/types';
import { setMappingForRepeatingGroupComponent } from 'src/utils/formLayout';
import type { ILayoutGroup } from 'src/layout/Group/types';
-import type { ILayout, ILayoutComponent, ILayoutComponentOrGroup, ILayouts } from 'src/layout/layout';
+import type {
+ ComponentInGroup,
+ ILayout,
+ ILayoutComponent,
+ ILayoutComponentOrGroup,
+ ILayouts,
+ RenderableGenericComponent,
+} from 'src/layout/layout';
import type { ILayoutSet, ILayoutSets } from 'src/types';
import type { IInstance } from 'src/types/shared';
@@ -59,7 +66,7 @@ export function matchLayoutComponent(providedId: string, componentId: string) {
}
interface RenderGenericComponentProps {
- component: ILayoutComponentOrGroup;
+ component: RenderableGenericComponent | ILayoutGroup;
layout?: ILayout | null;
index?: number;
}
@@ -82,7 +89,7 @@ export function renderLayoutGroup(layoutGroup: ILayoutGroup, layout?: ILayout, i
.map((child) => {
return layout?.find((c) => c.id === child);
})
- .filter((item) => item !== undefined) as ILayoutComponentOrGroup[];
+ .filter((item) => item !== undefined) as ComponentInGroup[];
const panel = layoutGroup.panel;
if (panel) {
@@ -99,13 +106,7 @@ export function renderLayoutGroup(layoutGroup: ILayoutGroup, layout?: ILayout, i
const repeating = layoutGroup.maxCount && layoutGroup.maxCount > 1;
if (!repeating) {
// If not repeating, treat as regular components
- return (
- <>
- {deepCopyComponents.map((component: ILayoutComponent) => {
- return renderGenericComponent({ component, layout });
- })}
- >
- );
+ return <>{deepCopyComponents.map((component) => renderGenericComponent({ component, layout }))}>;
}
return (
@@ -119,11 +120,11 @@ export function renderLayoutGroup(layoutGroup: ILayoutGroup, layout?: ILayout, i
}
export function setupGroupComponents(
- components: (ILayoutComponent | ILayoutGroup)[],
+ components: ComponentInGroup[],
groupDataModelBinding: string | undefined,
index: number | undefined,
-): (ILayoutGroup | ILayoutComponent)[] {
- return components.map((component: ILayoutComponent | ILayoutGroup) => {
+): ComponentInGroup[] {
+ return components.map((component) => {
if (component.type === 'Group' && component.panel?.groupReference) {
// Do not treat as a regular group child as this is merely an option to add elements for another group from this group context
return component;
@@ -133,7 +134,7 @@ export function setupGroupComponents(
return component;
}
- const componentDeepCopy: ILayoutComponent = JSON.parse(JSON.stringify(component));
+ const componentDeepCopy: ComponentInGroup = JSON.parse(JSON.stringify(component));
const dataModelBindings = { ...componentDeepCopy.dataModelBindings };
Object.keys(dataModelBindings).forEach((key) => {
const originalGroupBinding = groupDataModelBinding.replace(`[${index}]`, '');
diff --git a/src/utils/validation/validation.ts b/src/utils/validation/validation.ts
index 66eb7ae015..cc4655030f 100644
--- a/src/utils/validation/validation.ts
+++ b/src/utils/validation/validation.ts
@@ -21,7 +21,7 @@ import { getLanguageFromKey, getParsedLanguageFromKey, getTextResourceByKey } fr
import type { IFormData } from 'src/features/form/data';
import type { ILayoutCompDatepicker } from 'src/layout/Datepicker/types';
import type { ILayoutGroup } from 'src/layout/Group/types';
-import type { IDataModelBindings, ILayout, ILayoutComponent, ILayouts } from 'src/layout/layout';
+import type { ComponentInGroup, IDataModelBindings, ILayout, ILayoutComponent, ILayouts } from 'src/layout/layout';
import type { IAttachment, IAttachments } from 'src/shared/resources/attachments';
import type {
IComponentBindingValidation,
@@ -1158,7 +1158,9 @@ export function repeatingGroupHasValidations(
return false;
}
const childGroupIndex = repeatingGroups[element.id]?.index;
- const childGroupComponents = layout.filter((childElement) => element.children?.indexOf(childElement.id) > -1);
+ const childGroupComponents = layout.filter(
+ (childElement) => element.children?.indexOf(childElement.id) > -1,
+ ) as ComponentInGroup[];
const renderComponents = setupGroupComponents(childGroupComponents, element.dataModelBindings?.group, index);
const deepCopyComponents = createRepeatingGroupComponents(
element,