Skip to content

Commit f9893bb

Browse files
feat: 14164 implement the legg til ny component for the last four standard choices (#14392)
Co-authored-by: Michael Queyrichon <[email protected]>
1 parent ce58b90 commit f9893bb

File tree

9 files changed

+382
-54
lines changed

9 files changed

+382
-54
lines changed

frontend/language/src/nb.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1420,11 +1420,13 @@
14201420
"ux_editor.component_properties.optionalIndicator": "Vis valgfri-indikator på ledetekst",
14211421
"ux_editor.component_properties.options": "Alternativer",
14221422
"ux_editor.component_properties.optionsId": "Kodeliste",
1423+
"ux_editor.component_properties.page": "Side",
14231424
"ux_editor.component_properties.pageBreak": "PDF-innstillinger (pageBreak)",
14241425
"ux_editor.component_properties.pageRef": "Navnet til siden det gjelder (pageRef)",
14251426
"ux_editor.component_properties.pagination": "Sidenummerering",
14261427
"ux_editor.component_properties.position": "Plassering av valuta",
14271428
"ux_editor.component_properties.preselectedOptionIndex": "Angi det valget som skal være forhåndsvalgt.",
1429+
"ux_editor.component_properties.preselectedOptionIndex_button": "Plassering av forhåndsvalgt verdi (indeks)",
14281430
"ux_editor.component_properties.queryParameters": "Parametere i spørringen",
14291431
"ux_editor.component_properties.readOnly": "Feltet kan kun leses",
14301432
"ux_editor.component_properties.receiver": "Den som mottar skjemaet",
@@ -1452,7 +1454,7 @@
14521454
"ux_editor.component_properties.showIcon": "Vis ikon",
14531455
"ux_editor.component_properties.showLabelsInTable": "Alternativene skal alltid vises i tabeller",
14541456
"ux_editor.component_properties.showPageInAccordion": "Vis side i trekkspilliste",
1455-
"ux_editor.component_properties.showValidations": "Vis valideringer",
1457+
"ux_editor.component_properties.showValidations": "Vis valideringstyper",
14561458
"ux_editor.component_properties.simplified": "Forenklet visning",
14571459
"ux_editor.component_properties.size": "Størrelse",
14581460
"ux_editor.component_properties.sortOrder": "Sorteringsrekkefølge",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
.collapsibleContainer {
2+
display: flex;
3+
flex-direction: row;
4+
align-items: flex-end;
5+
gap: var(--fds-spacing-4);
6+
padding-bottom: var(--fds-spacing-4);
7+
padding-top: var(--fds-spacing-1);
8+
}
9+
10+
.collapsibleContainerClosed {
11+
margin-top: var(--fds-spacing-1);
12+
margin-bottom: var(--fds-spacing-1);
13+
}
14+
15+
.editorContent {
16+
flex: 1;
17+
}
18+
19+
.button {
20+
display: flex;
21+
gap: var(--fds-spacing-3);
22+
padding-left: 0;
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React from 'react';
2+
import {
3+
CollapsiblePropertyEditor,
4+
type CollapsiblePropertyEditorProps,
5+
} from './CollapsiblePropertyEditor';
6+
import userEvent from '@testing-library/user-event';
7+
import { screen } from '@testing-library/react';
8+
import { renderWithProviders } from 'app-development/test/mocks';
9+
import { textMock } from '@studio/testing/mocks/i18nMock';
10+
11+
// Test data
12+
const label = 'Test label';
13+
const children = <div>Test children</div>;
14+
const icon = <div>Test icon</div>;
15+
16+
describe('CollapsiblePropertyEditor', () => {
17+
it('should render the label', () => {
18+
renderCollapsiblePropertyEditor({ label: label });
19+
expect(screen.getByText('Test label')).toBeInTheDocument();
20+
});
21+
22+
it('should render the icon', () => {
23+
renderCollapsiblePropertyEditor({ icon: <div>Test icon</div> });
24+
expect(screen.getByText('Test icon')).toBeInTheDocument();
25+
});
26+
27+
it('should render the children', () => {
28+
renderCollapsiblePropertyEditor();
29+
expect(screen.queryByText('Test children')).not.toBeInTheDocument();
30+
});
31+
32+
it('should render the children when the button is clicked', async () => {
33+
const user = userEvent.setup();
34+
renderCollapsiblePropertyEditor();
35+
await user.click(screen.getByText('Test label'));
36+
expect(screen.getByText('Test children')).toBeInTheDocument();
37+
});
38+
39+
it('should hide the children when the close button is clicked', async () => {
40+
const user = userEvent.setup();
41+
renderCollapsiblePropertyEditor();
42+
await user.click(screen.getByText('Test label'));
43+
expect(screen.getByText('Test children')).toBeInTheDocument();
44+
await user.click(screen.getByRole('button', { name: textMock('general.close') }));
45+
expect(screen.queryByText('Test children')).not.toBeInTheDocument();
46+
});
47+
});
48+
49+
const defaultProps: CollapsiblePropertyEditorProps = {
50+
label,
51+
children,
52+
icon,
53+
};
54+
55+
const renderCollapsiblePropertyEditor = (props: Partial<CollapsiblePropertyEditorProps> = {}) => {
56+
renderWithProviders()(<CollapsiblePropertyEditor {...defaultProps} {...props} />);
57+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React, { useState } from 'react';
2+
import { PlusCircleIcon, XMarkIcon } from '@studio/icons';
3+
import { StudioButton, StudioProperty } from '@studio/components';
4+
import classes from './CollapsiblePropertyEditor.module.css';
5+
import { useTranslation } from 'react-i18next';
6+
import cn from 'classnames';
7+
8+
export type CollapsiblePropertyEditorProps = {
9+
label?: string;
10+
children?: React.ReactNode;
11+
icon?: React.ReactNode;
12+
disabledCloseButton?: boolean;
13+
};
14+
15+
export const CollapsiblePropertyEditor = ({
16+
label,
17+
children,
18+
disabledCloseButton = false,
19+
icon = <PlusCircleIcon />,
20+
}: CollapsiblePropertyEditorProps) => {
21+
const { t } = useTranslation();
22+
const [isVisible, setIsVisible] = useState(false);
23+
24+
return (
25+
<div
26+
className={cn(isVisible ? classes.collapsibleContainer : classes.collapsibleContainerClosed)}
27+
>
28+
{!isVisible ? (
29+
<StudioProperty.Button
30+
className={classes.button}
31+
icon={icon}
32+
onClick={() => setIsVisible(true)}
33+
property={label}
34+
/>
35+
) : (
36+
<>
37+
<div className={classes.editorContent}>{children}</div>
38+
{!disabledCloseButton && (
39+
<StudioButton
40+
icon={<XMarkIcon />}
41+
onClick={() => setIsVisible(false)}
42+
title={t('general.close')}
43+
variant='secondary'
44+
/>
45+
)}
46+
</>
47+
)}
48+
</div>
49+
);
50+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { CollapsiblePropertyEditor } from './CollapsiblePropertyEditor';

frontend/packages/ux-editor/src/components/config/FormComponentConfig.module.css

+22
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,25 @@
1111
.downIcon {
1212
font-size: var(--fds-sizing-6);
1313
}
14+
15+
.gridButton {
16+
padding: 0;
17+
}
18+
19+
.gridHeader {
20+
padding: 0;
21+
}
22+
23+
.flexContainer {
24+
display: flex;
25+
align-items: center;
26+
justify-content: space-between;
27+
}
28+
29+
.heading {
30+
margin-left: var(--fds-spacing-5);
31+
}
32+
33+
.button {
34+
margin: var(--fds-spacing-2);
35+
}

frontend/packages/ux-editor/src/components/config/FormComponentConfig.test.tsx

+134-1
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,87 @@ describe('FormComponentConfig', () => {
160160
expect(screen.queryByText('unsupportedProperty')).not.toBeInTheDocument();
161161
});
162162

163+
it('should render CollapsiblePropertyEditor for the "sortOrder" property', async () => {
164+
const user = userEvent.setup();
165+
render({
166+
props: {
167+
schema: {
168+
...InputSchema,
169+
properties: {
170+
...InputSchema.properties,
171+
sortOrder: {
172+
type: 'array',
173+
items: {
174+
type: 'string',
175+
enum: ['option1', 'option2'],
176+
},
177+
},
178+
},
179+
},
180+
},
181+
});
182+
await user.click(screen.getByText(textMock('ux_editor.component_properties.sortOrder')));
183+
expect(
184+
screen.getByRole('combobox', {
185+
name: textMock('ux_editor.component_properties.sortOrder'),
186+
}),
187+
).toBeInTheDocument();
188+
});
189+
190+
it('should render CollapsiblePropertyEditor for the "showValidations" property and EditStringValue for other properties', () => {
191+
render({
192+
props: {
193+
schema: {
194+
...InputSchema,
195+
properties: {
196+
...InputSchema.properties,
197+
showValidations: {
198+
type: 'array',
199+
items: {
200+
type: 'string',
201+
enum: ['true', 'false'],
202+
},
203+
},
204+
anotherProperty: {
205+
type: 'array',
206+
items: {
207+
type: 'string',
208+
enum: ['option1', 'option2'],
209+
},
210+
},
211+
},
212+
},
213+
},
214+
});
215+
expect(
216+
screen.getByText(textMock('ux_editor.component_properties.showValidations')),
217+
).toBeInTheDocument();
218+
});
219+
220+
it('should render CollapsiblePropertyEditor for "preselectedOptionIndex" and EditNumberValue for other properties', () => {
221+
render({
222+
props: {
223+
schema: {
224+
...InputSchema,
225+
properties: {
226+
...InputSchema.properties,
227+
preselectedOptionIndex: {
228+
type: 'number',
229+
enum: [0, 1, 2],
230+
},
231+
anotherNumberProperty: {
232+
type: 'number',
233+
description: 'A sample number property',
234+
},
235+
},
236+
},
237+
},
238+
});
239+
expect(
240+
screen.getByText(textMock('ux_editor.component_properties.preselectedOptionIndex_button')),
241+
).toBeInTheDocument();
242+
});
243+
163244
it('should not render property if it is null', () => {
164245
render({
165246
props: {
@@ -296,7 +377,8 @@ describe('FormComponentConfig', () => {
296377
).not.toBeInTheDocument();
297378
});
298379

299-
it('should only render array properties with items of type string AND enum values', () => {
380+
it('should only render array properties with items of type string AND enum values', async () => {
381+
const user = userEvent.setup();
300382
render({
301383
props: {
302384
schema: {
@@ -320,6 +402,9 @@ describe('FormComponentConfig', () => {
320402
},
321403
},
322404
});
405+
await user.click(
406+
screen.getByText(textMock('ux_editor.component_properties.supportedArrayProperty')),
407+
);
323408
expect(
324409
screen.getByRole('combobox', {
325410
name: textMock('ux_editor.component_properties.supportedArrayProperty'),
@@ -401,6 +486,54 @@ describe('FormComponentConfig', () => {
401486
);
402487
});
403488

489+
it('should toggle close button and grid width text when the open and close buttons are clicked', async () => {
490+
const user = userEvent.setup();
491+
render({
492+
props: {
493+
schema: InputSchema,
494+
},
495+
});
496+
const openGridButton = screen.getByRole('button', {
497+
name: textMock('ux_editor.component_properties.grid'),
498+
});
499+
await user.click(openGridButton);
500+
expect(screen.getByText(textMock('ux_editor.component_properties.grid'))).toBeInTheDocument();
501+
const widthText = screen.getByText(textMock('ux_editor.modal_properties_grid'));
502+
expect(widthText).toBeInTheDocument();
503+
504+
const closeGridButton = screen.getByRole('button', {
505+
name: textMock('general.close'),
506+
});
507+
await user.click(closeGridButton);
508+
expect(closeGridButton).not.toBeInTheDocument();
509+
expect(widthText).not.toBeInTheDocument();
510+
});
511+
512+
it('should not render grid width text if grid button is not clicked', async () => {
513+
const user = userEvent.setup();
514+
render({
515+
props: {
516+
schema: InputSchema,
517+
},
518+
});
519+
expect(screen.queryByText(textMock('ux_editor.modal_properties_grid'))).not.toBeInTheDocument();
520+
const openGridButton = screen.getByRole('button', {
521+
name: textMock('ux_editor.component_properties.grid'),
522+
});
523+
await user.click(openGridButton);
524+
expect(screen.getByText(textMock('ux_editor.component_properties.grid'))).toBeInTheDocument();
525+
526+
const widthText = screen.getByText(textMock('ux_editor.modal_properties_grid'));
527+
expect(widthText).toBeInTheDocument();
528+
529+
const closeGridButton = screen.getByRole('button', {
530+
name: textMock('general.close'),
531+
});
532+
await user.click(closeGridButton);
533+
expect(closeGridButton).not.toBeInTheDocument();
534+
expect(widthText).not.toBeInTheDocument();
535+
});
536+
404537
const render = ({
405538
props = {},
406539
queries = {},

0 commit comments

Comments
 (0)