From 0df80450cc93dec40e349168f3b351e586c6b562 Mon Sep 17 00:00:00 2001
From: JamalAlabdullah <90609090+JamalAlabdullah@users.noreply.github.com>
Date: Thu, 20 Feb 2025 17:47:53 +0100
Subject: [PATCH] fix: 14472 should show the value of selector in properties
config (#14686)
---
frontend/language/src/nb.json | 2 +-
.../CollapsiblePropertyEditor.module.css | 23 -----
.../CollapsiblePropertyEditor.test.tsx | 57 -----------
.../CollapsiblePropertyEditor.tsx | 50 ----------
.../config/CollapsiblePropertyEditor/index.ts | 1 -
.../config/FormComponentConfig.module.css | 1 +
.../config/FormComponentConfig.test.tsx | 99 ++++++++++++++++---
.../components/config/FormComponentConfig.tsx | 59 ++++++++---
.../SelectPropertyEditor.module.css | 27 +++++
.../SelectPropertyEditor.test.tsx | 48 +++++++++
.../SelectPropertyEditor.tsx | 47 +++++++++
.../config/SelectPropertyEditor/index.ts | 1 +
12 files changed, 260 insertions(+), 155 deletions(-)
delete mode 100644 frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.module.css
delete mode 100644 frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.test.tsx
delete mode 100644 frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.tsx
delete mode 100644 frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/index.ts
create mode 100644 frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.module.css
create mode 100644 frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.test.tsx
create mode 100644 frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.tsx
create mode 100644 frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/index.ts
diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json
index 4272bb5fc23..15e54fc8b05 100644
--- a/frontend/language/src/nb.json
+++ b/frontend/language/src/nb.json
@@ -1456,7 +1456,7 @@
"ux_editor.component_properties.showIcon": "Vis ikon",
"ux_editor.component_properties.showLabelsInTable": "Alternativene skal alltid vises i tabeller",
"ux_editor.component_properties.showPageInAccordion": "Vis side i trekkspilliste",
- "ux_editor.component_properties.showValidations": "Vis valideringstyper",
+ "ux_editor.component_properties.showValidations": "Valideringstyper",
"ux_editor.component_properties.simplified": "Forenklet visning",
"ux_editor.component_properties.size": "Størrelse",
"ux_editor.component_properties.sortOrder": "Sorteringsrekkefølge",
diff --git a/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.module.css b/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.module.css
deleted file mode 100644
index ed1fcde3172..00000000000
--- a/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.module.css
+++ /dev/null
@@ -1,23 +0,0 @@
-.collapsibleContainer {
- display: flex;
- flex-direction: row;
- align-items: flex-end;
- gap: var(--fds-spacing-4);
- padding-bottom: var(--fds-spacing-4);
- padding-top: var(--fds-spacing-1);
-}
-
-.collapsibleContainerClosed {
- margin-top: var(--fds-spacing-1);
- margin-bottom: var(--fds-spacing-1);
-}
-
-.editorContent {
- flex: 1;
-}
-
-.button {
- display: flex;
- gap: var(--fds-spacing-3);
- padding-left: 0;
-}
diff --git a/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.test.tsx b/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.test.tsx
deleted file mode 100644
index 56f1f156a11..00000000000
--- a/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.test.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import React from 'react';
-import {
- CollapsiblePropertyEditor,
- type CollapsiblePropertyEditorProps,
-} from './CollapsiblePropertyEditor';
-import userEvent from '@testing-library/user-event';
-import { screen } from '@testing-library/react';
-import { renderWithProviders } from 'app-development/test/mocks';
-import { textMock } from '@studio/testing/mocks/i18nMock';
-
-// Test data
-const label = 'Test label';
-const children =
Test children
;
-const icon = Test icon
;
-
-describe('CollapsiblePropertyEditor', () => {
- it('should render the label', () => {
- renderCollapsiblePropertyEditor({ label: label });
- expect(screen.getByText('Test label')).toBeInTheDocument();
- });
-
- it('should render the icon', () => {
- renderCollapsiblePropertyEditor({ icon: Test icon
});
- expect(screen.getByText('Test icon')).toBeInTheDocument();
- });
-
- it('should render the children', () => {
- renderCollapsiblePropertyEditor();
- expect(screen.queryByText('Test children')).not.toBeInTheDocument();
- });
-
- it('should render the children when the button is clicked', async () => {
- const user = userEvent.setup();
- renderCollapsiblePropertyEditor();
- await user.click(screen.getByText('Test label'));
- expect(screen.getByText('Test children')).toBeInTheDocument();
- });
-
- it('should hide the children when the close button is clicked', async () => {
- const user = userEvent.setup();
- renderCollapsiblePropertyEditor();
- await user.click(screen.getByText('Test label'));
- expect(screen.getByText('Test children')).toBeInTheDocument();
- await user.click(screen.getByRole('button', { name: textMock('general.close') }));
- expect(screen.queryByText('Test children')).not.toBeInTheDocument();
- });
-});
-
-const defaultProps: CollapsiblePropertyEditorProps = {
- label,
- children,
- icon,
-};
-
-const renderCollapsiblePropertyEditor = (props: Partial = {}) => {
- renderWithProviders()();
-};
diff --git a/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.tsx b/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.tsx
deleted file mode 100644
index e4ea3c369ce..00000000000
--- a/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import React, { useState } from 'react';
-import { PlusCircleIcon, XMarkIcon } from '@studio/icons';
-import { StudioButton, StudioProperty } from '@studio/components';
-import classes from './CollapsiblePropertyEditor.module.css';
-import { useTranslation } from 'react-i18next';
-import cn from 'classnames';
-
-export type CollapsiblePropertyEditorProps = {
- label?: string;
- children?: React.ReactNode;
- icon?: React.ReactNode;
- disabledCloseButton?: boolean;
-};
-
-export const CollapsiblePropertyEditor = ({
- label,
- children,
- disabledCloseButton = false,
- icon = ,
-}: CollapsiblePropertyEditorProps) => {
- const { t } = useTranslation();
- const [isVisible, setIsVisible] = useState(false);
-
- return (
-
- {!isVisible ? (
-
setIsVisible(true)}
- property={label}
- />
- ) : (
- <>
- {children}
- {!disabledCloseButton && (
- }
- onClick={() => setIsVisible(false)}
- title={t('general.close')}
- variant='secondary'
- />
- )}
- >
- )}
-
- );
-};
diff --git a/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/index.ts b/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/index.ts
deleted file mode 100644
index b7790d086df..00000000000
--- a/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { CollapsiblePropertyEditor } from './CollapsiblePropertyEditor';
diff --git a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.module.css b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.module.css
index 6f4fe987c4c..1a8653696b4 100644
--- a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.module.css
+++ b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.module.css
@@ -14,6 +14,7 @@
.gridButton {
padding: 0;
+ margin-bottom: var(--fds-spacing-0);
}
.gridHeader {
diff --git a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.test.tsx b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.test.tsx
index c219f0340ff..52e4a0a3e1a 100644
--- a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.test.tsx
+++ b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.test.tsx
@@ -6,7 +6,7 @@ import { componentMocks } from '../../testing/componentMocks';
import InputSchema from '../../testing/schemas/json/component/Input.schema.v1.json';
import DatepickerSchema from '../../testing/schemas/json/component/Datepicker.schema.v1.json';
import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext';
-import { screen } from '@testing-library/react';
+import { screen, waitFor } from '@testing-library/react';
import { textMock } from '@studio/testing/mocks/i18nMock';
import type { KeyValuePairs } from 'app-shared/types/KeyValuePairs';
import userEvent from '@testing-library/user-event';
@@ -160,7 +160,7 @@ describe('FormComponentConfig', () => {
expect(screen.queryByText('unsupportedProperty')).not.toBeInTheDocument();
});
- it('should render CollapsiblePropertyEditor for the "sortOrder" property', async () => {
+ it('should render property text for the "sortOrder" property', async () => {
const user = userEvent.setup();
render({
props: {
@@ -187,7 +187,7 @@ describe('FormComponentConfig', () => {
).toBeInTheDocument();
});
- it('should render CollapsiblePropertyEditor for the "showValidations" property and EditStringValue for other properties', () => {
+ it('should render property text for the "showValidations" property', () => {
render({
props: {
schema: {
@@ -217,7 +217,7 @@ describe('FormComponentConfig', () => {
).toBeInTheDocument();
});
- it('should render CollapsiblePropertyEditor for "preselectedOptionIndex" and EditNumberValue for other properties', () => {
+ it('should render property text for "preselectedOptionIndex" and EditNumberValue for other properties', () => {
render({
props: {
schema: {
@@ -281,15 +281,56 @@ describe('FormComponentConfig', () => {
).not.toBeInTheDocument();
});
- it('should show description text for objects if key is defined', () => {
- render({
- props: {
- schema: InputSchema,
- },
+ it('should call handleComponentUpdate and setSelectedValue when array property is updated', async () => {
+ const user = userEvent.setup();
+ const handleComponentUpdateMock = jest.fn();
+ const propertyKey = 'supportedArrayProperty';
+ renderWithProviders(
+ ,
+ );
+ const arrayPropertyButton = screen.getByRole('button', {
+ name: textMock(`ux_editor.component_properties.${propertyKey}`),
});
- expect(
- screen.getByText(textMock('ux_editor.component_properties_description.pageBreak')),
- ).toBeInTheDocument();
+ await user.click(arrayPropertyButton);
+
+ const combobox = screen.getByRole('combobox', {
+ name: textMock(`ux_editor.component_properties.${propertyKey}`),
+ });
+ await user.click(combobox);
+
+ const option1 = screen.getByRole('option', {
+ name: textMock('ux_editor.component_properties.enum_option1'),
+ });
+ await user.click(option1);
+
+ await waitFor(() => {
+ expect(handleComponentUpdateMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ [propertyKey]: ['option1'],
+ }),
+ );
+ });
+
+ const selectedValueDisplay = screen.getByRole('option', {
+ name: textMock('ux_editor.component_properties.enum_option1'),
+ });
+ expect(selectedValueDisplay).toBeInTheDocument();
});
it('should render default boolean values if defined', async () => {
@@ -442,6 +483,40 @@ describe('FormComponentConfig', () => {
);
});
+ it('should render array properties with enum values correctly', async () => {
+ const user = userEvent.setup();
+ const propertyKey = 'supportedArrayProperty';
+ const enumValues = ['option1', 'option2'];
+ render({
+ props: {
+ schema: {
+ properties: {
+ [propertyKey]: {
+ type: 'array',
+ items: {
+ type: 'string',
+ enum: enumValues,
+ },
+ },
+ },
+ },
+ component: {
+ ...componentMocks.Input,
+ [propertyKey]: enumValues,
+ },
+ },
+ });
+ const arrayPropertyButton = screen.getByRole('button', {
+ name: textMock(`ux_editor.component_properties.${propertyKey}`),
+ });
+ await user.click(arrayPropertyButton);
+ for (const dataType of enumValues) {
+ expect(
+ screen.getByText(textMock(`ux_editor.component_properties.enum_${dataType}`)),
+ ).toBeInTheDocument();
+ }
+ });
+
it('should call handleComponentUpdate with updated component when hasCustomFileEndings is true', async () => {
const handleComponentUpdateMock = jest.fn();
render({
diff --git a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx
index 872cb79ff9b..88cb3086137 100644
--- a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx
+++ b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useMemo, useState } from 'react';
import { Alert, Card, Heading, Paragraph } from '@digdir/designsystemet-react';
import type { FormComponent } from '../../types/FormComponent';
import { EditBooleanValue } from './editModal/EditBooleanValue';
@@ -18,7 +18,8 @@ import classes from './FormComponentConfig.module.css';
import { RedirectToLayoutSet } from './editModal/RedirectToLayoutSet';
import { ChevronDownIcon, ChevronUpIcon, PlusCircleIcon, XMarkIcon } from '@studio/icons';
import { StudioButton, StudioCard, StudioProperty } from '@studio/components';
-import { CollapsiblePropertyEditor } from './CollapsiblePropertyEditor';
+import { useComponentPropertyEnumValue } from '@altinn/ux-editor/hooks/useComponentPropertyEnumValue';
+import { SelectPropertyEditor } from './SelectPropertyEditor/SelectPropertyEditor';
export interface IEditFormComponentProps {
editFormId: string;
@@ -44,6 +45,27 @@ export const FormComponentConfig = ({
const [showOtherComponents, setShowOtherComponents] = useState(false);
const [showGrid, setShowGrid] = useState(false);
+ const selectedDataType = useComponentPropertyEnumValue();
+
+ const memoizedGetSelectedValuesDisplay = useMemo(
+ () => (propertyKey: string) => {
+ if (!component[propertyKey] || component[propertyKey].length === 0) return undefined;
+ return component[propertyKey].map((dataType: string) => (
+ {selectedDataType(dataType)}
+ ));
+ },
+ [component, selectedDataType],
+ );
+
+ const memoizedSelectedStringPropertiesDisplay = useMemo(
+ () => (propertyKey: string) => {
+ const value = component[propertyKey];
+ if (Array.isArray(value)) return value.map((dataType) => selectedDataType(dataType));
+ return value ? selectedDataType(value) : undefined;
+ },
+ [component, selectedDataType],
+ );
+
if (!schema?.properties) return null;
const { properties } = schema;
@@ -222,25 +244,33 @@ export const FormComponentConfig = ({
{/** String properties */}
{stringPropertyKeys.map((propertyKey) => {
return (
-
+
-
+
);
})}
{/** Number properties (number and integer types) */}
{numberPropertyKeys.map((propertyKey) => {
return (
-
-
+
);
})}
{/** Array properties with enum values) */}
{arrayPropertyKeys.map((propertyKey) => {
return (
-
+
{
+ handleComponentUpdate(updatedComponent);
+ }}
propertyKey={propertyKey}
key={propertyKey}
enumValues={properties[propertyKey]?.items?.enum}
multiple={true}
/>
-
+
);
})}
- {/** Object properties */}
+ {/** Object properties */}
{objectPropertyKeys.map((propertyKey) => {
return (
diff --git a/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.module.css b/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.module.css
new file mode 100644
index 00000000000..8998a8c7639
--- /dev/null
+++ b/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.module.css
@@ -0,0 +1,27 @@
+.container {
+ display: grid;
+ gap: var(--fds-spacing-2);
+ padding-bottom: var(--fds-spacing-1);
+ align-items: center;
+}
+
+.viewMode {
+ margin-top: var(--fds-spacing-1);
+ margin-bottom: var(--fds-spacing-1);
+}
+
+.editSelectProperty {
+ display: flex;
+ flex-direction: row;
+ align-items: end;
+ gap: var(--fds-spacing-4);
+ margin-bottom: var(--fds-spacing-3);
+}
+
+.selectProperty {
+ flex: 1;
+}
+
+.viewSelectProperty {
+ padding-left: 0;
+}
diff --git a/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.test.tsx b/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.test.tsx
new file mode 100644
index 00000000000..43890b3bba9
--- /dev/null
+++ b/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.test.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import userEvent from '@testing-library/user-event';
+import { screen } from '@testing-library/react';
+import { renderWithProviders } from 'app-development/test/mocks';
+import { textMock } from '@studio/testing/mocks/i18nMock';
+import { SelectPropertyEditor, type SelectPropertyEditorProps } from './SelectPropertyEditor';
+
+const children = Test children
;
+const value = Test value
;
+const property = 'Test property';
+
+describe('SelectPropertyEditor', () => {
+ it('should render the children when button is clicked', async () => {
+ const user = userEvent.setup();
+ renderSelectPropertyEditor();
+ await user.click(screen.getByText('Test property'));
+ expect(screen.getByText('Test children')).toBeInTheDocument();
+ });
+
+ it('should hide the children when the close button is clicked', async () => {
+ const user = userEvent.setup();
+ renderSelectPropertyEditor();
+ await user.click(screen.getByText('Test property'));
+ expect(screen.getByText('Test children')).toBeInTheDocument();
+ await user.click(screen.getByRole('button', { name: textMock('general.close') }));
+ expect(screen.queryByText('Test children')).not.toBeInTheDocument();
+ });
+
+ it('should render value', () => {
+ renderSelectPropertyEditor();
+ expect(screen.getByText('Test value')).toBeInTheDocument();
+ });
+
+ it('should render the property', () => {
+ renderSelectPropertyEditor({ property: 'Test property' });
+ expect(screen.getByText('Test property')).toBeInTheDocument();
+ });
+});
+
+const defaultProps: SelectPropertyEditorProps = {
+ children,
+ value,
+ property,
+};
+
+const renderSelectPropertyEditor = (props: Partial = {}) => {
+ renderWithProviders()();
+};
diff --git a/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.tsx b/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.tsx
new file mode 100644
index 00000000000..001ed0309e4
--- /dev/null
+++ b/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.tsx
@@ -0,0 +1,47 @@
+import React, { useState } from 'react';
+import { XMarkIcon } from '@studio/icons';
+import { StudioButton, StudioProperty } from '@studio/components';
+import classes from './SelectPropertyEditor.module.css';
+import { useTranslation } from 'react-i18next';
+import cn from 'classnames';
+
+export type SelectPropertyEditorProps = {
+ children?: React.ReactNode;
+ value?: string | React.ReactNode;
+ property?: string;
+ title?: string;
+};
+
+export const SelectPropertyEditor = ({
+ children,
+ value,
+ property,
+ title,
+}: SelectPropertyEditorProps) => {
+ const { t } = useTranslation();
+ const [dataTypeSelectVisible, setDataTypeSelectVisible] = useState(false);
+
+ return (
+
+ {dataTypeSelectVisible ? (
+
+
{children}
+
}
+ onClick={() => setDataTypeSelectVisible(false)}
+ title={t('general.close')}
+ variant='secondary'
+ />
+
+ ) : (
+
setDataTypeSelectVisible(true)}
+ property={property}
+ title={title}
+ value={value}
+ className={classes.viewSelectProperty}
+ />
+ )}
+
+ );
+};
diff --git a/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/index.ts b/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/index.ts
new file mode 100644
index 00000000000..400bc19d606
--- /dev/null
+++ b/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/index.ts
@@ -0,0 +1 @@
+export { SelectPropertyEditor } from './SelectPropertyEditor';