Skip to content

Commit

Permalink
feat: 13614 nextrecommendedaction in config column when adding a subf…
Browse files Browse the repository at this point in the history
…orm to select a subform from list (#13827)

Co-authored-by: Jonas Dyrlie <[email protected]>
  • Loading branch information
JamalAlabdullah and Jondyr authored Oct 24, 2024
1 parent 1f22a8a commit 9ec0e3b
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 117 deletions.
6 changes: 4 additions & 2 deletions frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -1292,8 +1292,10 @@
"ux_editor.component_properties.style": "Stil",
"ux_editor.component_properties.subdomains": "Subdomener (kommaseparert)",
"ux_editor.component_properties.subform": "Sidegruppe for underskjema",
"ux_editor.component_properties.subform.choose_layout_set": "Velg sidegruppe...",
"ux_editor.component_properties.subform.choose_layout_set_label": "Velg sidegruppe å knytte til underskjema",
"ux_editor.component_properties.subform.choose_layout_set": "Velg et underskjema...",
"ux_editor.component_properties.subform.choose_layout_set_description": " Før du kan bruke komponenten Tabell for underskjema, må du velge hvilket underskjema du skal bruke den med. Deretter kan du velge hvilke egenskaper komponenten skal ha.",
"ux_editor.component_properties.subform.choose_layout_set_header": "Velg underskjemaet du vil bruke",
"ux_editor.component_properties.subform.choose_layout_set_label": "Velg et underskjema",
"ux_editor.component_properties.subform.go_to_layout_set": "Gå til utforming av underskjemaet",
"ux_editor.component_properties.subform.no_layout_sets_acting_as_subform": "Det finnes ingen sidegrupper i løsningen som kan brukes som et underskjema",
"ux_editor.component_properties.subform.selected_layout_set_label": "Underskjema",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import { StudioParagraph } from '../StudioParagraph';
import { Heading } from '@digdir/designsystemet-react';

export type StudioRecommendedNextActionProps = {
onSave: React.FormEventHandler<HTMLFormElement>;
saveButtonText: string;
onSkip: React.MouseEventHandler<HTMLButtonElement>;
skipButtonText: string;
onSave?: React.FormEventHandler<HTMLFormElement>;
saveButtonText?: string;
onSkip?: React.MouseEventHandler<HTMLButtonElement>;
skipButtonText?: string;
title: string;
description: string;
hideSaveButton?: boolean;
hideSkipButton?: boolean;
children: React.ReactNode;
};

Expand All @@ -24,6 +25,7 @@ export const StudioRecommendedNextAction = ({
title,
description,
hideSaveButton = false,
hideSkipButton,
children,
}: StudioRecommendedNextActionProps): React.ReactElement => {
const formName = useId();
Expand All @@ -44,9 +46,11 @@ export const StudioRecommendedNextAction = ({
{saveButtonText}
</StudioButton>
)}
<StudioButton onClick={onSkip} variant='tertiary'>
{skipButtonText}
</StudioButton>
{!hideSkipButton && (
<StudioButton onClick={onSkip} variant='tertiary'>
{skipButtonText}
</StudioButton>
)}
</div>
</StudioCard.Content>
</StudioCard>
Expand Down
4 changes: 3 additions & 1 deletion frontend/packages/ux-editor/src/classes/SubFormUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export class SubFormUtilsImpl implements SubFormUtils {
}

private get getSubformLayoutSets(): Array<SubFormLayoutSet> {
return this.layoutSets.filter((set) => set.type === 'subform') as Array<SubFormLayoutSet>;
return (this.layoutSets || []).filter(
(set) => set.type === 'subform',
) as Array<SubFormLayoutSet>;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,28 @@ describe('Properties', () => {
screen.getByText(textMock('right_menu.rules_calculations_deprecated_info_title')),
).toBeInTheDocument();
});

it('renders properties when formItem is not a Subform component', () => {
renderProperties({ formItem: componentMocks[ComponentType.Input] });
expect(screen.getByText(textMock('right_menu.text'))).toBeInTheDocument();
expect(screen.getByText(textMock('right_menu.data_model_bindings'))).toBeInTheDocument();
expect(screen.getByText(textMock('right_menu.content'))).toBeInTheDocument();
expect(screen.getByText(textMock('right_menu.dynamics'))).toBeInTheDocument();
expect(screen.getByText(textMock('right_menu.calculations'))).toBeInTheDocument();
});

it('render properties accordions for a subform component when it is linked to a subform layoutSet', () => {
editFormComponentSpy.mockReturnValue(<input data-testid={editFormComponentTestId}></input>);
renderProperties({
formItem: { ...componentMocks[ComponentType.SubForm], layoutSet: layoutSetName },
formItemId: componentMocks[ComponentType.SubForm].id,
});
expect(screen.getByText(textMock('right_menu.text'))).toBeInTheDocument();
expect(screen.getByText(textMock('right_menu.data_model_bindings'))).toBeInTheDocument();
expect(screen.getByText(textMock('right_menu.content'))).toBeInTheDocument();
expect(screen.getByText(textMock('right_menu.dynamics'))).toBeInTheDocument();
expect(screen.getByText(textMock('right_menu.calculations'))).toBeInTheDocument();
});
});

