Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modularized components #798

Merged
merged 10 commits into from
Jan 3, 2023
6 changes: 3 additions & 3 deletions src/features/form/components/Label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ export interface IFormLabelProps {
labelText: any;
id: string;
language: ILanguage;
required: boolean;
readOnly: boolean;
required?: boolean;
readOnly?: boolean;
labelSettings?: ILabelSettings;
helpText: string;
helpText: string | number | boolean | React.ReactNode | undefined | null;
}

export default function Label(props: IFormLabelProps) {
Expand Down
2 changes: 1 addition & 1 deletion src/features/form/components/Legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export default function Legend(props: IFormLegendProps) {
{props.descriptionText && (
<Description
description={props.descriptionText}
{...props}
id={props.id}
/>
)}
</>
Expand Down
4 changes: 2 additions & 2 deletions src/features/form/containers/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { extractBottomButtons, hasRequiredFields, topLevelComponents } from 'src
import { renderGenericComponent } from 'src/utils/layout';
import { getFormHasErrors, missingFieldsInLayoutValidations } from 'src/utils/validation';
import type { ILayoutGroup } from 'src/layout/Group/types';
import type { ILayout, ILayoutComponent } from 'src/layout/layout';
import type { ILayout, ILayoutComponent, RenderableGenericComponent } from 'src/layout/layout';

export function renderLayoutComponent(
layoutComponent: ILayoutComponent | ILayoutGroup,
Expand Down Expand Up @@ -44,7 +44,7 @@ export function renderLayoutComponent(
}
}

function GenericComponent(component: ILayoutComponent, layout: ILayout) {
function GenericComponent(component: RenderableGenericComponent, layout: ILayout) {
return renderGenericComponent({ component, layout });
}

Expand Down
7 changes: 4 additions & 3 deletions src/features/form/containers/GroupContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ import { createRepeatingGroupComponents, getRepeatingGroupFilteredIndices } from
import { getHiddenFieldsForGroup } from 'src/utils/layout';
import { renderValidationMessagesForComponent } from 'src/utils/render';
import type { ILayoutGroup } from 'src/layout/Group/types';
import type { ILayoutComponent, ILayoutComponentOrGroup } from 'src/layout/layout';
import type { ComponentInGroup, ILayoutComponent } from 'src/layout/layout';
import type { IRuntimeState } from 'src/types';

export interface IGroupProps {
id: string;
container: ILayoutGroup;
components: ILayoutComponentOrGroup[];
components: ComponentInGroup[];
triggers?: Triggers[];
}

Expand Down Expand Up @@ -193,7 +194,7 @@ export function GroupContainer({ id, container, components }: IGroupProps): JSX.
<>
<RepeatingGroupsLikertContainer
id={id}
repeatingGroupDeepCopyComponents={repeatingGroupDeepCopyComponents.map((c) => c[0] as ILayoutComponent)}
repeatingGroupDeepCopyComponents={repeatingGroupDeepCopyComponents.map((c) => c[0])}
textResources={textResources}
container={container}
/>
Expand Down
10 changes: 6 additions & 4 deletions src/features/form/containers/RepeatingGroupTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@ import { componentHasValidations, repeatingGroupHasValidations } from 'src/utils
import type { IFormData } from 'src/features/form/data';
import type { ILayoutGroup } from 'src/layout/Group/types';
import type { ILayoutCompInput } from 'src/layout/Input/types';
import type { ILayout, ILayoutComponent } from 'src/layout/layout';
import type { ComponentInGroup, ILayout, ILayoutComponent } from 'src/layout/layout';
import type { IAttachments } from 'src/shared/resources/attachments';
import type { IOptions, IRepeatingGroups, ITextResource, ITextResourceBindings, IValidations } from 'src/types';
import type { ILanguage } from 'src/types/shared';

export interface IRepeatingGroupTableProps {
id: string;
container: ILayoutGroup;
components: (ILayoutComponent | ILayoutGroup)[];
components: ComponentInGroup[];
repeatingGroupIndex: number;
repeatingGroups: IRepeatingGroups | null;
repeatingGroupDeepCopyComponents: (ILayoutComponent | ILayoutGroup)[][];
repeatingGroupDeepCopyComponents: ComponentInGroup[][];
hiddenFields: string[];
formData: IFormData;
attachments: IAttachments;
Expand Down Expand Up @@ -267,7 +267,9 @@ export function RepeatingGroupTable({
}

const childGroupIndex = repeatingGroups[childGroup.id]?.index;
const childGroupComponents = layout.filter((childElement) => childGroup.children?.indexOf(childElement.id) > -1);
const childGroupComponents = layout.filter(
(childElement) => childGroup.children?.indexOf(childElement.id) > -1,
) as ComponentInGroup[];
const childRenderComponents = setupGroupComponents(
childGroupComponents,
childGroup.dataModelBindings?.group,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,12 @@ describe('RepeatingGroupsEditContainer', () => {
const layout: ILayout = [multiPageGroup, ...components];

const repeatingGroupIndex = 3;
const repeatingGroupDeepCopyComponents: Array<Array<ILayoutComponent | ILayoutGroup>> =
createRepeatingGroupComponents(multiPageGroup, components, repeatingGroupIndex, textResources);
const repeatingGroupDeepCopyComponents = createRepeatingGroupComponents(
multiPageGroup,
components,
repeatingGroupIndex,
textResources,
);

it('calls setEditIndex when save and open next is pressed when edit.saveAndNextButton is true', async () => {
const setEditIndex = jest.fn();
Expand Down
6 changes: 3 additions & 3 deletions src/features/form/containers/RepeatingGroupsEditContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import theme from 'src/theme/altinnStudioTheme';
import { renderGenericComponent } from 'src/utils/layout';
import { getLanguageFromKey, getTextResourceByKey } from 'src/utils/sharedUtils';
import type { ILayoutGroup } from 'src/layout/Group/types';
import type { ILayout, ILayoutComponent } from 'src/layout/layout';
import type { ComponentInGroup, ILayout } from 'src/layout/layout';
import type { ITextResource } from 'src/types';
import type { ILanguage } from 'src/types/shared';

export interface IRepeatingGroupsEditContainer {
id: string;
className?: string;
container: ILayoutGroup;
repeatingGroupDeepCopyComponents: (ILayoutComponent | ILayoutGroup)[][];
repeatingGroupDeepCopyComponents: ComponentInGroup[][];
language: ILanguage;
textResources: ITextResource[];
layout: ILayout | null;
Expand Down Expand Up @@ -168,7 +168,7 @@ export function RepeatingGroupsEditContainer({
item={true}
spacing={3}
>
{repeatingGroupDeepCopyComponents[editIndex]?.map((component: ILayoutComponent) => {
{repeatingGroupDeepCopyComponents[editIndex]?.map((component) => {
if (
group.edit?.multiPage &&
typeof multiPageIndex === 'number' &&
Expand Down
22 changes: 15 additions & 7 deletions src/features/form/containers/RepeatingGroupsTable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { IRepeatingGroupTableProps } from 'src/features/form/containers/Rep
import type { IFormData } from 'src/features/form/data';
import type { ILayoutState } from 'src/features/form/layout/formLayoutSlice';
import type { ILayoutGroup } from 'src/layout/Group/types';
import type { ILayoutComponent, ISelectionComponentProps } from 'src/layout/layout';
import type { ComponentInGroup, ILayoutComponent, ISelectionComponentProps } from 'src/layout/layout';
import type { IAttachments } from 'src/shared/resources/attachments';
import type { IOption, ITextResource } from 'src/types';
import type { ILanguage } from 'src/types/shared';
Expand All @@ -21,7 +21,7 @@ import type { ILanguage } from 'src/types/shared';

const user = userEvent.setup();

const getLayout = (group: ILayoutGroup, components: ILayoutComponent[]) => {
const getLayout = (group: ILayoutGroup, components: (ILayoutComponent | ComponentInGroup)[]) => {
const layout: ILayoutState = {
layouts: {
FormLayout: [group, ...components],
Expand Down Expand Up @@ -65,7 +65,7 @@ describe('RepeatingGroupTable', () => {
const textResources: ITextResource[] = [{ id: 'option.label', value: 'Value to be shown' }];
const attachments: IAttachments = {};
const options: IOption[] = [{ value: 'option.value', label: 'option.label' }];
const components: ILayoutComponent[] = [
const components: ComponentInGroup[] = [
{
id: 'field1',
type: 'Input',
Expand Down Expand Up @@ -127,8 +127,12 @@ describe('RepeatingGroupTable', () => {
};

const repeatingGroupIndex = 3;
const repeatingGroupDeepCopyComponents: Array<Array<ILayoutComponent | ILayoutGroup>> =
createRepeatingGroupComponents(group, components, repeatingGroupIndex, textResources);
const repeatingGroupDeepCopyComponents = createRepeatingGroupComponents(
group,
components,
repeatingGroupIndex,
textResources,
);

it('should render table header when table has entries', () => {
const container = render();
Expand All @@ -150,8 +154,12 @@ describe('RepeatingGroupTable', () => {
edit: { alertOnDelete: true },
});
const layout: ILayoutState = getLayout(group, components);
const repeatingGroupDeepCopyComponents: Array<Array<ILayoutComponent | ILayoutGroup>> =
createRepeatingGroupComponents(group, components, repeatingGroupIndex, textResources);
const repeatingGroupDeepCopyComponents = createRepeatingGroupComponents(
group,
components,
repeatingGroupIndex,
textResources,
);

if (!layout.layouts) {
return;
Expand Down
4 changes: 2 additions & 2 deletions src/features/form/containers/formUtils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { ILayoutGroup } from 'src/layout/Group/types';
import type { ILayout, ILayoutComponentOrGroup } from 'src/layout/layout';
import type { ComponentInGroup, ILayout } from 'src/layout/layout';

export const mapGroupComponents = ({ children, edit }: ILayoutGroup, layout: ILayout | undefined | null) =>
children
.map((child) => {
const childId = (edit?.multiPage && child.split(':')[1]) || child;
return layout && layout.find((c) => c.id === childId);
})
.filter((c) => c !== undefined && c !== null) as ILayoutComponentOrGroup[];
.filter((c) => c !== undefined && c !== null) as ComponentInGroup[];
15 changes: 15 additions & 0 deletions src/layout/Address/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

import { AddressComponent } from 'src/layout/Address/AddressComponent';
import { LayoutComponent } from 'src/layout/LayoutComponent';
import type { PropsFromGenericComponent } from 'src/layout';

export class Address extends LayoutComponent<'AddressComponent'> {
render(props: PropsFromGenericComponent<'AddressComponent'>): JSX.Element | null {
return <AddressComponent {...props} />;
}

renderWithLabel(): boolean {
return false;
}
}
15 changes: 15 additions & 0 deletions src/layout/AttachmentList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

import { AttachmentListComponent } from 'src/layout/AttachmentList/AttachmentListComponent';
import { LayoutComponent } from 'src/layout/LayoutComponent';
import type { PropsFromGenericComponent } from 'src/layout';

export class AttachmentList extends LayoutComponent<'AttachmentList'> {
render(props: PropsFromGenericComponent<'AttachmentList'>): JSX.Element | null {
return <AttachmentListComponent {...props} />;
}

renderWithLabel(): boolean {
return false;
}
}
15 changes: 6 additions & 9 deletions src/layout/Button/ButtonComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,10 @@ import { SaveButton } from 'src/layout/Button/SaveButton';
import { SubmitButton } from 'src/layout/Button/SubmitButton';
import { ProcessActions } from 'src/shared/resources/process/processSlice';
import { getLanguageFromKey } from 'src/utils/sharedUtils';
import type { IComponentProps } from 'src/layout';
import type { ButtonMode } from 'src/layout/Button/getComponentFromMode';
import type { ILayoutCompButton } from 'src/layout/Button/types';
import type { PropsFromGenericComponent } from 'src/layout';
import type { IAltinnWindow } from 'src/types';

export interface IButtonProvidedProps extends IComponentProps, ILayoutCompButton {
disabled: boolean;
id: string;
mode?: ButtonMode;
taskId?: string;
}
export type IButtonProvidedProps = PropsFromGenericComponent<'Button'>;

export const ButtonComponent = ({ mode, ...props }: IButtonProvidedProps) => {
const dispatch = useAppDispatch();
Expand All @@ -29,6 +22,10 @@ export const ButtonComponent = ({ mode, ...props }: IButtonProvidedProps) => {
const currentTaskType = useAppSelector((state) => state.instanceData.instance?.process.currentTask?.altinnTaskType);
if (mode && !(mode === 'save' || mode === 'submit')) {
const GenericButton = getComponentFromMode(mode);
if (!GenericButton) {
return null;
}

return (
<div className='container pl-0'>
<div className={css['button-group']}>
Expand Down
29 changes: 19 additions & 10 deletions src/layout/Button/GoToTaskButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<IButtonProvidedProps>;
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(<GoToTaskButton {...allProps}>Go to task</GoToTaskButton>, {
store,
});
renderWithProviders(
<GoToTaskButton
{...mockComponentProps}
{...props}
>
Go to task
</GoToTaskButton>,
{
store,
},
);
};

describe('GoToTaskButton', () => {
Expand Down
8 changes: 3 additions & 5 deletions src/layout/Button/GoToTaskButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<ButtonProps, 'onClick'> & { taskId: string };

export const GoToTaskButton = ({ children, taskId, ...props }: Props) => {
export const GoToTaskButton = ({ children, taskId, ...props }: React.PropsWithChildren<IButtonProvidedProps>) => {
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(
Expand Down
10 changes: 8 additions & 2 deletions src/layout/Button/getComponentFromMode.ts
Original file line number Diff line number Diff line change
@@ -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<React.PropsWithChildren<IButtonProvidedProps>> | null } = {
save: null,
submit: null,
'go-to-task': GoToTaskButton,
instantiate: InstantiationButton,
};

export const getComponentFromMode = (mode: ButtonMode) => {
return buttons[mode];
};
15 changes: 15 additions & 0 deletions src/layout/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -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 <ButtonComponent {...props} />;
}

renderWithLabel(): boolean {
return false;
}
}
12 changes: 11 additions & 1 deletion src/layout/Button/types.d.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ const render = (props: Partial<ICheckboxContainerProps> = {}, customState: Prelo
options: [],
optionsId: 'countries',
preselectedOptionIndex: undefined,
validationMessages: {},
legend: () => <span>legend</span>,
handleDataChange: jest.fn(),
getTextResource: (value) => value,
Expand Down
Loading