Skip to content

Commit

Permalink
feat: update summary2 override configuration design (#14379)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jondyr authored Jan 23, 2025
1 parent 4f62d96 commit fd014a5
Show file tree
Hide file tree
Showing 18 changed files with 573 additions and 142 deletions.
7 changes: 6 additions & 1 deletion frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -1467,10 +1467,15 @@
"ux_editor.component_properties.summary.override.display_type.not_set": "Ikke satt",
"ux_editor.component_properties.summary.override.display_type.string": "Tekst",
"ux_editor.component_properties.summary.override.empty_field_text": "Tekst du vil vise i tomme felt",
"ux_editor.component_properties.summary.override.force_show": "Vis komponenten",
"ux_editor.component_properties.summary.override.hidden": "Skjul feltet",
"ux_editor.component_properties.summary.override.hide_empty_fields": "Skjul tomme felter",
"ux_editor.component_properties.summary.override.hide_empty_fields": "Vis tomme felt",
"ux_editor.component_properties.summary.override.hide_empty_fields.info_message": "Du kan bare skjule komponenten fra oppsummeringen hvis feltet er valgfritt.",
"ux_editor.component_properties.summary.override.is_compact": "Bruk kompakt visning",
"ux_editor.component_properties.summary.override.show_component": "Vis komponenten",
"ux_editor.component_properties.summary.override.title": "Overstyr hva som skal vises",
"ux_editor.component_properties.summary.overrides": "Overstyringer",
"ux_editor.component_properties.summary.overrides.nth": "Overstyring {{n}}",
"ux_editor.component_properties.summaryDelimiter": "Skillelinje for sammendragsvisningsceller",
"ux_editor.component_properties.tableColumns": "Innstillinger for kolonner",
"ux_editor.component_properties.tableHeaders": "Felter som skal vises i tabellens overskrift",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from 'react';
import { screen } from '@testing-library/react';
import { renderWithProviders } from '../../../testing/mocks';
import { ComponentMainConfig } from './ComponentMainConfig';
import type { FormItem } from '../../../types/FormItem';
import { ComponentType } from 'app-shared/types/ComponentType';
import userEvent from '@testing-library/user-event';
import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
import { QueryKey } from 'app-shared/types/QueryKey';
import { app, org } from '@studio/testing/testids';
import { layoutSet1NameMock } from '../../../testing/layoutSetsMock';
import { layout1NameMock, layoutMock } from '../../../testing/layoutMock';

const summary2Component: FormItem = {
id: '0',
type: ComponentType.Summary2,
itemType: 'COMPONENT',
target: {},
};

describe('ComponentMainConfig', () => {
describe('Summary2', () => {
it('should render summary2 config', async () => {
const user = userEvent.setup();
render(summary2Component);
expect(summary2AccordionButton()).toBeInTheDocument();
await user.click(summary2AccordionButton());
expect(summary2AddOverrideButton()).toBeInTheDocument();
});

it('should display overrides', async () => {
const user = userEvent.setup();
const summary2ComponentWithOverrides = {
...summary2Component,
overrides: [{ componentId: '0' }],
};
render(summary2ComponentWithOverrides);
await user.click(summary2AccordionButton());
expect(summary2CollapsedButton(1)).toBeInTheDocument();
});

it('should call handleComponentChange when adding overrides', async () => {
const user = userEvent.setup();
render(summary2Component);
await user.click(summary2AccordionButton());
await user.click(summary2AddOverrideButton());
expect(handleComponentChange).toHaveBeenCalledTimes(1);
});
});
});

const summary2AccordionButton = () =>
screen.getByRole('button', { name: /ux_editor.component_properties.summary.override.title/ });
const summary2AddOverrideButton = () =>
screen.getByRole('button', { name: /ux_editor.component_properties.summary.add_override/ });
const summary2CollapsedButton = (n: number) =>
screen.getByRole('button', {
name: new RegExp(`ux_editor.component_properties.summary.overrides.nth.*:${n}}`),
});

const handleComponentChange = jest.fn();
const render = (component: FormItem) => {
const queryClient = createQueryClientMock();
queryClient.setQueryData([QueryKey.FormLayouts, org, app, layoutSet1NameMock], {
[layout1NameMock]: layoutMock,
});
renderWithProviders(
<ComponentMainConfig component={component} handleComponentChange={handleComponentChange} />,
{
queryClient,
appContextProps: {
selectedFormLayoutSetName: layoutSet1NameMock,
selectedFormLayoutName: layout1NameMock,
},
},
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';
import type { FormItem } from '../../../types/FormItem';
import { ComponentType } from 'app-shared/types/ComponentType';
import { Accordion } from '@digdir/designsystemet-react';
import { useTranslation } from 'react-i18next';
import { Summary2Override } from '../../config/componentSpecificContent/Summary2/Override/Summary2Override';
import type { Summary2OverrideConfig } from 'app-shared/types/ComponentSpecificConfig';

export type ComponentMainConfigProps = {
component: FormItem;
handleComponentChange: (component: FormItem) => void;
};

export const ComponentMainConfig = ({
component,
handleComponentChange,
}: ComponentMainConfigProps) => {
const [accordionOpen, setAccordionOpen] = React.useState<Record<string, boolean>>({});
const { t } = useTranslation();

const handleOverridesChange = (updatedOverrides: Summary2OverrideConfig[]): void => {
const updatedComponent = { ...component } as FormItem<ComponentType.Summary2>;
updatedComponent.overrides = updatedOverrides;
handleComponentChange(updatedComponent);
};

return (
<>
{component.type === ComponentType.Summary2 && (
<Accordion color='subtle'>
<Accordion.Item open={accordionOpen['summary2overrides'] === true}>
<Accordion.Header
onHeaderClick={() =>
setAccordionOpen((prev) => {
return { ...prev, summary2overrides: !prev['summary2overrides'] };
})
}
>
{t('ux_editor.component_properties.summary.override.title')}
</Accordion.Header>
<Accordion.Content>
<Summary2Override overrides={component.overrides} onChange={handleOverridesChange} />
</Accordion.Content>
</Accordion.Item>
</Accordion>
)}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { EditComponentIdRow } from './EditComponentIdRow';
import type { FormItem } from '../../../types/FormItem';
import { ComponentType } from 'app-shared/types/ComponentType';
import { EditLayoutSetForSubform } from './EditLayoutSetForSubform';
import { ComponentMainConfig } from './ComponentMainConfig';

export type PropertiesHeaderProps = {
formItem: FormItem;
Expand Down Expand Up @@ -49,6 +50,7 @@ export const PropertiesHeader = ({
/>
)}
</div>
<ComponentMainConfig component={formItem} handleComponentChange={handleComponentUpdate} />
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { type ChangeEvent } from 'react';
import type { Summary2OverrideConfig } from 'app-shared/types/ComponentSpecificConfig';
import { StudioSwitch } from '@studio/components';
import { useTranslation } from 'react-i18next';
import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams';
import { useAppContext } from '../../../../../../hooks';
import { useFormLayoutsQuery } from '../../../../../../hooks/queries/useFormLayoutsQuery';
import { getAllLayoutComponents } from '../../../../../../utils/formLayoutUtils';
import { ComponentType } from 'app-shared/types/ComponentType';

type CompactViewSwitchProps = {
onChange: (updatedOverride: Summary2OverrideConfig) => void;
override: Summary2OverrideConfig;
};

export const CompactViewSwitch = ({ onChange, override }: CompactViewSwitchProps) => {
const { t } = useTranslation();
const { org, app } = useStudioEnvironmentParams();
const { selectedFormLayoutSetName } = useAppContext();
const { data: formLayoutsData } = useFormLayoutsQuery(org, app, selectedFormLayoutSetName);

const components = Object.values(formLayoutsData).flatMap((layout) =>
getAllLayoutComponents(layout),
);
const component = components.find((comp) => comp.id === override.componentId);
const isGroupComponent = component?.type === (ComponentType.Group as ComponentType);

if (!isGroupComponent) {
return null;
}
return (
<StudioSwitch
position='right'
size='sm'
onChange={(event: ChangeEvent<HTMLInputElement>) =>
onChange({ ...override, isCompact: event.target.checked })
}
checked={override.isCompact ?? false}
value='isCompact'
>
{t('ux_editor.component_properties.summary.override.is_compact')}
</StudioSwitch>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.propertyButton {
padding-left: var(--fds-spacing-0);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import { renderWithProviders } from '../../../../../../testing/mocks';
import { EmptyTextField } from './EmptyTextField';
import { component1IdMock } from '../../../../../../testing/layoutMock';
import { userEvent } from '@testing-library/user-event';
import { screen } from '@testing-library/react';
import type { Summary2OverrideConfig } from 'app-shared/types/ComponentSpecificConfig';

describe('EmptyTextField', () => {
it('Should display textfield when openbutton is clicked', async () => {
const user = userEvent.setup();
render();
await user.click(emptyTextFieldButton());
expect(emptyTextFieldTextBox()).toBeInTheDocument();
});

it('should call onChange prop when textbox is edited', async () => {
const user = userEvent.setup();
render();
await user.click(emptyTextFieldButton());
const inputtext = 'test string 123@';
await user.type(emptyTextFieldTextBox(), inputtext);
expect(onChangeMock).toHaveBeenCalledWith(
expect.objectContaining({ emptyFieldText: inputtext }),
);
});

it('should close editor on input blur', async () => {
const user = userEvent.setup();
render();
await user.click(emptyTextFieldButton());
await user.type(emptyTextFieldTextBox(), 'inputtext');
expect(emptyTextFieldTextBox()).toBeInTheDocument();
await user.click(document.body);
expect(emptyTextFieldTextBox()).not.toBeInTheDocument();
});

it('should close editor when pressing enter', async () => {
const user = userEvent.setup();
render();
await user.click(emptyTextFieldButton());
await user.type(emptyTextFieldTextBox(), 'inputtext');
expect(emptyTextFieldTextBox()).toBeInTheDocument();
await user.type(emptyTextFieldTextBox(), '{enter}');
expect(emptyTextFieldTextBox()).not.toBeInTheDocument();
});
});

const emptyTextFieldButton = () =>
screen.getByRole('button', {
name: /ux_editor.component_properties.summary.override.empty_field_text/i,
});
const emptyTextFieldTextBox = () =>
screen.queryByRole('textbox', {
name: /ux_editor.component_properties.summary.override.empty_field_text/i,
});

const onChangeMock = jest.fn();

const render = () => {
const override: Summary2OverrideConfig = {
componentId: component1IdMock,
hideEmptyFields: false,
forceShow: true,
hidden: false,
};
return renderWithProviders(<EmptyTextField onChange={onChangeMock} override={override} />);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { type ChangeEvent } from 'react';
import classes from './EmptyTextField.module.css';
import type { Summary2OverrideConfig } from 'app-shared/types/ComponentSpecificConfig';
import { StudioAlert, StudioProperty, StudioTextfield } from '@studio/components';
import { useTranslation } from 'react-i18next';

type EmptyTextFieldProps = {
onChange: (updatedOverride: Summary2OverrideConfig) => void;
override: Summary2OverrideConfig;
};

export const EmptyTextField = ({ onChange, override }: EmptyTextFieldProps) => {
const { t } = useTranslation();
const [open, setOpen] = React.useState(false);

if (override.hideEmptyFields || !override.forceShow) {
return (
<StudioAlert>
{t('ux_editor.component_properties.summary.override.hide_empty_fields.info_message')}
</StudioAlert>
);
}

if (!open) {
return (
<StudioProperty.Button
className={classes.propertyButton}
value={override.emptyFieldText}
property={t('ux_editor.component_properties.summary.override.empty_field_text')}
onClick={() => setOpen(true)}
></StudioProperty.Button>
);
}

return (
<StudioTextfield
label={t('ux_editor.component_properties.summary.override.empty_field_text')}
autoFocus={true}
onBlur={() => setOpen(false)}
onKeyDown={({ key }) => key === 'Enter' && setOpen(false)}
value={override.emptyFieldText}
onChange={(event: ChangeEvent<HTMLInputElement>) =>
onChange({ ...override, emptyFieldText: event.target.value })
}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import type { Summary2OverrideConfig } from 'app-shared/types/ComponentSpecificConfig';
import { StudioSwitch } from '@studio/components';
import { useTranslation } from 'react-i18next';

type ForceShowSwitchProps = {
onChange: (updatedOverride: Summary2OverrideConfig) => void;
override: Summary2OverrideConfig;
};

export const OverrideShowComponentSwitch = ({ onChange, override }: ForceShowSwitchProps) => {
const { t } = useTranslation();
return (
<StudioSwitch
position='right'
size='sm'
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
onChange({ ...override, hidden: !event.target.checked })
}
checked={!override.hidden}
value={'hidden'}
>
{t('ux_editor.component_properties.summary.override.show_component')}
</StudioSwitch>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import type { Summary2OverrideConfig } from 'app-shared/types/ComponentSpecificConfig';
import { StudioSwitch } from '@studio/components';
import { useTranslation } from 'react-i18next';

type ShowEmptyFieldSwitchProps = {
onChange: (override: Summary2OverrideConfig) => void;
override: Summary2OverrideConfig;
};

export const ShowEmptyFieldSwitch = ({ onChange, override }: ShowEmptyFieldSwitchProps) => {
const { t } = useTranslation();
return (
<StudioSwitch
position='right'
size='sm'
onChange={async (event: React.ChangeEvent<HTMLInputElement>) => {
const updatedOverride = {
...override,
hideEmptyFields: !event.target.checked,
forceShow: event.target.checked,
};
onChange(updatedOverride);
}}
checked={override.forceShow}
>
{t('ux_editor.component_properties.summary.override.hide_empty_fields')}
</StudioSwitch>
);
};
Loading

0 comments on commit fd014a5

Please sign in to comment.