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

feat: 13614 nextrecommendedaction in config column when adding a subform to select a subform from list #13827

Merged
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>
);
}
lassopicasso marked this conversation as resolved.
Show resolved Hide resolved

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) {
lassopicasso marked this conversation as resolved.
Show resolved Hide resolved
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,
})}
>
lassopicasso marked this conversation as resolved.
Show resolved Hide resolved
<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