diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.module.css b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.module.css new file mode 100644 index 00000000000..999ef91795a --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.module.css @@ -0,0 +1,12 @@ +.codeListEditor { + border: 0; + display: flex; + flex-direction: column; + gap: var(--fds-spacing-3); + margin: 0; + padding: 0; +} + +.codeListEditor > legend { + display: none; +} diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.stories.tsx b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.stories.tsx new file mode 100644 index 00000000000..d001ffeb4d1 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.stories.tsx @@ -0,0 +1,45 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { StudioCodeListEditor } from './StudioCodeListEditor'; + +type Story = StoryObj; + +const meta: Meta = { + title: 'Components/StudioCodeListEditor', + component: StudioCodeListEditor, +}; +export default meta; + +export const Preview: Story = { + args: { + codeList: [ + { + label: 'Test 1', + value: 'test1', + description: 'Test 1 description', + }, + { + label: 'Test 2', + value: 'test2', + description: 'Test 2 description', + }, + { + label: 'Test 3', + value: 'test3', + description: 'Test 3 description', + }, + ], + texts: { + add: 'Add', + codeList: 'Code list', + delete: 'Delete', + deleteItem: (number) => `Delete item number ${number}`, + description: 'Description', + emptyCodeList: 'The code list is empty.', + itemDescription: (number) => `Description for item number ${number}`, + itemLabel: (number) => `Label for item number ${number}`, + itemValue: (number) => `Value for item number ${number}`, + label: 'Label', + value: 'Value', + }, + }, +}; diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx new file mode 100644 index 00000000000..38902340df9 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx @@ -0,0 +1,182 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import type { StudioCodeListEditorProps } from './StudioCodeListEditor'; +import { StudioCodeListEditor } from './StudioCodeListEditor'; +import type { CodeListEditorTexts } from './types/CodeListEditorTexts'; +import type { CodeList } from './types/CodeList'; +import userEvent from '@testing-library/user-event'; + +// Test data: +const texts: CodeListEditorTexts = { + add: 'Add', + codeList: 'Code list', + delete: 'Delete', + deleteItem: (number) => `Delete item number ${number}`, + description: 'Description', + emptyCodeList: 'The code list is empty.', + itemDescription: (number) => `Description for item number ${number}`, + itemLabel: (number) => `Label for item number ${number}`, + itemValue: (number) => `Value for item number ${number}`, + label: 'Label', + value: 'Value', +}; +const codeList: CodeList = [ + { + label: 'Test 1', + value: 'test1', + description: 'Test 1 description', + }, + { + label: 'Test 2', + value: 'test2', + description: 'Test 2 description', + }, + { + label: 'Test 3', + value: 'test3', + description: 'Test 3 description', + }, +]; +const onChange = jest.fn(); +const defaultProps: StudioCodeListEditorProps = { + codeList, + texts, + onChange, +}; +const numberOfHeadingRows = 1; + +describe('StudioCodeListEditor', () => { + afterEach(jest.clearAllMocks); + + it('Renders a group element with the given title', () => { + renderCodeListEditor(); + expect(screen.getByRole('group', { name: texts.codeList })).toBeInTheDocument(); + }); + + it('Renders a table of code list items', () => { + renderCodeListEditor(); + expect(screen.getByRole('table')).toBeInTheDocument(); + const numberOfCodeListItems = codeList.length; + const expectedNumberOfRows = numberOfCodeListItems + numberOfHeadingRows; + expect(screen.getAllByRole('row')).toHaveLength(expectedNumberOfRows); + }); + + it('Renders the given column headers', () => { + renderCodeListEditor(); + expect(screen.getByRole('columnheader', { name: texts.label })).toBeInTheDocument(); + expect(screen.getByRole('columnheader', { name: texts.value })).toBeInTheDocument(); + expect(screen.getByRole('columnheader', { name: texts.description })).toBeInTheDocument(); + }); + + it('Renders a button to add a new code list item', () => { + renderCodeListEditor(); + expect(screen.getByRole('button', { name: texts.add })).toBeInTheDocument(); + }); + + it('Renders a message when the code list is empty', () => { + renderCodeListEditor({ codeList: [] }); + expect(screen.getByText(texts.emptyCodeList)).toBeInTheDocument(); + }); + + it('Calls the onChange callback with the new code list when a label is changed', async () => { + const user = userEvent.setup(); + renderCodeListEditor(); + const labelInput = screen.getByRole('textbox', { name: texts.itemLabel(1) }); + const newValue = 'new text'; + await user.type(labelInput, newValue); + expect(onChange).toHaveBeenCalledTimes(newValue.length); + expect(onChange).toHaveBeenLastCalledWith([ + { ...codeList[0], label: newValue }, + codeList[1], + codeList[2], + ]); + }); + + it('Calls the onChange callback with the new code list when a value is changed', async () => { + const user = userEvent.setup(); + renderCodeListEditor(); + const valueInput = screen.getByRole('textbox', { name: texts.itemValue(1) }); + const newValue = 'new text'; + await user.type(valueInput, newValue); + expect(onChange).toHaveBeenCalledTimes(newValue.length); + expect(onChange).toHaveBeenLastCalledWith([ + { ...codeList[0], value: newValue }, + codeList[1], + codeList[2], + ]); + }); + + it('Calls the onChange callback with the new code list when a description is changed', async () => { + const user = userEvent.setup(); + renderCodeListEditor(); + const descriptionInput = screen.getByRole('textbox', { name: texts.itemDescription(1) }); + const newValue = 'new text'; + await user.type(descriptionInput, newValue); + expect(onChange).toHaveBeenCalledTimes(newValue.length); + expect(onChange).toHaveBeenLastCalledWith([ + { ...codeList[0], description: newValue }, + codeList[1], + codeList[2], + ]); + }); + + it('Calls the onChange callback with the new code list when an item is removed', async () => { + const user = userEvent.setup(); + renderCodeListEditor(); + const deleteButton = screen.getByRole('button', { name: texts.deleteItem(1) }); + await user.click(deleteButton); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledWith([codeList[1], codeList[2]]); + }); + + it('Calls the onChange callback with the new code list when an item is added', async () => { + renderCodeListEditor(); + const addButton = screen.getByRole('button', { name: texts.add }); + await userEvent.click(addButton); + expect(onChange).toHaveBeenCalledTimes(1); + expect(onChange).toHaveBeenCalledWith([ + ...codeList, + { + label: '', + value: '', + }, + ]); + }); + + it('Updates itself when the user changes something', async () => { + const user = userEvent.setup(); + renderCodeListEditor(); + const numberOfCodeListItems = codeList.length; + const expectedNumberOfRows = numberOfCodeListItems + numberOfHeadingRows; + const addButton = screen.getByRole('button', { name: texts.add }); + await user.click(addButton); + expect(screen.getAllByRole('row')).toHaveLength(expectedNumberOfRows + 1); + }); + + it('Rerenders with the new code list when the code list prop changes', () => { + const newCodeList = [ + { + label: 'New test 1', + value: 'newTest1', + description: 'New test 1 description', + }, + { + label: 'New test 2', + value: 'newTest2', + description: 'New test 2 description', + }, + ]; + const { rerender } = renderCodeListEditor(); + const numberOfCodeListItems = codeList.length; + const expectedNumberOfRows = numberOfCodeListItems + numberOfHeadingRows; + expect(screen.getAllByRole('row')).toHaveLength(expectedNumberOfRows); + rerender(); + const newNumberOfCodeListItems = newCodeList.length; + const newExpectedNumberOfRows = newNumberOfCodeListItems + numberOfHeadingRows; + expect(screen.getAllByRole('row')).toHaveLength(newExpectedNumberOfRows); + }); +}); + +function renderCodeListEditor(props: Partial = {}) { + return render(); +} diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.tsx b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.tsx new file mode 100644 index 00000000000..92b20ed5eb9 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.tsx @@ -0,0 +1,164 @@ +import type { CodeList } from './types/CodeList'; +import type { ReactElement } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; +import { StudioInputTable } from '../StudioInputTable'; +import type { CodeListItem } from './types/CodeListItem'; +import { StudioButton } from '../StudioButton'; +import { + removeCodeListItem, + addEmptyCodeListItem, + changeCodeListItem, + isCodeListEmpty, +} from './utils'; +import { StudioCodeListEditorRow } from './StudioCodeListEditorRow/StudioCodeListEditorRow'; +import type { CodeListEditorTexts } from './types/CodeListEditorTexts'; +import { + StudioCodeListEditorContext, + useStudioCodeListEditorContext, +} from './StudioCodeListEditorContext'; +import classes from './StudioCodeListEditor.module.css'; +import { PlusIcon } from '@studio/icons'; +import { StudioParagraph } from '../StudioParagraph'; + +export type StudioCodeListEditorProps = { + codeList: CodeList; + onChange: (codeList: CodeList) => void; + texts: CodeListEditorTexts; +}; + +export function StudioCodeListEditor({ + codeList, + onChange, + texts, +}: StudioCodeListEditorProps): ReactElement { + return ( + + + + ); +} + +type InternalCodeListEditorProps = Omit; + +function StatefulCodeListEditor({ + codeList: defaultCodeList, + onChange, +}: InternalCodeListEditorProps): ReactElement { + const [codeList, setCodeList] = useState(defaultCodeList); + + useEffect(() => { + setCodeList(defaultCodeList); + }, [defaultCodeList]); + + const handleChange = useCallback( + (newCodeList: CodeList) => { + setCodeList(newCodeList); + onChange(newCodeList); + }, + [onChange], + ); + + return ; +} + +function ControlledCodeListEditor({ + codeList, + onChange, +}: InternalCodeListEditorProps): ReactElement { + const { texts } = useStudioCodeListEditorContext(); + + const handleAddButtonClick = useCallback(() => { + const updatedCodeList = addEmptyCodeListItem(codeList); + onChange(updatedCodeList); + }, [codeList, onChange]); + + return ( +
+ {texts.codeList} + + +
+ ); +} + +function CodeListTable({ codeList, onChange }: InternalCodeListEditorProps): ReactElement { + return isCodeListEmpty(codeList) ? ( + + ) : ( + + ); +} + +function EmptyCodeListTable(): ReactElement { + const { texts } = useStudioCodeListEditorContext(); + return {texts.emptyCodeList}; +} + +function CodeListTableWithContent(props: InternalCodeListEditorProps): ReactElement { + return ( + + + + + ); +} + +function Headings(): ReactElement { + const { texts } = useStudioCodeListEditorContext(); + + return ( + + + {texts.label} + {texts.description} + {texts.value} + {texts.delete} + + + ); +} + +function CodeLists({ codeList, onChange }: InternalCodeListEditorProps): ReactElement { + const handleDeleteButtonClick = useCallback( + (index: number) => { + const updatedCodeList = removeCodeListItem(codeList, index); + onChange(updatedCodeList); + }, + [codeList, onChange], + ); + + const handleChange = useCallback( + (index: number, newItem: CodeListItem) => { + const updatedCodeList = changeCodeListItem(codeList, index, newItem); + onChange(updatedCodeList); + }, + [codeList, onChange], + ); + + return ( + + {codeList.map((item, index) => ( + handleChange(index, newItem)} + onDeleteButtonClick={() => handleDeleteButtonClick(index)} + /> + ))} + + ); +} + +type AddButtonProps = { + onClick: () => void; +}; + +function AddButton({ onClick }: AddButtonProps): ReactElement { + const { texts } = useStudioCodeListEditorContext(); + return ( + }> + {texts.add} + + ); +} diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorContext.ts b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorContext.ts new file mode 100644 index 00000000000..7af8e8f168e --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorContext.ts @@ -0,0 +1,10 @@ +import type { CodeListEditorTexts } from './types/CodeListEditorTexts'; +import { createContext, useContext } from 'react'; + +export type StudioCodeListEditorContextProps = { + texts: CodeListEditorTexts; +}; + +export const StudioCodeListEditorContext = createContext(null); + +export const useStudioCodeListEditorContext = () => useContext(StudioCodeListEditorContext); diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/StudioCodeListEditorRow.tsx b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/StudioCodeListEditorRow.tsx new file mode 100644 index 00000000000..daff69b8f85 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/StudioCodeListEditorRow.tsx @@ -0,0 +1,103 @@ +import type { CodeListItem } from '../types/CodeListItem'; +import { StudioInputTable } from '../../StudioInputTable'; +import { TrashIcon } from '../../../../../studio-icons'; +import React, { useCallback } from 'react'; +import { changeDescription, changeLabel, changeValue } from './utils'; +import { useStudioCodeListEditorContext } from '../StudioCodeListEditorContext'; + +type StudioCodeListEditorRowProps = { + item: CodeListItem; + number: number; + onChange: (newItem: CodeListItem) => void; + onDeleteButtonClick: () => void; +}; + +export function StudioCodeListEditorRow({ + item, + number, + onChange, + onDeleteButtonClick, +}: StudioCodeListEditorRowProps) { + const { texts } = useStudioCodeListEditorContext(); + + const handleLabelChange = useCallback( + (label: string) => { + const updatedItem = changeLabel(item, label); + onChange(updatedItem); + }, + [item, onChange], + ); + + const handleDescriptionChange = useCallback( + (description: string) => { + const updatedItem = changeDescription(item, description); + onChange(updatedItem); + }, + [item, onChange], + ); + + const handleValueChange = useCallback( + (value: string) => { + const updatedItem = changeValue(item, value); + onChange(updatedItem); + }, + [item, onChange], + ); + + return ( + + + + + + + ); +} + +type TextfieldCellProps = { + value: string; + label: string; + onChange: (newString: string) => void; +}; + +function TextfieldCell({ label, value, onChange }: TextfieldCellProps) { + const handleChange = useCallback( + (event: React.ChangeEvent) => { + onChange(event.target.value); + }, + [onChange], + ); + + return ( + + ); +} + +type DeleteButtonCellProps = { + number: number; + onClick: () => void; +}; + +function DeleteButtonCell({ onClick, number }: DeleteButtonCellProps) { + const { texts } = useStudioCodeListEditorContext(); + return ( + } + color='danger' + onClick={onClick} + title={texts.deleteItem(number)} + /> + ); +} diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/utils.test.ts b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/utils.test.ts new file mode 100644 index 00000000000..781f1b4bc4a --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/utils.test.ts @@ -0,0 +1,58 @@ +import type { CodeListItem } from '../types/CodeListItem'; +import { ObjectUtils } from '@studio/pure-functions'; +import { changeDescription, changeLabel, changeValue } from './utils'; + +// Test data: +const testItem: CodeListItem = { + label: 'Test 1', + value: 'test1', + description: 'Test 1 description', +}; +const createTestItem = (): CodeListItem => ObjectUtils.deepCopy(testItem); + +describe('StudioCodeListEditorRow utils', () => { + describe('changeLabel', () => { + it('Changes the label of the code list item', () => { + const item = createTestItem(); + const newLabel = 'Updated label'; + const updatedItem = changeLabel(item, newLabel); + expect(updatedItem.label).toBe(newLabel); + }); + + it('Returns a new instance', () => { + const item = createTestItem(); + const updatedItem = changeLabel(item, 'Updated label'); + expect(updatedItem).not.toBe(item); + }); + }); + + describe('changeDescription', () => { + it('Changes the description of the code list item', () => { + const item = createTestItem(); + const newDescription = 'Updated description'; + const updatedItem = changeDescription(item, newDescription); + expect(updatedItem.description).toBe(newDescription); + }); + + it('Returns a new instance', () => { + const item = createTestItem(); + const updatedItem = changeDescription(item, 'Updated description'); + expect(updatedItem).not.toBe(item); + }); + }); + + describe('changeValue', () => { + it('Changes the value of the code list item', () => { + const item = createTestItem(); + const newValue = 'updatedValue'; + const updatedItem = changeValue(item, newValue); + expect(updatedItem.value).toBe(newValue); + }); + + it('Returns a new instance', () => { + const item = createTestItem(); + const updatedItem = changeValue(item, 'updatedValue'); + expect(updatedItem).not.toBe(item); + }); + }); +}); diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/utils.ts b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/utils.ts new file mode 100644 index 00000000000..1f6ebec3977 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/utils.ts @@ -0,0 +1,13 @@ +import type { CodeListItem } from '../types/CodeListItem'; + +export function changeLabel(item: CodeListItem, label: string): CodeListItem { + return { ...item, label }; +} + +export function changeDescription(item: CodeListItem, description: string): CodeListItem { + return { ...item, description }; +} + +export function changeValue(item: CodeListItem, value: string): CodeListItem { + return { ...item, value }; +} diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/index.ts b/frontend/libs/studio-components/src/components/StudioCodelistEditor/index.ts new file mode 100644 index 00000000000..697f5f609a7 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/index.ts @@ -0,0 +1,5 @@ +export type { StudioCodeListEditorProps } from './StudioCodeListEditor'; +export { StudioCodeListEditor } from './StudioCodeListEditor'; +export type { CodeListEditorTexts } from './types/CodeListEditorTexts'; +export type { CodeListItem } from './types/CodeListItem'; +export type { CodeList } from './types/CodeList'; diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/types/CodeList.ts b/frontend/libs/studio-components/src/components/StudioCodelistEditor/types/CodeList.ts new file mode 100644 index 00000000000..0be0457b31c --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/types/CodeList.ts @@ -0,0 +1,3 @@ +import type { CodeListItem } from './CodeListItem'; + +export type CodeList = CodeListItem[]; diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/types/CodeListEditorTexts.ts b/frontend/libs/studio-components/src/components/StudioCodelistEditor/types/CodeListEditorTexts.ts new file mode 100644 index 00000000000..f7279737384 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/types/CodeListEditorTexts.ts @@ -0,0 +1,13 @@ +export type CodeListEditorTexts = { + add: string; + codeList: string; + delete: string; + deleteItem: (number: number) => string; + description: string; + emptyCodeList: string; + itemDescription: (number: number) => string; + itemLabel: (number: number) => string; + itemValue: (number: number) => string; + label: string; + value: string; +}; diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/types/CodeListItem.ts b/frontend/libs/studio-components/src/components/StudioCodelistEditor/types/CodeListItem.ts new file mode 100644 index 00000000000..0af72f7f616 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/types/CodeListItem.ts @@ -0,0 +1,5 @@ +export type CodeListItem = { + description?: string; + label: string; + value: string; +}; diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/utils.test.ts b/frontend/libs/studio-components/src/components/StudioCodelistEditor/utils.test.ts new file mode 100644 index 00000000000..9ceeb7b7857 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/utils.test.ts @@ -0,0 +1,90 @@ +import type { CodeList } from './types/CodeList'; +import { + addEmptyCodeListItem, + changeCodeListItem, + isCodeListEmpty, + removeCodeListItem, +} from './utils'; +import { ObjectUtils } from '@studio/pure-functions'; + +// Test data: +const testCodeList: CodeList = [ + { + label: 'Test 1', + value: 'test1', + description: 'Test 1 description', + }, + { + label: 'Test 2', + value: 'test2', + description: 'Test 2 description', + }, +]; +const createTestCodeList = (): CodeList => ObjectUtils.deepCopy(testCodeList); + +describe('StudioCodelistEditor utils', () => { + describe('addEmptyCodeListItem', () => { + it('Adds an empty item to the code list', () => { + const codeList = createTestCodeList(); + const updatedCodeList = addEmptyCodeListItem(codeList); + expect(updatedCodeList).toEqual([ + ...codeList, + { + label: '', + value: '', + }, + ]); + }); + + it('Returns a new instance', () => { + const codeList = createTestCodeList(); + const updatedCodeList = addEmptyCodeListItem(codeList); + expect(updatedCodeList).not.toBe(codeList); + }); + }); + + describe('removeCodeListItem', () => { + it('Removes the code list item at the given index', () => { + const codeList = createTestCodeList(); + const updatedCodeList = removeCodeListItem(codeList, 1); + expect(updatedCodeList).toEqual([codeList[0]]); + }); + + it('Returns a new instance', () => { + const codeList = createTestCodeList(); + const updatedCodeList = removeCodeListItem(codeList, 1); + expect(updatedCodeList).not.toBe(codeList); + }); + }); + + describe('changeCodeListItem', () => { + const updatedItem = { + label: 'Updated label', + value: 'updatedValue', + description: 'Updated description', + }; + + it('Replaces the code list item at the given index', () => { + const codeList = createTestCodeList(); + const updatedCodeList = changeCodeListItem(codeList, 1, updatedItem); + expect(updatedCodeList).toEqual([codeList[0], updatedItem]); + }); + + it('Returns a new instance', () => { + const codeList = createTestCodeList(); + const updatedCodeList = changeCodeListItem(codeList, 1, updatedItem); + expect(updatedCodeList).not.toBe(codeList); + }); + }); + + describe('isCodeListEmpty', () => { + it('Returns true when the code list is empty', () => { + expect(isCodeListEmpty([])).toBe(true); + }); + + it('Returns false when the code list is not empty', () => { + const codeList = createTestCodeList(); + expect(isCodeListEmpty(codeList)).toBe(false); + }); + }); +}); diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/utils.ts b/frontend/libs/studio-components/src/components/StudioCodelistEditor/utils.ts new file mode 100644 index 00000000000..11357c82b65 --- /dev/null +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/utils.ts @@ -0,0 +1,31 @@ +import type { CodeListItem } from './types/CodeListItem'; +import type { CodeList } from './types/CodeList'; +import { ArrayUtils } from '@studio/pure-functions'; + +export function addEmptyCodeListItem(codeList: CodeList): CodeList { + const emptyItem: CodeListItem = { + value: '', + label: '', + }; + return addCodeListItem(codeList, emptyItem); +} + +function addCodeListItem(codeList: CodeList, item: CodeListItem): CodeList { + return [...codeList, item]; +} + +export function removeCodeListItem(codeList: CodeList, index: number): CodeList { + return ArrayUtils.removeItemByIndex(codeList, index); +} + +export function changeCodeListItem( + codeList: CodeList, + index: number, + newItem: CodeListItem, +): CodeList { + return ArrayUtils.replaceByIndex(codeList, index, newItem); +} + +export function isCodeListEmpty(codeList: CodeList): boolean { + return codeList.length === 0; +} diff --git a/frontend/libs/studio-components/src/components/index.ts b/frontend/libs/studio-components/src/components/index.ts index 1dab625ced8..c0748e1991c 100644 --- a/frontend/libs/studio-components/src/components/index.ts +++ b/frontend/libs/studio-components/src/components/index.ts @@ -9,6 +9,7 @@ export * from './StudioCard'; export * from './StudioCenter'; export * from './StudioCheckbox'; export * from './StudioCodeFragment'; +export * from './StudioCodelistEditor'; export * from './StudioCombobox'; export * from './StudioDecimalInput'; export * from './StudioDeleteButton';