Skip to content

Commit

Permalink
feat(subform): tablecolumns config design update (#13902)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jondyr authored Oct 31, 2024
1 parent 79d7a4f commit 755260f
Show file tree
Hide file tree
Showing 12 changed files with 342 additions and 192 deletions.
1 change: 1 addition & 0 deletions frontend/language/src/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -1710,6 +1710,7 @@
"ux_editor.properties_panel.subform_table_columns.add_column": "Legg til kolonne",
"ux_editor.properties_panel.subform_table_columns.cell_content_default_label": "Default",
"ux_editor.properties_panel.subform_table_columns.cell_content_query_label": "Query",
"ux_editor.properties_panel.subform_table_columns.choose_component": "Velg komponenten fra underskjemaet som skal vises her",
"ux_editor.properties_panel.subform_table_columns.column_header": "Kolonne {{columnNumber}}",
"ux_editor.properties_panel.subform_table_columns.delete_column": "Slett kolonne {{columnNumber}}",
"ux_editor.properties_panel.subform_table_columns.header_content_label": "Header Content",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.wrapper {
margin-top: var(--fds-spacing-4);
margin-top: var(--fds-spacing-1);
}

.headerWrapper {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import React from 'react';
import { screen } from '@testing-library/react';
import { screen, waitFor } from '@testing-library/react';
import { ColumnElement, type ColumnElementProps } from './ColumnElement';
import { textMock } from '@studio/testing/mocks/i18nMock';
import { renderWithProviders } from 'dashboard/testing/mocks';
import { createQueryClientMock } from 'app-shared/mocks/queryClientMock';
import { queriesMock } from 'app-shared/mocks/queriesMock';
import userEvent from '@testing-library/user-event';
import { type TableColumn } from '../types/TableColumn';
import { layoutSet3SubformNameMock } from '../../../../testing/layoutSetsMock';
import { QueryKey } from 'app-shared/types/QueryKey';
import { app, org } from '@studio/testing/testids';
import { subformLayoutMock } from '../../../../testing/subformLayoutMock';

const headerContentMock: string = 'Header';
const cellContentQueryMock: string = 'Query';
Expand All @@ -26,6 +30,7 @@ const defaultProps: ColumnElementProps = {
columnNumber: columnNumberMock,
onDeleteColumn: jest.fn(),
onEdit: jest.fn(),
layoutSetName: layoutSet3SubformNameMock,
};

describe('ColumnElement', () => {
Expand All @@ -41,79 +46,33 @@ describe('ColumnElement', () => {
onEdit: onEditMock,
});

const headerInputbutton = screen.getByRole('button', {
name: `${textMock('ux_editor.properties_panel.subform_table_columns.header_content_label')}: ${headerContentMock}`,
const editButton = screen.getByRole('button', {
name: /ux_editor.properties_panel.subform_table_columns.column_header/,
});
await user.click(headerInputbutton);
await user.click(editButton);

const headerInputfield = screen.getByLabelText(
textMock('ux_editor.properties_panel.subform_table_columns.header_content_label'),
);
const newValue: string = 'a';
await user.type(headerInputfield, newValue);
await user.tab();

expect(onEditMock).toHaveBeenCalledTimes(1);
expect(onEditMock).toHaveBeenCalledWith({
...mockTableColumn,
headerContent: `${headerContentMock}${newValue}`,
});
});

it('should call onEdit with updated query content when query text field is blurred', async () => {
const onEditMock = jest.fn();

const user = userEvent.setup();
renderColumnElement({
onEdit: onEditMock,
});

const queryInputbutton = screen.getByRole('button', {
name: `${textMock('ux_editor.properties_panel.subform_table_columns.cell_content_query_label')}: ${cellContentQueryMock}`,
const componentSelect = screen.getByRole('combobox', {
name: textMock('ux_editor.properties_panel.subform_table_columns.choose_component'),
});
await user.click(queryInputbutton);

const queryInputfield = screen.getByLabelText(
textMock('ux_editor.properties_panel.subform_table_columns.cell_content_query_label'),
await user.click(componentSelect);
await user.click(
screen.getByRole('option', { name: new RegExp(`${subformLayoutMock.component1Id}`) }),
);
const newValue: string = 'a';
await user.type(queryInputfield, newValue);
await user.tab();

expect(onEditMock).toHaveBeenCalledTimes(1);
expect(onEditMock).toHaveBeenCalledWith({
...mockTableColumn,
cellContent: { ...mockTableColumn.cellContent, query: `${cellContentQueryMock}${newValue}` },
await waitFor(async () => {
await user.click(
screen.getByRole('button', {
name: textMock('general.save'),
}),
);
});
});

it('should call onEdit with updated default content when default text field is blurred', async () => {
const onEditMock = jest.fn();

const user = userEvent.setup();
renderColumnElement({
onEdit: onEditMock,
});

const defaultInputbutton = screen.getByRole('button', {
name: `${textMock('ux_editor.properties_panel.subform_table_columns.cell_content_default_label')}: ${cellContentDefaultMock}`,
});
await user.click(defaultInputbutton);

const defaultInputfield = screen.getByLabelText(
textMock('ux_editor.properties_panel.subform_table_columns.cell_content_default_label'),
);
const newValue: string = 'a';
await user.type(defaultInputfield, newValue);
await user.tab();

expect(onEditMock).toHaveBeenCalledTimes(1);
expect(onEditMock).toHaveBeenCalledWith({
...mockTableColumn,
cellContent: {
...mockTableColumn.cellContent,
default: `${cellContentDefaultMock}${newValue}`,
},
headerContent: subformLayoutMock.component1.textResourceBindings.title,
cellContent: { query: subformLayoutMock.component1.dataModelBindings.simpleBinding },
});
});

Expand All @@ -125,10 +84,12 @@ describe('ColumnElement', () => {
onDeleteColumn: onDeleteColumnMock,
});

const editButton = screen.getByRole('button', {
name: /ux_editor.properties_panel.subform_table_columns.column_header/,
});
await user.click(editButton);
const deleteButton = screen.getByRole('button', {
name: textMock('ux_editor.properties_panel.subform_table_columns.delete_column', {
columnNumber: columnNumberMock,
}),
name: textMock('general.delete'),
});
await user.click(deleteButton);

Expand All @@ -138,6 +99,10 @@ describe('ColumnElement', () => {

const renderColumnElement = (props: Partial<ColumnElementProps> = {}) => {
const queryClient = createQueryClientMock();
queryClient.setQueryData(
[QueryKey.FormLayouts, org, app, layoutSet3SubformNameMock],
subformLayoutMock.layoutSet,
);
return renderWithProviders(<ColumnElement {...defaultProps} {...props} />, {
...queriesMock,
queryClient,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React, { type ReactElement, type ChangeEvent } from 'react';
import React, { useState, type ReactElement } from 'react';
import classes from './ColumnElement.module.css';
import { type TableColumn } from '../types/TableColumn';
import { useTranslation } from 'react-i18next';
import {
StudioButton,
StudioLabelAsParagraph,
StudioToggleableTextfield,
} from '@studio/components';
import { KeyVerticalFillIcon, TrashFillIcon } from '@studio/icons';
import { StudioProperty } from '@studio/components';
import { EditColumnElement } from './EditColumnElement';
import { useTextResourcesQuery } from 'app-shared/hooks/queries';
import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams';
import { textResourceByLanguageAndIdSelector } from '../../../../selectors/textResourceSelectors';

export type ColumnElementProps = {
layoutSetName: string;
tableColumn: TableColumn;
columnNumber: number;
onDeleteColumn: () => void;
Expand All @@ -21,110 +21,45 @@ export const ColumnElement = ({
columnNumber,
onDeleteColumn,
onEdit,
layoutSetName,
}: ColumnElementProps): ReactElement => {
const { t } = useTranslation();
const [editing, setEditing] = useState(false);
const { org, app } = useStudioEnvironmentParams();
const { data: textResources } = useTextResourcesQuery(org, app);

const handleEditHeaderContent = (event: ChangeEvent<HTMLInputElement>) => {
onEdit({ ...tableColumn, headerContent: event.target.value });
};

const handleEditQuery = (event: ChangeEvent<HTMLInputElement>) => {
onEdit({
...tableColumn,
cellContent: { ...tableColumn.cellContent, query: event.target.value },
});
};

const handleEditDefault = (event: ChangeEvent<HTMLInputElement>) => {
onEdit({
...tableColumn,
cellContent: { ...tableColumn.cellContent, default: event.target.value },
});
};
return (
<div className={classes.wrapper}>
<TableColumnHeader columnNumber={columnNumber} onDeleteColumn={onDeleteColumn} />
<TableColumnToggleableTextfield
label={t('ux_editor.properties_panel.subform_table_columns.header_content_label')}
value={tableColumn.headerContent}
onBlur={handleEditHeaderContent}
required={true}
/>
<TableColumnToggleableTextfield
label={t('ux_editor.properties_panel.subform_table_columns.cell_content_query_label')}
value={tableColumn.cellContent.query}
onBlur={handleEditQuery}
required={true}
/>
<TableColumnToggleableTextfield
label={t('ux_editor.properties_panel.subform_table_columns.cell_content_default_label')}
value={tableColumn.cellContent.default}
onBlur={handleEditDefault}
/>
</div>
);
};

type TableColumnHeaderProps = {
columnNumber: number;
onDeleteColumn: () => void;
};

const TableColumnHeader = ({
columnNumber,
onDeleteColumn,
}: TableColumnHeaderProps): ReactElement => {
const { t } = useTranslation();
const textKeyValue = textResourceByLanguageAndIdSelector(
'nb',
tableColumn.headerContent,
)(textResources)?.value;

return (
<div className={classes.headerWrapper}>
<StudioLabelAsParagraph size='sm'>
{t('ux_editor.properties_panel.subform_table_columns.column_header', { columnNumber })}
</StudioLabelAsParagraph>
<StudioButton
icon={<TrashFillIcon />}
title={t('ux_editor.properties_panel.subform_table_columns.delete_column', {
columnNumber,
})}
onClick={onDeleteColumn}
color='danger'
variant='secondary'
if (editing) {
return (
<EditColumnElement
layoutSetName={layoutSetName}
sourceColumn={tableColumn}
columnNumber={columnNumber}
onDeleteColumn={onDeleteColumn}
onEdit={(col) => {
setEditing(false);
onEdit(col);
}}
/>
</div>
);
};
);
}

type TableColumnToggleableTextfieldProps = {
label: string;
value: string;
onBlur: (event: ChangeEvent<HTMLInputElement>) => void;
required?: boolean;
};
const handleClickEdit = (): void => {
setEditing(true);
};

const TableColumnToggleableTextfield = ({
label,
value,
onBlur,
required = false,
}: TableColumnToggleableTextfieldProps): ReactElement => {
return (
<StudioToggleableTextfield
inputProps={{
icon: <KeyVerticalFillIcon />,
label,
value,
size: 'sm',
required,
onBlur,
}}
viewProps={{
children: (
<span>
<b>{label}:</b> {value}
</span>
),
variant: 'tertiary',
}}
/>
<StudioProperty.Button
className={classes.wrapper}
onClick={handleClickEdit}
property={t('ux_editor.properties_panel.subform_table_columns.column_header', {
columnNumber,
})}
value={textKeyValue}
></StudioProperty.Button>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.wrapper {
margin-top: var(--fds-spacing-4);
padding: var(--fds-spacing-3);
}

.header {
padding-top: var(--fds-spacing-2);
padding-bottom: var(--fds-spacing-0);
padding-left: var(--fds-spacing-0);
}

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

.divider {
margin-top: var(--fds-spacing-0);
}
.buttons {
display: flex;
gap: var(--fds-spacing-2);
}
Loading

0 comments on commit 755260f

Please sign in to comment.