From 139ddbdd0ae138a2867b518eb021c36049474200 Mon Sep 17 00:00:00 2001 From: Erling Hauan <148075168+ErlingHauan@users.noreply.github.com> Date: Fri, 8 Nov 2024 13:56:08 +0100 Subject: [PATCH] feat: Implement StudioCodeListEditor in component edit view (#13922) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gørild Døhl --- .../components/RepoList/RepoList.tsx | 2 +- frontend/language/src/nb.json | 18 +- .../shared/src/utils/featureToggleUtils.ts | 3 +- .../Properties/Properties.module.css | 3 +- .../src/components/Properties/Text.tsx | 4 +- .../EditCodeList/EditCodeList.module.css | 9 - .../EditManualOptions/EditManualOptions.tsx | 88 --------- .../EditOptions/EditManualOptions/index.ts | 1 - .../EditOptions/EditOptions.module.css | 30 +-- .../EditOptions/EditOptions.test.tsx | 6 +- .../editModal/EditOptions/EditOptions.tsx | 84 ++------- .../EditCodeList/EditCodeList.module.css | 3 + .../EditCodeList/EditCodeList.test.tsx | 4 +- .../EditCodeList/EditCodeList.tsx | 9 +- .../EditCodeList/EditCodeListReference.tsx | 4 +- .../EditCodelistReference.test.tsx | 4 +- .../EditCodeList/findFileNameError.test.ts | 0 .../EditCodeList/findFileNameError.ts | 0 .../{ => OptionTabs}/EditCodeList/index.ts | 0 .../EditManualOptions.test.tsx | 6 +- .../EditManualOptions/EditManualOptions.tsx | 77 ++++++++ .../EditOption/EditOption.module.css | 0 .../EditOption/EditOption.test.tsx | 8 +- .../EditOption/EditOption.tsx | 8 +- .../OptionValue/OptionValue.module.css | 0 .../EditOption/OptionValue/OptionValue.tsx | 4 +- .../EditOption/OptionValue/index.ts | 0 .../EditManualOptions}/EditOption/index.ts | 0 .../EditOption/utils.test.ts | 0 .../EditManualOptions}/EditOption/utils.ts | 0 .../OptionTabs/EditManualOptions/index.ts | 2 + .../EditManualOptionsWithEditor.test.tsx | 176 ++++++++++++++++++ .../EditManualOptionsWithEditor.tsx | 50 +++++ .../EditManualOptionsWithEditor/index.ts | 1 + .../EditOptions/OptionTabs/OptionTabs.tsx | 127 +++++++++++++ .../EditOptions/OptionTabs/hooks/index.ts | 2 + .../hooks/useCodeListButtonValue.ts | 14 ++ .../hooks/useCodeListEditorTexts.ts | 29 +++ .../editModal/EditOptions/OptionTabs/index.ts | 1 + .../EditTextResourceBindings.module.css | 2 +- .../EditTextResourceBindings.tsx | 1 - 41 files changed, 559 insertions(+), 221 deletions(-) delete mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.module.css delete mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditManualOptions/EditManualOptions.tsx delete mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditManualOptions/index.ts create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/EditCodeList.module.css rename frontend/packages/ux-editor/src/components/config/editModal/EditOptions/{ => OptionTabs}/EditCodeList/EditCodeList.test.tsx (98%) rename frontend/packages/ux-editor/src/components/config/editModal/EditOptions/{ => OptionTabs}/EditCodeList/EditCodeList.tsx (94%) rename frontend/packages/ux-editor/src/components/config/editModal/EditOptions/{ => OptionTabs}/EditCodeList/EditCodeListReference.tsx (90%) rename frontend/packages/ux-editor/src/components/config/editModal/EditOptions/{ => OptionTabs}/EditCodeList/EditCodelistReference.test.tsx (94%) rename frontend/packages/ux-editor/src/components/config/editModal/EditOptions/{ => OptionTabs}/EditCodeList/findFileNameError.test.ts (100%) rename frontend/packages/ux-editor/src/components/config/editModal/EditOptions/{ => OptionTabs}/EditCodeList/findFileNameError.ts (100%) rename frontend/packages/ux-editor/src/components/config/editModal/EditOptions/{ => OptionTabs}/EditCodeList/index.ts (100%) rename frontend/packages/ux-editor/src/components/config/editModal/EditOptions/{ => OptionTabs}/EditManualOptions/EditManualOptions.test.tsx (96%) create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditManualOptions.tsx rename frontend/packages/ux-editor/src/components/config/editModal/{ => EditOptions/OptionTabs/EditManualOptions}/EditOption/EditOption.module.css (100%) rename frontend/packages/ux-editor/src/components/config/editModal/{ => EditOptions/OptionTabs/EditManualOptions}/EditOption/EditOption.test.tsx (96%) rename frontend/packages/ux-editor/src/components/config/editModal/{ => EditOptions/OptionTabs/EditManualOptions}/EditOption/EditOption.tsx (92%) rename frontend/packages/ux-editor/src/components/config/editModal/{ => EditOptions/OptionTabs/EditManualOptions}/EditOption/OptionValue/OptionValue.module.css (100%) rename frontend/packages/ux-editor/src/components/config/editModal/{ => EditOptions/OptionTabs/EditManualOptions}/EditOption/OptionValue/OptionValue.tsx (88%) rename frontend/packages/ux-editor/src/components/config/editModal/{ => EditOptions/OptionTabs/EditManualOptions}/EditOption/OptionValue/index.ts (100%) rename frontend/packages/ux-editor/src/components/config/editModal/{ => EditOptions/OptionTabs/EditManualOptions}/EditOption/index.ts (100%) rename frontend/packages/ux-editor/src/components/config/editModal/{ => EditOptions/OptionTabs/EditManualOptions}/EditOption/utils.test.ts (100%) rename frontend/packages/ux-editor/src/components/config/editModal/{ => EditOptions/OptionTabs/EditManualOptions}/EditOption/utils.ts (100%) create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/index.ts create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptionsWithEditor/EditManualOptionsWithEditor.test.tsx create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptionsWithEditor/EditManualOptionsWithEditor.tsx create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptionsWithEditor/index.ts create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/OptionTabs.tsx create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/hooks/index.ts create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/hooks/useCodeListButtonValue.ts create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/hooks/useCodeListEditorTexts.ts create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/index.ts diff --git a/frontend/dashboard/components/RepoList/RepoList.tsx b/frontend/dashboard/components/RepoList/RepoList.tsx index e083a1e5f94..a1ef29772b7 100644 --- a/frontend/dashboard/components/RepoList/RepoList.tsx +++ b/frontend/dashboard/components/RepoList/RepoList.tsx @@ -69,7 +69,7 @@ export const RepoList = ({ }, { accessor: 'description', - heading: t('dashboard.description'), + heading: t('general.description'), sortable: true, }, { diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 3d3da74bec8..3f13fd20ca5 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -121,7 +121,6 @@ "dashboard.created_by": "Opprettet av", "dashboard.creating_your_service": "Oppretter appen din", "dashboard.data_models": "Datamodeller", - "dashboard.description": "Beskrivelse", "dashboard.edit_app": "Endre {{appName}} i Studio", "dashboard.error_getting_organization_data.message": "Det oppsto en feil da vi skulle hente de organisasjonene som trengs for å kjøre appen.", "dashboard.error_getting_organization_data.title": "Kunne ikke laste inn organisasjoner", @@ -273,6 +272,7 @@ "general.date_time_format": "{{date}} kl. {{time}}", "general.delete": "Slett", "general.delete_item": "Slett {{item}}", + "general.description": "Beskrivelse", "general.edit": "Endre", "general.empty_string": "Tom tekst", "general.error_message": "Det har oppstått en feil. Hvis problemet fortsetter, ta kontakt med oss.", @@ -288,6 +288,7 @@ "general.loading": "Laster...", "general.next": "Neste", "general.no_options": "Ingen alternativer tilgjengelige", + "general.option": "Alternativ", "general.options": "Alternativer", "general.page": "Side", "general.page_error_message": "Vi vet ikke helt hva, men ta kontakt med oss, så graver vi i det sammen.", @@ -1492,9 +1493,17 @@ "ux_editor.modal_header_type_helper": "Velg titteltype", "ux_editor.modal_new_option": "Legg til alternativ", "ux_editor.modal_properties_add_radio_button_options": "Hvordan vil du legge til radioknapper?", + "ux_editor.modal_properties_code_list_custom_list": "Egendefinert kodeliste", + "ux_editor.modal_properties_code_list_delete_item": "Slett alternativ {{number}}", + "ux_editor.modal_properties_code_list_empty": "Kodelisten er tom.", "ux_editor.modal_properties_code_list_filename_error": "Filnavnet er ugyldig. Du kan bruke tall, understrek, punktum, bindestrek, og store/små bokstaver fra det norske alfabetet. Filnavnet må starte med en engelsk bokstav.", + "ux_editor.modal_properties_code_list_general_error": "Kan ikke lagre kodelisten, den inneholder feil.", "ux_editor.modal_properties_code_list_helper": "Velg kodeliste", "ux_editor.modal_properties_code_list_id": "Kodeliste-ID", + "ux_editor.modal_properties_code_list_item_description": "Beskrivelse for alternativ {{number}}", + "ux_editor.modal_properties_code_list_item_helpText": "Hjelpetekst for alternativ {{number}}", + "ux_editor.modal_properties_code_list_item_label": "Ledetekst for alternativ {{number}}", + "ux_editor.modal_properties_code_list_item_value": "Verdi for alternativ {{number}}", "ux_editor.modal_properties_code_list_read_more": "<0 href=\"{{optionsDocs}}\" >Les mer om kodelister", "ux_editor.modal_properties_code_list_read_more_dynamic": "<0 href=\"{{optionsDocs}}\" >Les mer om dynamiske kodelister", "ux_editor.modal_properties_code_list_read_more_static": "<0 href=\"{{optionsDocs}}\" >Les mer om statiske kodelister", @@ -1648,7 +1657,7 @@ "ux_editor.options.codelist_create_info.step4": "Skriv inn kodelisten i tekstfeltet midt på siden. Kodelisten må være i JSON-format.", "ux_editor.options.codelist_create_info.step5": "Velg \"Commit endringer\".", "ux_editor.options.codelist_create_info.step6": "Du er nå ferdig i Gitea for denne gang. Gå tilbake til Altinn Studio-fanen, eller klikk på Altinn-logoen øverst til venstre i Gitea for å komme tilbake til Altinn Studio.", - "ux_editor.options.codelist_only": "Denne komponenten støtter kun oppsett med kodelister.", + "ux_editor.options.codelist_only": "Denne komponenten støtter bare oppsett med forhåndsdefinerte kodelister.", "ux_editor.options.codelist_referenceId.description": "Her kan du legge til en referanse-ID til en dynamisk kodeliste som er satt opp i koden.", "ux_editor.options.codelist_referenceId.description_details": "Du bruker dynamiske kodelister for å tilpasse alternativer for brukerne. Det kan for eksempel være tilpasninger ut fra geografisk plassering, eller valg brukeren gjør tidligere i skjemaet.", "ux_editor.options.codelist_upload_info.heading": "Steg for å laste opp kodelister manuelt", @@ -1657,13 +1666,12 @@ "ux_editor.options.codelist_upload_info.step3": "Filen må ligge i mappen \"App/options\". Sørg for at den blir plassert der ved å oppgi denne stien i opplastingsfeltet. Når du skriver \"App/options/\", blir feltet automatisk oppdatert med mappesti.", "ux_editor.options.codelist_upload_info.step4": "Velg \"Commit endringer\".", "ux_editor.options.codelist_upload_info.step5": "Du er nå ferdig i Gitea for denne gang. Gå tilbake til Altinn Studio-fanen, eller klikk på Altinn-logoen øverst til venstre i Gitea for å komme tilbake til Altinn Studio.", + "ux_editor.options.multiple": "{{value}} alternativer", "ux_editor.options.section_heading": "Valg for kodelister", + "ux_editor.options.single": "{{value}} alternativ", "ux_editor.options.tab_codelist": "Velg kodeliste", "ux_editor.options.tab_manual": "Sett opp egne alternativer", "ux_editor.options.tab_referenceId": "Angi referanse-ID", - "ux_editor.options_text_description": "Beskrivelse", - "ux_editor.options_text_help_text": "Hjelpetekst", - "ux_editor.options_text_label": "Ledetekst", "ux_editor.page": "Side", "ux_editor.page_config_pdf_abort_converting_page_to_pdf": "Avbryt å gjøre om siden til PDF", "ux_editor.page_config_pdf_card_heading": "Siden skal være en PDF", diff --git a/frontend/packages/shared/src/utils/featureToggleUtils.ts b/frontend/packages/shared/src/utils/featureToggleUtils.ts index 476f2f64936..2410fb688db 100644 --- a/frontend/packages/shared/src/utils/featureToggleUtils.ts +++ b/frontend/packages/shared/src/utils/featureToggleUtils.ts @@ -12,7 +12,8 @@ export type SupportedFeatureFlags = | 'exportForm' | 'addComponentModal' | 'subform' - | 'summary2'; + | 'summary2' + | 'codeListEditor'; /* * Please add all the features that you want to be toggle on by default here. diff --git a/frontend/packages/ux-editor/src/components/Properties/Properties.module.css b/frontend/packages/ux-editor/src/components/Properties/Properties.module.css index dfcca284b18..da219e9fdd2 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Properties.module.css +++ b/frontend/packages/ux-editor/src/components/Properties/Properties.module.css @@ -10,5 +10,6 @@ .texts { background-color: var(--fds-semantic-surface-neutral-default); - padding: var(--fds-spacing-5) 0; + padding-block: var(--fds-spacing-5) 0; + padding-inline: 0; } diff --git a/frontend/packages/ux-editor/src/components/Properties/Text.tsx b/frontend/packages/ux-editor/src/components/Properties/Text.tsx index 3b3d9a87368..7900772a841 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Text.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Text.tsx @@ -50,7 +50,6 @@ export const Text = () => { component={form} handleComponentChange={handleComponentChange} textResourceBindingKeys={Object.keys(schema.properties.textResourceBindings.properties)} - editFormId={formId} layoutName={selectedFormLayoutName} /> )} @@ -64,10 +63,9 @@ export const Text = () => { ComponentSpecificConfig) } handleComponentChange={handleComponentChange} - editFormId={formId} layoutName={selectedFormLayoutName} renderOptions={{ - onlyCodeListOptions: schema.properties.optionsId && !schema.properties.options, + areLayoutOptionsSupported: schema.properties.optionsId! && schema.properties.options, }} /> )} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.module.css b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.module.css deleted file mode 100644 index 309c7dd3efd..00000000000 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.module.css +++ /dev/null @@ -1,9 +0,0 @@ -.studioFileUploader { - padding-top: var(--fds-spacing-2); - padding-bottom: var(--fds-spacing-1); -} - -.linkStaticCodeLists { - margin-bottom: 0; - padding-top: var(--fds-spacing-2); -} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditManualOptions/EditManualOptions.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditManualOptions/EditManualOptions.tsx deleted file mode 100644 index 9bd3ce9b2fa..00000000000 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditManualOptions/EditManualOptions.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, { useMemo } from 'react'; -import { ErrorMessage } from '@digdir/designsystemet-react'; -import classes from '../EditOptions.module.css'; -import type { IGenericEditComponent } from '../../../componentConfig'; -import { useComponentErrorMessage } from '../../../../../hooks'; -import { addOptionToComponent, generateRandomOption } from '../../../../../utils/component'; -import { StudioProperty } from '@studio/components'; -import type { SelectionComponentType } from '../../../../../types/FormComponent'; -import { EditOption } from '../../EditOption'; -import { ArrayUtils } from '@studio/pure-functions'; -import type { Option } from 'app-shared/types/Option'; -import { useTranslation } from 'react-i18next'; - -export function EditManualOptions({ - component, - handleComponentChange, -}: IGenericEditComponent) { - const { t } = useTranslation(); - - const mappedOptionIds = useMemo( - () => component.options?.map((_, index) => `option_${index}`), - [component.options], - ); - - const errorMessage = useComponentErrorMessage(component); - - const handleOptionsChange = (options: Option[]) => { - handleComponentChange({ - ...component, - options, - }); - }; - - const handleOptionChange = (index: number) => (newOption: Option) => { - const newOptions = ArrayUtils.replaceByIndex(component.options || [], index, newOption); - return handleOptionsChange(newOptions); - }; - - const handleRemoveOption = (index: number) => { - const options = [...(component.options || [])]; - options.splice(index, 1); - handleOptionsChange(options); - }; - - const handleAddOption = () => { - if (component.optionsId) { - delete component.optionsId; - } - - handleComponentChange(addOptionToComponent(component, generateRandomOption())); - }; - - return ( - <> - - {component.options?.map((option, index) => { - const removeItem = () => handleRemoveOption(index); - const key = mappedOptionIds[index]; - const optionNumber = index + 1; - const legend = - component.type === 'RadioButtons' - ? t('ux_editor.radios_option', { optionNumber }) - : t('ux_editor.checkboxes_option', { optionNumber }); - return ( - - ); - })} - !label)} - onClick={handleAddOption} - property={t('ux_editor.modal_new_option')} - /> - - - {errorMessage && ( - - {errorMessage} - - )} - - ); -} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditManualOptions/index.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditManualOptions/index.ts deleted file mode 100644 index 1d7722683ca..00000000000 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditManualOptions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { EditManualOptions } from './EditManualOptions'; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.module.css b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.module.css index 4cedbe1a3d0..a719b57e128 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.module.css +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.module.css @@ -4,26 +4,34 @@ padding: var(--fds-spacing-5) 0 0; } -.codeListSwitchWrapper { - display: flex; - flex-direction: row; - align-items: center; - gap: 8px; - margin: var(--fds-spacing-5); +.optionsHeading { + padding-left: var(--fds-spacing-5); } .errorMessage { margin: var(--fds-spacing-5) var(--fds-spacing-5) 0; } +.codelistTabContent { + padding: var(--fds-spacing-5); + display: flex; + flex-direction: column; + gap: var(--fds-spacing-2); +} + .manualTabContent { - padding: var(--fds-spacing-5) 0; + padding-block: var(--fds-spacing-5); + padding-inline: 0; } -.codelistTabContent { - padding: var(--fds-spacing-4); +.manualTabAlert { + margin-inline: var(--fds-spacing-5); } -.optionsHeading { - padding-left: var(--fds-spacing-5); +.manualTabDialog[open] { + --code-list-modal-min-width: min(80rem, 100%); + --code-list-modal-height: min(40rem, 100%); + + min-width: var(--code-list-modal-min-width); + height: var(--code-list-modal-height); } diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.test.tsx index d4f4f5d6860..b33cea061df 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.test.tsx @@ -34,7 +34,7 @@ const renderEditOptions = async void; queries?: Partial; renderOptions?: { - onlyCodeListOptions?: boolean; + areLayoutOptionsSupported?: boolean; }; } = {}) => { const component = { @@ -204,11 +204,11 @@ describe('EditOptions', () => { ).toBeInTheDocument(); }); - it('should show alert message in Manual tab when prop onlyCodeListOptions is true', async () => { + it('should show alert message in Manual tab when prop areLayoutOptionsSupported is false', async () => { const user = userEvent.setup(); await renderEditOptions({ componentProps: { optionsId: '' }, - renderOptions: { onlyCodeListOptions: true }, + renderOptions: { areLayoutOptionsSupported: false }, queries: { getOptionListIds: jest.fn().mockImplementation(() => Promise.resolve([])), }, diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.tsx index 1481f9de604..419c19faa77 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.tsx @@ -1,21 +1,18 @@ -import React, { useEffect, useRef } from 'react'; -import { ErrorMessage, Heading, Alert } from '@digdir/designsystemet-react'; +import React from 'react'; +import { ErrorMessage, Heading } from '@digdir/designsystemet-react'; import classes from './EditOptions.module.css'; import type { IGenericEditComponent } from '../../componentConfig'; -import { EditCodeList, EditCodeListReference } from './EditCodeList'; -import { getSelectedOptionsType } from '../../../../utils/optionsUtils'; import { useOptionListIdsQuery } from '../../../../hooks/queries/useOptionListIdsQuery'; - -import { StudioSpinner, StudioTabs } from '@studio/components'; +import { StudioSpinner } from '@studio/components'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; import { useTranslation } from 'react-i18next'; -import { EditManualOptions } from './EditManualOptions/EditManualOptions'; import type { SelectionComponentType } from '../../../../types/FormComponent'; +import { OptionTabs } from '@altinn/ux-editor/components/config/editModal/EditOptions/OptionTabs/OptionTabs'; export interface ISelectionEditComponentProvidedProps extends IGenericEditComponent { renderOptions?: { - onlyCodeListOptions?: boolean; + areLayoutOptionsSupported?: boolean; }; } @@ -27,33 +24,14 @@ export enum SelectedOptionsType { } export function EditOptions({ - editFormId, component, handleComponentChange, renderOptions, }: ISelectionEditComponentProvidedProps) { - const previousEditFormId = useRef(editFormId); const { org, app } = useStudioEnvironmentParams(); const { data: optionListIds, isPending, isError, error } = useOptionListIdsQuery(org, app); - const [initialSelectedOptionType, setInitialSelectedOptionType] = - React.useState( - getSelectedOptionsType(component.optionsId, component.options, optionListIds || []), - ); const { t } = useTranslation(); - useEffect(() => { - if (editFormId !== previousEditFormId.current) { - previousEditFormId.current = editFormId; - } - }, [editFormId]); - - useEffect(() => { - if (!optionListIds) return; - setInitialSelectedOptionType( - getSelectedOptionsType(component.optionsId, component.options, optionListIds), - ); - }, [optionListIds, component.optionsId, component.options, setInitialSelectedOptionType]); - return (
@@ -65,54 +43,16 @@ export function EditOptions({ spinnerTitle={t('ux_editor.modal_properties_loading')} /> ) : isError ? ( - + {error instanceof Error ? error.message : t('ux_editor.modal_properties_error_message')} ) : ( - { - setInitialSelectedOptionType(value as SelectedOptionsType); - }} - > - - - {t('ux_editor.options.tab_codelist')} - - - {t('ux_editor.options.tab_manual')} - - - {t('ux_editor.options.tab_referenceId')} - - - - - - - {renderOptions.onlyCodeListOptions ? ( - {t('ux_editor.options.codelist_only')} - ) : ( - - )} - - - - - + )}
); diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/EditCodeList.module.css b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/EditCodeList.module.css new file mode 100644 index 00000000000..1090d83be58 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/EditCodeList.module.css @@ -0,0 +1,3 @@ +.linkStaticCodeLists { + padding-top: var(--fds-spacing-2); +} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/EditCodeList.test.tsx similarity index 98% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.test.tsx rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/EditCodeList.test.tsx index ddf8d29494e..8b619303179 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/EditCodeList.test.tsx @@ -2,11 +2,11 @@ import React from 'react'; import { EditCodeList } from './EditCodeList'; import { screen, waitFor } from '@testing-library/react'; import { ComponentType } from 'app-shared/types/ComponentType'; -import { renderWithProviders, optionListIdsMock } from '../../../../../testing/mocks'; +import { renderWithProviders, optionListIdsMock } from '../../../../../../testing/mocks'; import userEvent from '@testing-library/user-event'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; -import type { FormComponent } from '../../../../../types/FormComponent'; +import type { FormComponent } from '../../../../../../types/FormComponent'; const mockComponent: FormComponent = { id: 'c24d0812-0c34-4582-8f31-ff4ce9795e96', diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/EditCodeList.tsx similarity index 94% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.tsx rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/EditCodeList.tsx index 59a7fc8bcc6..3b9d925f05f 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/EditCodeList.tsx @@ -1,14 +1,14 @@ import React from 'react'; import { ErrorMessage } from '@digdir/designsystemet-react'; -import type { IGenericEditComponent } from '../../../componentConfig'; -import { useOptionListIdsQuery } from '../../../../../hooks/queries/useOptionListIdsQuery'; +import type { IGenericEditComponent } from '../../../../componentConfig'; +import { useOptionListIdsQuery } from '../../../../../../hooks/queries/useOptionListIdsQuery'; import { useAddOptionListMutation } from 'app-shared/hooks/mutations'; import { useTranslation, Trans } from 'react-i18next'; import { StudioFileUploader, StudioNativeSelect, StudioSpinner } from '@studio/components'; import { altinnDocsUrl } from 'app-shared/ext-urls'; -import { FormField } from '../../../../FormField'; +import { FormField } from '../../../../../FormField'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; -import type { SelectionComponentType } from '../../../../../types/FormComponent'; +import type { SelectionComponentType } from '../../../../../../types/FormComponent'; import { removeExtension } from 'app-shared/utils/filenameUtils'; import { findFileNameError } from './findFileNameError'; import type { FileNameError } from './findFileNameError'; @@ -75,7 +75,6 @@ export function EditCodeList({ <> ({ component, diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodelistReference.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/EditCodelistReference.test.tsx similarity index 94% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodelistReference.test.tsx rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/EditCodelistReference.test.tsx index af5d8ba32ff..80f047e5542 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodelistReference.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/EditCodelistReference.test.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { screen } from '@testing-library/react'; import { EditCodeListReference } from './EditCodeListReference'; -import { renderWithProviders } from '../../../../../testing/mocks'; +import { renderWithProviders } from '../../../../../../testing/mocks'; import { ComponentType } from 'app-shared/types/ComponentType'; -import type { FormComponent } from '../../../../../types/FormComponent'; +import type { FormComponent } from '../../../../../../types/FormComponent'; import { textMock } from '@studio/testing/mocks/i18nMock'; import userEvent from '@testing-library/user-event'; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/findFileNameError.test.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/findFileNameError.test.ts similarity index 100% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/findFileNameError.test.ts rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/findFileNameError.test.ts diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/findFileNameError.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/findFileNameError.ts similarity index 100% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/findFileNameError.ts rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/findFileNameError.ts diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/index.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/index.ts similarity index 100% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/index.ts rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditCodeList/index.ts diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditManualOptions/EditManualOptions.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditManualOptions.test.tsx similarity index 96% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditManualOptions/EditManualOptions.test.tsx rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditManualOptions.test.tsx index 5217e274d36..4e84a057ade 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditManualOptions/EditManualOptions.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditManualOptions.test.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { screen } from '@testing-library/react'; import { EditManualOptions } from './EditManualOptions'; -import { renderWithProviders } from '../../../../../testing/mocks'; +import { renderWithProviders } from '../../../../../../testing/mocks'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { ComponentType } from 'app-shared/types/ComponentType'; -import type { FormItem } from '../../../../../types/FormItem'; +import type { FormItem } from '../../../../../../types/FormItem'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; -import type { FormComponent } from '../../../../../types/FormComponent'; +import type { FormComponent } from '../../../../../../types/FormComponent'; import userEvent from '@testing-library/user-event'; const mockComponent: FormComponent = { diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditManualOptions.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditManualOptions.tsx new file mode 100644 index 00000000000..cc9d1222172 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditManualOptions.tsx @@ -0,0 +1,77 @@ +import React, { useMemo } from 'react'; +import type { IGenericEditComponent } from '../../../../componentConfig'; +import { addOptionToComponent, generateRandomOption } from '../../../../../../utils/component'; +import { StudioProperty } from '@studio/components'; +import type { SelectionComponentType } from '../../../../../../types/FormComponent'; +import { EditOption } from './EditOption'; +import { ArrayUtils } from '@studio/pure-functions'; +import type { Option } from 'app-shared/types/Option'; +import { useTranslation } from 'react-i18next'; + +export type EditManualOptionsProps = Pick< + IGenericEditComponent, + 'component' | 'handleComponentChange' +>; + +export function EditManualOptions({ component, handleComponentChange }: EditManualOptionsProps) { + const { t } = useTranslation(); + + const mappedOptionIds = useMemo( + () => component.options?.map((_, index) => `option_${index}`), + [component.options], + ); + + const handleOptionsChange = (options: Option[]) => { + handleComponentChange({ + ...component, + options, + }); + }; + + const handleOptionChange = (index: number) => (newOption: Option) => { + const newOptions = ArrayUtils.replaceByIndex(component.options || [], index, newOption); + return handleOptionsChange(newOptions); + }; + + const handleRemoveOption = (index: number) => { + const options = [...(component.options || [])]; + options.splice(index, 1); + handleOptionsChange(options); + }; + + const handleAddOption = () => { + if (component.optionsId) { + delete component.optionsId; + } + + handleComponentChange(addOptionToComponent(component, generateRandomOption())); + }; + + return ( + + {component.options?.map((option, index) => { + const removeItem = () => handleRemoveOption(index); + const key = mappedOptionIds[index]; + const optionNumber = index + 1; + const legend = + component.type === 'RadioButtons' + ? t('ux_editor.radios_option', { optionNumber }) + : t('ux_editor.checkboxes_option', { optionNumber }); + return ( + + ); + })} + !label)} + onClick={handleAddOption} + property={t('ux_editor.modal_new_option')} + /> + + ); +} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOption/EditOption.module.css b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/EditOption.module.css similarity index 100% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOption/EditOption.module.css rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/EditOption.module.css diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOption/EditOption.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/EditOption.test.tsx similarity index 96% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOption/EditOption.test.tsx rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/EditOption.test.tsx index fb5f00154ac..f744113cd75 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOption/EditOption.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/EditOption.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { renderWithProviders } from '../../../../testing/mocks'; +import { renderWithProviders } from '../../../../../../../testing/mocks'; import type { EditOptionProps } from './EditOption'; import { EditOption } from './EditOption'; import { screen, within } from '@testing-library/react'; @@ -91,9 +91,9 @@ describe('EditOption', () => { }); const textResourceLabels: KeyValuePairs = { - label: textMock('ux_editor.options_text_label'), - description: textMock('ux_editor.options_text_description'), - helpText: textMock('ux_editor.options_text_help_text'), + label: textMock('ux_editor.modal_properties_textResourceBindings_title'), + description: textMock('general.description'), + helpText: textMock('ux_editor.modal_properties_textResourceBindings_help'), }; it.each(Object.keys(textResourceLabels))( diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOption/EditOption.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/EditOption.tsx similarity index 92% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOption/EditOption.tsx rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/EditOption.tsx index c4f26e0c7ed..3cd712933c1 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOption/EditOption.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/EditOption.tsx @@ -8,7 +8,7 @@ import { import { useTranslation } from 'react-i18next'; import type { Option } from 'app-shared/types/Option'; import { XMarkIcon } from '@studio/icons'; -import { TextResource } from '../../../TextResource/TextResource'; +import { TextResource } from '../../../../../../TextResource/TextResource'; import { deleteDescription, deleteHelpText, @@ -83,7 +83,7 @@ const OpenOption = ({ legend, onChange, option, onDelete, onClose }: OpenOptionP @@ -91,14 +91,14 @@ const OpenOption = ({ legend, onChange, option, onDelete, onClose }: OpenOptionP compact handleIdChange={handleDescriptionChange} handleRemoveTextResource={handleDeleteDescription} - label={t('ux_editor.options_text_description')} + label={t('general.description')} textResourceId={option.description} /> diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOption/OptionValue/OptionValue.module.css b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/OptionValue/OptionValue.module.css similarity index 100% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOption/OptionValue/OptionValue.module.css rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/OptionValue/OptionValue.module.css diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOption/OptionValue/OptionValue.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/OptionValue/OptionValue.tsx similarity index 88% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOption/OptionValue/OptionValue.tsx rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/OptionValue/OptionValue.tsx index fbe8bc6b403..afc10cf3feb 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOption/OptionValue/OptionValue.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/OptionValue/OptionValue.tsx @@ -1,7 +1,7 @@ import React from 'react'; import type { Option } from 'app-shared/types/Option'; -import { useTextResourcesSelector } from '../../../../../hooks'; -import { textResourceByLanguageAndIdSelector } from '../../../../../selectors/textResourceSelectors'; +import { useTextResourcesSelector } from '../../../../../../../../hooks'; +import { textResourceByLanguageAndIdSelector } from '../../../../../../../../selectors/textResourceSelectors'; import { DEFAULT_LANGUAGE } from 'app-shared/constants'; import { StudioCodeFragment } from '@studio/components'; import type { ITextResource } from 'app-shared/types/global'; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOption/OptionValue/index.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/OptionValue/index.ts similarity index 100% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOption/OptionValue/index.ts rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/OptionValue/index.ts diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOption/index.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/index.ts similarity index 100% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOption/index.ts rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/index.ts diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOption/utils.test.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/utils.test.ts similarity index 100% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOption/utils.test.ts rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/utils.test.ts diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOption/utils.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/utils.ts similarity index 100% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOption/utils.ts rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/EditOption/utils.ts diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/index.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/index.ts new file mode 100644 index 00000000000..5c20c260147 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptions/index.ts @@ -0,0 +1,2 @@ +export { EditManualOptions } from './EditManualOptions'; +export type { EditManualOptionsProps } from './EditManualOptions'; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptionsWithEditor/EditManualOptionsWithEditor.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptionsWithEditor/EditManualOptionsWithEditor.test.tsx new file mode 100644 index 00000000000..6ff9f1af430 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptionsWithEditor/EditManualOptionsWithEditor.test.tsx @@ -0,0 +1,176 @@ +import React from 'react'; +import { screen } from '@testing-library/react'; +import { EditManualOptionsWithEditor } from './EditManualOptionsWithEditor'; +import { renderWithProviders } from '../../../../../../testing/mocks'; +import { textMock } from '@studio/testing/mocks/i18nMock'; +import { ComponentType } from 'app-shared/types/ComponentType'; +import type { FormItem } from '../../../../../../types/FormItem'; +import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; +import type { FormComponent } from '../../../../../../types/FormComponent'; +import userEvent from '@testing-library/user-event'; + +const mockComponent: FormComponent = { + id: 'c24d0812-0c34-4582-8f31-ff4ce9795e96', + type: ComponentType.RadioButtons, + textResourceBindings: { + title: 'ServiceName', + }, + maxLength: 10, + itemType: 'COMPONENT', + dataModelBindings: { simpleBinding: '' }, +}; + +const renderEditManualOptionsWithEditor = < + T extends ComponentType.Checkboxes | ComponentType.RadioButtons, +>({ + componentProps, + handleComponentChange = jest.fn(), +}: { + componentProps?: Partial>; + handleComponentChange?: () => void; + queries?: Partial; +} = {}) => { + const component = { + ...mockComponent, + ...componentProps, + }; + renderWithProviders( + , + ); +}; + +describe('EditManualOptionsWithEditor', () => { + it('should display a button when no code list is defined in the layout', () => { + renderEditManualOptionsWithEditor(); + + const modalButton = screen.getByRole('button', { + name: textMock('ux_editor.modal_properties_code_list_custom_list'), + }); + + expect(modalButton).toBeInTheDocument(); + }); + + it('should display a button when a code list is defined in the layout', () => { + renderEditManualOptionsWithEditor({ + componentProps: { + options: [{ label: 'option1', value: 'option1' }], + }, + }); + + const modalButton = screen.getByRole('button', { + name: textMock('ux_editor.modal_properties_code_list_custom_list'), + }); + + expect(modalButton).toBeInTheDocument(); + }); + + it('should not display how many options have been defined, when no options are defined', () => { + renderEditManualOptionsWithEditor(); + + const optionText = screen.queryByText(textMock('ux_editor.options.single', { value: 1 })); + const optionsText = screen.queryByText(textMock('ux_editor.options.multiple', { value: 2 })); + + expect(optionText).not.toBeInTheDocument(); + expect(optionsText).not.toBeInTheDocument(); + }); + + it('should display how many options have been defined, when a single option is defined', () => { + renderEditManualOptionsWithEditor({ + componentProps: { + options: [{ label: 'option1', value: 'option1' }], + }, + }); + + const optionText = screen.getByText(textMock('ux_editor.options.single', { value: 1 })); + const optionsText = screen.queryByText(textMock('ux_editor.options.multiple', { value: 2 })); + + expect(optionText).toBeInTheDocument(); + expect(optionsText).not.toBeInTheDocument(); + }); + + it('should display how many options have been defined, when multiple options are defined', () => { + renderEditManualOptionsWithEditor({ + componentProps: { + options: [ + { label: 'option1', value: 'option1' }, + { label: 'option2', value: 'option2' }, + ], + }, + }); + + const optionText = screen.queryByText(textMock('ux_editor.options.single', { value: 1 })); + const optionsText = screen.getByText(textMock('ux_editor.options.multiple', { value: 2 })); + + expect(optionText).not.toBeInTheDocument(); + expect(optionsText).toBeInTheDocument(); + }); + + it('should open a modal when the trigger button is clicked', async () => { + const user = userEvent.setup(); + renderEditManualOptionsWithEditor(); + + const modalButton = screen.getByRole('button', { + name: textMock('ux_editor.modal_properties_code_list_custom_list'), + }); + + await user.click(modalButton); + + const modalDialog = screen.getByRole('dialog'); + + expect(modalDialog).toBeInTheDocument(); + }); + + it('should call handleComponentChange when there has been a change in the editor', async () => { + const mockHandleComponentChange = jest.fn(); + const user = userEvent.setup(); + renderEditManualOptionsWithEditor({ handleComponentChange: mockHandleComponentChange }); + + const modalButton = screen.getByRole('button', { + name: textMock('ux_editor.modal_properties_code_list_custom_list'), + }); + + await user.click(modalButton); + + const addNewButton = screen.getByRole('button', { + name: textMock('ux_editor.modal_new_option'), + }); + + await user.click(addNewButton); + + expect(mockHandleComponentChange).toHaveBeenCalledWith({ + ...mockComponent, + options: [{ label: '', value: '' }], + }); + }); + + it('should delete optionsId from the layout when using the manual editor', async () => { + const user = userEvent.setup(); + const mockHandleComponentChange = jest.fn(); + renderEditManualOptionsWithEditor({ + componentProps: { + optionsId: 'somePredefinedOptionsList', + }, + handleComponentChange: mockHandleComponentChange, + }); + + const modalButton = screen.getByRole('button', { + name: textMock('ux_editor.modal_properties_code_list_custom_list'), + }); + + await user.click(modalButton); + + const addNewButton = screen.getByRole('button', { + name: textMock('ux_editor.modal_new_option'), + }); + + await user.click(addNewButton); + + expect(mockHandleComponentChange).toHaveBeenCalledWith({ + ...mockComponent, // does not contain optionsId + options: [{ label: '', value: '' }], + }); + }); +}); diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptionsWithEditor/EditManualOptionsWithEditor.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptionsWithEditor/EditManualOptionsWithEditor.tsx new file mode 100644 index 00000000000..376385d2760 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptionsWithEditor/EditManualOptionsWithEditor.tsx @@ -0,0 +1,50 @@ +import React, { useRef } from 'react'; +import classes from '../../EditOptions.module.css'; +import { StudioCodeListEditor, StudioModal, StudioProperty } from '@studio/components'; +import type { Option } from 'app-shared/types/Option'; +import { useTranslation } from 'react-i18next'; +import { useCodeListButtonValue, useCodeListEditorTexts } from '../hooks'; +import type { EditManualOptionsProps } from '../EditManualOptions'; + +export function EditManualOptionsWithEditor({ + component, + handleComponentChange, +}: EditManualOptionsProps) { + const { t } = useTranslation(); + const manualOptionsModalRef = useRef(null); + const buttonValue = useCodeListButtonValue(component.options); + const editorTexts = useCodeListEditorTexts(); + + const handleOptionsChange = (options: Option[]) => { + if (component.optionsId) { + delete component.optionsId; + } + + handleComponentChange({ + ...component, + options, + }); + }; + + return ( + <> + manualOptionsModalRef.current.showModal()} + property={t('ux_editor.modal_properties_code_list_custom_list')} + value={buttonValue} + /> + + handleOptionsChange(codeList)} + texts={editorTexts} + /> + + + ); +} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptionsWithEditor/index.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptionsWithEditor/index.ts new file mode 100644 index 00000000000..118cc12b2bc --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditManualOptionsWithEditor/index.ts @@ -0,0 +1 @@ +export { EditManualOptionsWithEditor } from './EditManualOptionsWithEditor'; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/OptionTabs.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/OptionTabs.tsx new file mode 100644 index 00000000000..feaa8a857fa --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/OptionTabs.tsx @@ -0,0 +1,127 @@ +import { getSelectedOptionsType } from '@altinn/ux-editor/utils/optionsUtils'; +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import classes from '@altinn/ux-editor/components/config/editModal/EditOptions/EditOptions.module.css'; +import { EditCodeList, EditCodeListReference } from './EditCodeList'; +import { SelectedOptionsType } from '@altinn/ux-editor/components/config/editModal/EditOptions/EditOptions'; +import { shouldDisplayFeature } from 'app-shared/utils/featureToggleUtils'; +import { EditManualOptionsWithEditor } from './EditManualOptionsWithEditor'; +import { EditManualOptions } from './EditManualOptions'; +import { StudioTabs, StudioAlert, StudioErrorMessage } from '@studio/components'; +import { useComponentErrorMessage } from '@altinn/ux-editor/hooks'; +import type { IGenericEditComponent } from '@altinn/ux-editor/components/config/componentConfig'; +import type { SelectionComponentType } from '@altinn/ux-editor/types/FormComponent'; + +type OptionTabsProps = { + optionListIds: string[]; + renderOptions?: { + areLayoutOptionsSupported?: boolean; + }; +} & Pick, 'component' | 'handleComponentChange'>; + +export const OptionTabs = ({ + component, + handleComponentChange, + optionListIds, + renderOptions, +}: OptionTabsProps) => { + const initialSelectedOptionsType = getSelectedOptionsType( + component.optionsId, + component.options, + optionListIds || [], + ); + const [selectedOptionsType, setSelectedOptionsType] = useState(initialSelectedOptionsType); + const { t } = useTranslation(); + + useEffect(() => { + const updatedSelectedOptionsType = getSelectedOptionsType( + component.optionsId, + component.options, + optionListIds, + ); + setSelectedOptionsType(updatedSelectedOptionsType); + }, [optionListIds, component.optionsId, component.options, setSelectedOptionsType]); + + return ( + { + setSelectedOptionsType(value as SelectedOptionsType); + }} + > + + + {t('ux_editor.options.tab_codelist')} + + + {t('ux_editor.options.tab_manual')} + + + {t('ux_editor.options.tab_referenceId')} + + + + + + + + + + + + + ); +}; + +type RenderManualOptionsProps = { + areLayoutOptionsSupported: boolean; +} & Pick, 'component' | 'handleComponentChange'>; + +const RenderManualOptions = ({ + component, + handleComponentChange, + areLayoutOptionsSupported, +}: RenderManualOptionsProps) => { + const errorMessage = useComponentErrorMessage(component); + const { t } = useTranslation(); + + if (areLayoutOptionsSupported === false) { + return ( + + {t('ux_editor.options.codelist_only')} + + ); + } + + return ( + <> + {shouldDisplayFeature('codeListEditor') ? ( + + ) : ( + + )} + {errorMessage && ( + + {errorMessage} + + )} + + ); +}; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/hooks/index.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/hooks/index.ts new file mode 100644 index 00000000000..0ecd9b2cb39 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/hooks/index.ts @@ -0,0 +1,2 @@ +export { useCodeListButtonValue } from './useCodeListButtonValue'; +export { useCodeListEditorTexts } from './useCodeListEditorTexts'; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/hooks/useCodeListButtonValue.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/hooks/useCodeListButtonValue.ts new file mode 100644 index 00000000000..5ea35d8cde0 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/hooks/useCodeListButtonValue.ts @@ -0,0 +1,14 @@ +import { useTranslation } from 'react-i18next'; +import type { Option } from 'app-shared/types/Option'; + +export const useCodeListButtonValue = (options: Option[] | undefined): string | undefined => { + const { t } = useTranslation(); + + if (options?.length > 1) { + return t('ux_editor.options.multiple', { value: options.length }); + } else if (options?.length === 1) { + return t('ux_editor.options.single', { value: options.length }); + } else { + return undefined; + } +}; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/hooks/useCodeListEditorTexts.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/hooks/useCodeListEditorTexts.ts new file mode 100644 index 00000000000..6c765b5963c --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/hooks/useCodeListEditorTexts.ts @@ -0,0 +1,29 @@ +import { useTranslation } from 'react-i18next'; +import type { CodeListEditorTexts } from '@studio/components'; + +export const useCodeListEditorTexts = (): CodeListEditorTexts => { + const { t } = useTranslation(); + + return { + add: t('ux_editor.modal_new_option'), + codeList: t('ux_editor.modal_add_options_codelist'), + delete: t('general.delete'), + deleteItem: (number: number) => + t('ux_editor.modal_properties_code_list_delete_item', { number }), + description: t('general.description'), + emptyCodeList: t('ux_editor.modal_properties_code_list_empty'), + valueErrors: { + duplicateValue: t('ux_editor.radios_error_DuplicateValues'), + }, + generalError: t('ux_editor.modal_properties_code_list_general_error'), + helpText: t('ux_editor.modal_properties_textResourceBindings_help'), + itemDescription: (number: number) => + t('ux_editor.modal_properties_code_list_item_description', { number }), + itemHelpText: (number: number) => + t('ux_editor.modal_properties_code_list_item_helpText', { number }), + itemLabel: (number: number) => t('ux_editor.modal_properties_code_list_item_label', { number }), + itemValue: (number: number) => t('ux_editor.modal_properties_code_list_item_value', { number }), + label: t('ux_editor.modal_properties_textResourceBindings_title'), + value: t('general.value'), + }; +}; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/index.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/index.ts new file mode 100644 index 00000000000..a141398ee3a --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/index.ts @@ -0,0 +1 @@ +export { OptionTabs } from './OptionTabs'; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings/EditTextResourceBindings.module.css b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings/EditTextResourceBindings.module.css index 508854bae3d..329b459f623 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings/EditTextResourceBindings.module.css +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings/EditTextResourceBindings.module.css @@ -1,3 +1,3 @@ .texts { - margin-bottom: var(--fds-spacing-5); + margin-bottom: var(--fds-spacing-7); } diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings/EditTextResourceBindings.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings/EditTextResourceBindings.tsx index 763ac47d238..6679be6ad19 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings/EditTextResourceBindings.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings/EditTextResourceBindings.tsx @@ -6,7 +6,6 @@ import { StudioProperty } from '@studio/components'; import classes from './EditTextResourceBindings.module.css'; export interface EditTextResourceBindingBase { - editFormId?: string; component: FormComponent | FormContainer; handleComponentChange: (component: FormComponent | FormContainer) => void; layoutName?: string;