const getComponent = (
Expand All @@ -268,7 +290,9 @@ const renderProperties = (
},
) => {
const queryClientMock = createQueryClientMock();

queryClientMock.setQueryData([QueryKey.FormLayouts, org, app, layoutSetName], layouts);
queryClientMock.setQueryData([QueryKey.LayoutSets, org, app], layoutSet1NameMock);

return renderWithProviders(getComponent(formItemContextProps), {
queryClient: queryClientMock,
Expand Down
134 changes: 70 additions & 64 deletions frontend/packages/ux-editor/src/components/Properties/Properties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ export const Properties = () => {
const { formItemId, formItem, handleUpdate, debounceSave } = useFormItemContext();
const [openList, setOpenList] = React.useState<string[]>([]);

if (!formItem) {
return (
<div className={classes.root} key={formItemId}>
<PageConfigPanel />
</div>
);
}

const toggleOpen = (id: string) => {
if (openList.includes(id)) {
setOpenList(openList.filter((item) => item !== id));
Expand All @@ -24,72 +32,70 @@ export const Properties = () => {
}
};

const isNotSubformOrHasLayoutSet = formItem.type !== 'SubForm' || !!formItem.layoutSet;

return (
<div className={classes.root} key={formItemId}>
{!formItem ? (
<PageConfigPanel />
) : (
<>
<PropertiesHeader
formItem={formItem}
handleComponentUpdate={async (updatedComponent) => {
handleUpdate(updatedComponent);
debounceSave(formItemId, updatedComponent);
}}
/>
<Accordion color='subtle'>
<Accordion.Item open={openList.includes('text')}>
<Accordion.Header
aria-label={t('right_menu.text_label')}
onHeaderClick={() => toggleOpen('text')}
>
{t(formItem.type === 'Image' ? 'right_menu.text_and_image' : 'right_menu.text')}
</Accordion.Header>
<Accordion.Content className={classes.texts}>
<Text />
</Accordion.Content>
</Accordion.Item>
<Accordion.Item open={openList.includes('dataModel')}>
<Accordion.Header onHeaderClick={() => toggleOpen('dataModel')}>
{t('right_menu.data_model_bindings')}
</Accordion.Header>
<Accordion.Content className={classes.dataModelBindings}>
<DataModelBindings />
</Accordion.Content>
</Accordion.Item>
<Accordion.Item open={openList.includes('content')}>
<Accordion.Header onHeaderClick={() => toggleOpen('content')}>
{t('right_menu.content')}
</Accordion.Header>
<Accordion.Content>
<EditFormComponent
editFormId={formItemId}
component={formItem}
handleComponentUpdate={async (updatedComponent, mutateOptions) => {
handleUpdate(updatedComponent);
debounceSave(formItemId, updatedComponent, mutateOptions);
}}
/>
</Accordion.Content>
</Accordion.Item>
<Accordion.Item open={openList.includes('dynamics')}>
<Accordion.Header onHeaderClick={() => toggleOpen('dynamics')}>
{t('right_menu.dynamics')}
</Accordion.Header>
<Accordion.Content>
<Dynamics />
</Accordion.Content>
</Accordion.Item>
<Accordion.Item open={openList.includes('calculations')}>
<Accordion.Header onHeaderClick={(e) => toggleOpen('calculations')}>
{t('right_menu.calculations')}
</Accordion.Header>
<Accordion.Content>
<DeprecatedCalculationsInfo />
</Accordion.Content>
</Accordion.Item>
</Accordion>
</>
<PropertiesHeader
formItem={formItem}
handleComponentUpdate={async (updatedComponent) => {
handleUpdate(updatedComponent);
debounceSave(formItemId, updatedComponent);
}}
/>
{isNotSubformOrHasLayoutSet && (
<Accordion color='subtle'>
<Accordion.Item open={openList.includes('text')}>
<Accordion.Header
aria-label={t('right_menu.text_label')}
onHeaderClick={() => toggleOpen('text')}
>
{t(formItem.type === 'Image' ? 'right_menu.text_and_image' : 'right_menu.text')}
</Accordion.Header>
<Accordion.Content className={classes.texts}>
<Text />
</Accordion.Content>
</Accordion.Item>
<Accordion.Item open={openList.includes('dataModel')}>
<Accordion.Header onHeaderClick={() => toggleOpen('dataModel')}>
{t('right_menu.data_model_bindings')}
</Accordion.Header>
<Accordion.Content className={classes.dataModelBindings}>
<DataModelBindings />
</Accordion.Content>
</Accordion.Item>
<Accordion.Item open={openList.includes('content')}>
<Accordion.Header onHeaderClick={() => toggleOpen('content')}>
{t('right_menu.content')}
</Accordion.Header>
<Accordion.Content>
<EditFormComponent
editFormId={formItemId}
component={formItem}
handleComponentUpdate={async (updatedComponent, mutateOptions) => {
handleUpdate(updatedComponent);
debounceSave(formItemId, updatedComponent, mutateOptions);
}}
/>
</Accordion.Content>
</Accordion.Item>
<Accordion.Item open={openList.includes('dynamics')}>
<Accordion.Header onHeaderClick={() => toggleOpen('dynamics')}>
{t('right_menu.dynamics')}
</Accordion.Header>
<Accordion.Content>
<Dynamics />
</Accordion.Content>
</Accordion.Item>
<Accordion.Item open={openList.includes('calculations')}>
<Accordion.Header onHeaderClick={(e) => toggleOpen('calculations')}>
{t('right_menu.calculations')}
</Accordion.Header>
<Accordion.Content>
<DeprecatedCalculationsInfo />
</Accordion.Content>
</Accordion.Item>
</Accordion>
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { DefinedLayoutSet } from './DefinedLayoutSet/DefinedLayoutSet';
import { UndefinedLayoutSet } from './UndefinedLayoutSet/UndefinedLayoutSet';
import { SelectLayoutSet } from './SelectLayoutSet/SelectLayoutSet';
import { StudioRecommendedNextAction } from '@studio/components';

type EditLayoutSetProps = {
existingLayoutSetForSubform: string;
Expand All @@ -22,17 +22,27 @@ export const EditLayoutSet = ({
existingLayoutSetForSubForm={existingLayoutSetForSubform}
onUpdateLayoutSet={onUpdateLayoutSet}
onSetLayoutSetSelectorVisible={setIsLayoutSetSelectorVisible}
showButtons={true}
/>
);
}

const layoutSetIsUndefined = !existingLayoutSetForSubform;
if (layoutSetIsUndefined) {
return (
<UndefinedLayoutSet
label={t('ux_editor.component_properties.subform.selected_layout_set_label')}
onClick={() => setIsLayoutSetSelectorVisible(true)}
/>
<StudioRecommendedNextAction
title={t('ux_editor.component_properties.subform.choose_layout_set_header')}
description={t('ux_editor.component_properties.subform.choose_layout_set_description')}
hideSaveButton={true}
hideSkipButton={true}
>
<SelectLayoutSet
existingLayoutSetForSubForm={existingLayoutSetForSubform}
onUpdateLayoutSet={onUpdateLayoutSet}
onSetLayoutSetSelectorVisible={setIsLayoutSetSelectorVisible}
showButtons={false}
/>
</StudioRecommendedNextAction>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
.selectLayoutSet {
display: flex;
display: grid;
flex-direction: column;
gap: var(--fds-spacing-2);
}

.selectLayoutSetwithPadding {
padding: 0 var(--fds-spacing-5);
}

.layoutSetsOption {
text-overflow: ellipsis;
width: 100%;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@ import { EditLayoutSetButtons } from './EditLayoutSetButtons/EditLayoutSetButton
import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams';
import { useLayoutSetsQuery } from 'app-shared/hooks/queries/useLayoutSetsQuery';
import { SubFormUtilsImpl } from '../../../../../../classes/SubFormUtils';
import cn from 'classnames';

type SelectLayoutSetProps = {
existingLayoutSetForSubForm: string;
onUpdateLayoutSet: (layoutSetId: string) => void;
onSetLayoutSetSelectorVisible: (visible: boolean) => void;
showButtons?: boolean;
};

export const SelectLayoutSet = ({
existingLayoutSetForSubForm,
onUpdateLayoutSet,
onSetLayoutSetSelectorVisible,
showButtons,
}: SelectLayoutSetProps) => {
const { t } = useTranslation();
const { org, app } = useStudioEnvironmentParams();
Expand Down Expand Up @@ -48,8 +51,13 @@ export const SelectLayoutSet = ({
};

return (
<div className={classes.selectLayoutSet}>
<div
className={cn(classes.selectLayoutSet, {
[classes.selectLayoutSetwithPadding]: existingLayoutSetForSubForm,
})}
>
<StudioNativeSelect
className={classes.layoutSetsOption}
size='small'
onChange={handleLayoutSetChange}
label={t('ux_editor.component_properties.subform.choose_layout_set_label')}
Expand All @@ -63,7 +71,9 @@ export const SelectLayoutSet = ({
</option>
))}
</StudioNativeSelect>
<EditLayoutSetButtons onClose={closeLayoutSetSelector} onDelete={deleteLinkToLayoutSet} />
{showButtons && (
<EditLayoutSetButtons onClose={closeLayoutSetSelector} onDelete={deleteLinkToLayoutSet} />
)}
</div>
);
};
Loading

0 comments on commit 9ec0e3b

Please sign in to comment.