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: update summary2 override configuration design #14379

Merged
merged 26 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}}
coderabbitai[bot] marked this conversation as resolved.
Show resolved Hide resolved
checked={override.forceShow}
>
{t('ux_editor.component_properties.summary.override.hide_empty_fields')}
</StudioSwitch>
);
};
Loading
Loading