diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 4542a2a226c..bc9ac83d8a8 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -1318,6 +1318,9 @@ "ux_editor.component_properties.subform.choose_layout_set_description": " Før du kan bruke komponenten Tabell for underskjema, må du velge hvilket underskjema du skal bruke den med. Deretter kan du velge hvilke egenskaper komponenten skal ha.", "ux_editor.component_properties.subform.choose_layout_set_header": "Velg underskjemaet du vil bruke", "ux_editor.component_properties.subform.choose_layout_set_label": "Velg et underskjema", + "ux_editor.component_properties.subform.create_layout_set_button": "Lag et nytt underskjema", + "ux_editor.component_properties.subform.create_layout_set_description": "Hvis du velger å lage et nytt underskjema, oppretter vi et tomt underskjema for deg. Det må du selv utforme, før du kan sette opp tabellen.", + "ux_editor.component_properties.subform.created_layout_set_name": "Navn på underskjema", "ux_editor.component_properties.subform.go_to_layout_set": "Gå til utforming av underskjemaet", "ux_editor.component_properties.subform.no_layout_sets_acting_as_subform": "Det finnes ingen sidegrupper i løsningen som kan brukes som et underskjema", "ux_editor.component_properties.subform.selected_layout_set_label": "Underskjema", diff --git a/frontend/libs/studio-components/src/components/StudioRecommendedNextAction/StudioRecommendedNextAction.module.css b/frontend/libs/studio-components/src/components/StudioRecommendedNextAction/StudioRecommendedNextAction.module.css index d2c491f06f4..71b154d5cb5 100644 --- a/frontend/libs/studio-components/src/components/StudioRecommendedNextAction/StudioRecommendedNextAction.module.css +++ b/frontend/libs/studio-components/src/components/StudioRecommendedNextAction/StudioRecommendedNextAction.module.css @@ -7,5 +7,5 @@ .description { margin-top: var(--fds-spacing-2); - margin-bottom: var(--fds-spacing-4); + margin-bottom: var(--fds-spacing-1); } diff --git a/frontend/libs/studio-components/src/components/StudioRecommendedNextAction/StudioRecommendedNextAction.tsx b/frontend/libs/studio-components/src/components/StudioRecommendedNextAction/StudioRecommendedNextAction.tsx index 12313cb3cfa..5bf6ed75fde 100644 --- a/frontend/libs/studio-components/src/components/StudioRecommendedNextAction/StudioRecommendedNextAction.tsx +++ b/frontend/libs/studio-components/src/components/StudioRecommendedNextAction/StudioRecommendedNextAction.tsx @@ -10,8 +10,8 @@ export type StudioRecommendedNextActionProps = { saveButtonText?: string; onSkip?: React.MouseEventHandler; skipButtonText?: string; - title: string; - description: string; + title?: string; + description?: string; hideSaveButton?: boolean; hideSkipButton?: boolean; children: React.ReactNode; diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformLayoutSet/CreateNewSubformLayoutSet.module.css b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformLayoutSet/CreateNewSubformLayoutSet.module.css new file mode 100644 index 00000000000..7def1fa7574 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformLayoutSet/CreateNewSubformLayoutSet.module.css @@ -0,0 +1,10 @@ +.savelayoutSetButton { + display: flex; + align-self: flex-start; + border: 2px solid var(--success-color); + color: var(--success-color); +} + +.headerIcon { + font-size: large; +} diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformLayoutSet/CreateNewSubformLayoutSet.test.tsx b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformLayoutSet/CreateNewSubformLayoutSet.test.tsx new file mode 100644 index 00000000000..970ce181232 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformLayoutSet/CreateNewSubformLayoutSet.test.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import { renderWithProviders } from '../../../../../../testing/mocks'; +import { CreateNewSubformLayoutSet } from './CreateNewSubformLayoutSet'; +import type { ComponentType } from 'app-shared/types/ComponentType'; +import { textMock } from '@studio/testing/mocks/i18nMock'; +import { screen, waitFor } from '@testing-library/react'; +import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; +import { app, org } from '@studio/testing/testids'; +import { QueryKey } from 'app-shared/types/QueryKey'; +import { layoutSets } from 'app-shared/mocks/mocks'; +import type { LayoutSets } from 'app-shared/types/api/LayoutSetsResponse'; +import userEvent from '@testing-library/user-event'; +import type { FormComponent } from '../../../../../../types/FormComponent'; +import { AppContext } from '../../../../../../AppContext'; +import { appContextMock } from '../../../../../../testing/appContextMock'; + +const onSubFormCreatedMock = jest.fn(); + +describe('CreateNewSubformLayoutSet ', () => { + afterEach(jest.clearAllMocks); + + it('displays the card with label and input field', () => { + renderCreateNewSubformLayoutSet(); + const card = screen.getByRole('textbox', { + name: textMock('ux_editor.component_properties.subform.created_layout_set_name'), + }); + + expect(card).toBeInTheDocument(); + }); + + it('displays the input field', () => { + renderCreateNewSubformLayoutSet(); + const input = screen.getByRole('textbox'); + expect(input).toBeInTheDocument(); + }); + + it('displays the save button', () => { + renderCreateNewSubformLayoutSet(); + const saveButton = screen.getByRole('button', { name: textMock('general.close') }); + expect(saveButton).toBeInTheDocument(); + }); + + it('calls onSubFormCreated when save button is clicked', async () => { + const user = userEvent.setup(); + renderCreateNewSubformLayoutSet(); + const input = screen.getByRole('textbox'); + await user.type(input, 'NewSubForm'); + const saveButton = screen.getByRole('button', { name: textMock('general.close') }); + await user.click(saveButton); + await waitFor(() => expect(onSubFormCreatedMock).toHaveBeenCalledTimes(1)); + expect(onSubFormCreatedMock).toHaveBeenCalledWith('NewSubForm'); + }); +}); + +const renderCreateNewSubformLayoutSet = ( + layoutSetsMock: LayoutSets = layoutSets, + componentProps: Partial> = {}, +) => { + const queryClient = createQueryClientMock(); + queryClient.setQueryData([QueryKey.LayoutSets, org, app], layoutSetsMock); + return renderWithProviders( + + + , + { queryClient }, + ); +}; diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformLayoutSet/CreateNewSubformLayoutSet.tsx b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformLayoutSet/CreateNewSubformLayoutSet.tsx new file mode 100644 index 00000000000..ef9b52de926 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformLayoutSet/CreateNewSubformLayoutSet.tsx @@ -0,0 +1,61 @@ +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { StudioButton, StudioCard, StudioTextfield } from '@studio/components'; +import { ClipboardIcon, CheckmarkIcon } from '@studio/icons'; +import { useAddLayoutSetMutation } from 'app-development/hooks/mutations/useAddLayoutSetMutation'; +import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; +import classes from './CreateNewSubformLayoutSet.module.css'; + +type CreateNewSubformLayoutSetProps = { + onSubFormCreated: (layoutSetName: string) => void; +}; + +export const CreateNewSubformLayoutSet = ({ + onSubFormCreated, +}: CreateNewSubformLayoutSetProps): React.ReactElement => { + const { t } = useTranslation(); + const [newSubForm, setNewSubForm] = useState(''); + const { org, app } = useStudioEnvironmentParams(); + const { mutate: addLayoutSet } = useAddLayoutSetMutation(org, app); + + const createNewSubform = () => { + if (!newSubForm) return; + addLayoutSet({ + layoutSetIdToUpdate: newSubForm, + layoutSetConfig: { + id: newSubForm, + type: 'subform', + }, + }); + onSubFormCreated(newSubForm); + setNewSubForm(''); + }; + + function handleChange(e: React.ChangeEvent) { + setNewSubForm(e.target.value); + } + + return ( + + + + + + + } + onClick={createNewSubform} + title={t('general.close')} + variant='tertiary' + color='success' + /> + + + ); +}; diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformLayoutSet/index.ts b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformLayoutSet/index.ts new file mode 100644 index 00000000000..39c8808d341 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/CreateNewSubformLayoutSet/index.ts @@ -0,0 +1 @@ +export { CreateNewSubformLayoutSet } from './CreateNewSubformLayoutSet'; diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/EditLayoutSet.module.css b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/EditLayoutSet.module.css new file mode 100644 index 00000000000..cec24eef80a --- /dev/null +++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/EditLayoutSet.module.css @@ -0,0 +1,4 @@ +.button { + padding-left: 0; + border-radius: var(--fds-sizing-1); +} diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/EditLayoutSet.tsx b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/EditLayoutSet.tsx index 84200aeaa3e..6968f45919a 100644 --- a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/EditLayoutSet.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSet/EditLayoutSet.tsx @@ -2,19 +2,29 @@ import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { DefinedLayoutSet } from './DefinedLayoutSet/DefinedLayoutSet'; import { SelectLayoutSet } from './SelectLayoutSet/SelectLayoutSet'; -import { StudioRecommendedNextAction } from '@studio/components'; +import { StudioParagraph, StudioProperty, StudioRecommendedNextAction } from '@studio/components'; +import { PlusIcon } from '@studio/icons'; +import classes from './EditLayoutSet.module.css'; +import { CreateNewSubformLayoutSet } from './CreateNewSubformLayoutSet'; type EditLayoutSetProps = { existingLayoutSetForSubform: string; onUpdateLayoutSet: (layoutSetId: string) => void; + onSubFormCreated: (layoutSetName: string) => void; }; export const EditLayoutSet = ({ existingLayoutSetForSubform, onUpdateLayoutSet, + onSubFormCreated, }: EditLayoutSetProps): React.ReactElement => { const { t } = useTranslation(); const [isLayoutSetSelectorVisible, setIsLayoutSetSelectorVisible] = useState(false); + const [showCreateSubform, setShowCreateSubform] = useState(false); + + function handleClick() { + setShowCreateSubform(true); + } if (isLayoutSetSelectorVisible) { return ( @@ -26,23 +36,34 @@ export const EditLayoutSet = ({ /> ); } - const layoutSetIsUndefined = !existingLayoutSetForSubform; if (layoutSetIsUndefined) { return ( - - - + <> + + + {t('ux_editor.component_properties.subform.create_layout_set_description')} + + + } + onClick={handleClick} + /> + + {showCreateSubform && } + ); } diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSetForSubform.test.tsx b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSetForSubform.test.tsx index f4f80260adc..9be5df6e5d6 100644 --- a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSetForSubform.test.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSetForSubform.test.tsx @@ -47,6 +47,29 @@ describe('EditLayoutSetForSubform', () => { expect(setLayoutSetButton).toBeInTheDocument(); }); + it('displays a button(Opprett et nytt skjema) to set a layout set for the subform', async () => { + const subformLayoutSetId = 'subformLayoutSetId'; + renderEditLayoutSetForSubform({ sets: [{ id: subformLayoutSetId, type: 'subform' }] }); + const createNewLayoutSetButton = screen.getByRole('button', { + name: textMock('ux_editor.component_properties.subform.create_layout_set_button'), + }); + expect(createNewLayoutSetButton).toBeInTheDocument(); + }); + + it('renders CreateNewLayoutSet component when clicking the create new layout set button', async () => { + const user = userEvent.setup(); + const subformLayoutSetId = 'subformLayoutSetId'; + renderEditLayoutSetForSubform({ sets: [{ id: subformLayoutSetId, type: 'subform' }] }); + const createNewLayoutSetButton = screen.getByRole('button', { + name: textMock('ux_editor.component_properties.subform.create_layout_set_button'), + }); + await user.click(createNewLayoutSetButton); + const createNewLayoutSetComponent = screen.getByRole('textbox', { + name: textMock('ux_editor.component_properties.subform.created_layout_set_name'), + }); + expect(createNewLayoutSetComponent).toBeInTheDocument(); + }); + it('displays a select to choose a layout set for the subform', async () => { const subformLayoutSetId = 'subformLayoutSetId'; renderEditLayoutSetForSubform({ sets: [{ id: subformLayoutSetId, type: 'subform' }] }); @@ -111,6 +134,26 @@ describe('EditLayoutSetForSubform', () => { ); }); + it('calls handleComponentChange after creating a new layout set and clicking Lukk button', async () => { + const user = userEvent.setup(); + const subformLayoutSetId = 'subformLayoutSetId'; + renderEditLayoutSetForSubform({ sets: [{ id: subformLayoutSetId, type: 'subform' }] }); + const createNewLayoutSetButton = screen.getByRole('button', { + name: textMock('ux_editor.component_properties.subform.create_layout_set_button'), + }); + await user.click(createNewLayoutSetButton); + const input = screen.getByRole('textbox'); + await user.type(input, 'NewSubForm'); + const saveButton = screen.getByRole('button', { name: textMock('general.close') }); + await user.click(saveButton); + expect(handleComponentChangeMock).toHaveBeenCalledTimes(1); + expect(handleComponentChangeMock).toHaveBeenCalledWith( + expect.objectContaining({ + layoutSet: 'NewSubForm', + }), + ); + }); + it('closes the view mode when clicking close button after selecting a layout set', async () => { const user = userEvent.setup(); const subformLayoutSetId = 'subformLayoutSetId'; diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSetForSubform.tsx b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSetForSubform.tsx index 80143db4bf2..db9441e69b3 100644 --- a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSetForSubform.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditLayoutSetForSubform/EditLayoutSetForSubform.tsx @@ -6,6 +6,7 @@ import { SubformUtilsImpl } from '../../../../classes/SubformUtils'; import { useLayoutSetsQuery } from 'app-shared/hooks/queries/useLayoutSetsQuery'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; import type { IGenericEditComponent } from '../../../../components/config/componentConfig'; +import { useAppContext } from '../../../../hooks'; export const EditLayoutSetForSubform = ({ handleComponentChange, @@ -13,6 +14,7 @@ export const EditLayoutSetForSubform = ({ }: IGenericEditComponent): React.ReactElement => { const { org, app } = useStudioEnvironmentParams(); const { data: layoutSets } = useLayoutSetsQuery(org, app); + const { setSelectedFormLayoutSetName } = useAppContext(); const subformUtils = new SubformUtilsImpl(layoutSets.sets); @@ -25,10 +27,16 @@ export const EditLayoutSetForSubform = ({ handleComponentChange(updatedComponent); }; + function handleCreatedSubForm(layoutSetName: string) { + setSelectedFormLayoutSetName(layoutSetName); + handleUpdatedLayoutSet(layoutSetName); + } + return ( ); };