diff --git a/frontend/libs/studio-components/src/components/StudioTextResourceInput/StudioTextResourceInput.test.tsx b/frontend/libs/studio-components/src/components/StudioTextResourceInput/StudioTextResourceInput.test.tsx index 66e936bd20d..366084cf827 100644 --- a/frontend/libs/studio-components/src/components/StudioTextResourceInput/StudioTextResourceInput.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioTextResourceInput/StudioTextResourceInput.test.tsx @@ -1,3 +1,4 @@ +import type { ForwardedRef } from 'react'; import React from 'react'; import type { StudioTextResourceInputProps } from './StudioTextResourceInput'; import { StudioTextResourceInput } from './StudioTextResourceInput'; @@ -9,6 +10,9 @@ import { textResourcesMock } from '../../test-data/textResourcesMock'; import type { UserEvent } from '@testing-library/user-event'; import { userEvent } from '@testing-library/user-event'; import { getTextResourceById } from './utils'; +import { testRefForwarding } from '../../test-utils/testRefForwarding'; +import { testRootClassNameAppending } from '../../test-utils/testRootClassNameAppending'; +import { testCustomAttributes } from '../../test-utils/testCustomAttributes'; // Test data: const textResources: TextResource[] = textResourcesMock; @@ -92,10 +96,28 @@ describe('StudioTextResourceInput', () => { await switchToSearchMode(user); expect(screen.getByText(currentId)).toBeInTheDocument(); }); + + it('Forwards the ref if given', () => { + testRefForwarding((ref) => renderTextResourceInput({}, ref), getValueField); + }); + + it('Appends the given class name to the root class', () => { + testRootClassNameAppending((className) => renderTextResourceInput({ className })); + }); + + it('Applies additional props to the input element', () => { + testCustomAttributes( + renderTextResourceInput, + getValueField, + ); + }); }); -function renderTextResourceInput(props: Partial = {}): RenderResult { - return render(); +function renderTextResourceInput( + props: Partial = {}, + ref?: ForwardedRef, +): RenderResult { + return render(); } function getValueField(): HTMLInputElement { diff --git a/frontend/libs/studio-components/src/components/StudioTextResourceInput/StudioTextResourceInput.tsx b/frontend/libs/studio-components/src/components/StudioTextResourceInput/StudioTextResourceInput.tsx index 74635162a16..ff9625132f6 100644 --- a/frontend/libs/studio-components/src/components/StudioTextResourceInput/StudioTextResourceInput.tsx +++ b/frontend/libs/studio-components/src/components/StudioTextResourceInput/StudioTextResourceInput.tsx @@ -1,142 +1,198 @@ -import type { ChangeEvent, ReactElement } from 'react'; -import React, { useState } from 'react'; +import type { ChangeEvent, HTMLAttributes, ReactElement } from 'react'; +import React, { forwardRef, useState } from 'react'; import type { TextResource } from '../../types/TextResource'; import { StudioTextResourcePicker } from '../StudioTextResourcePicker'; import { StudioCodeFragment } from '../StudioCodeFragment'; import { ToggleGroup } from '@digdir/designsystemet-react'; import { PencilIcon, MagnifyingGlassIcon } from '@studio/icons'; import classes from './StudioTextResourceInput.module.css'; +import type { StudioTextfieldProps } from '../StudioTextfield'; import { StudioTextfield } from '../StudioTextfield'; import { changeTextResourceInList, editTextResourceValue, getTextResourceById } from './utils'; import { usePropState } from '@studio/hooks'; import type { TextResourceInputTexts } from './types/TextResourceInputTexts'; +import cn from 'classnames'; -export type StudioTextResourceInputProps = { +export type StudioTextResourceInputProps = TextResourceInputPropsBase & + HTMLAttributes; + +type TextResourceInputPropsBase = { currentId: string; + currentIdClass?: string; + inputClass?: string; onChangeCurrentId: (id: string) => void; onChangeTextResource: (textResource: TextResource) => void; textResources: TextResource[]; texts: TextResourceInputTexts; + toggleClass?: string; }; -export function StudioTextResourceInput({ - currentId: givenCurrentId, - onChangeTextResource, - onChangeCurrentId, - textResources: givenTextResources, - texts, -}: StudioTextResourceInputProps): ReactElement { - const [inputMode, setInputMode] = useState(InputMode.EditValue); - const [currentId, setCurrentId] = usePropState(givenCurrentId); - const [textResources, setTextResources] = usePropState(givenTextResources); - - const handleChangeCurrentId = (id: string) => { - setCurrentId(id); - onChangeCurrentId(id); - }; - - const handleTextResourceChange = (newTextResource: TextResource) => { - const newList = changeTextResourceInList(textResources, newTextResource); - setTextResources(newList); - onChangeTextResource(newTextResource); - }; +export const StudioTextResourceInput = forwardRef( + ( + { + className: givenClass, + currentId: givenCurrentId, + currentIdClass, + inputClass, + onChangeTextResource, + onChangeCurrentId, + onKeyDown, + textResources: givenTextResources, + texts, + toggleClass, + ...rest + }, + ref, + ): ReactElement => { + const [mode, setMode] = useState(Mode.EditValue); + const [currentId, setCurrentId] = usePropState(givenCurrentId); + const [textResources, setTextResources] = usePropState(givenTextResources); + + const handleChangeCurrentId = (id: string): void => { + setCurrentId(id); + onChangeCurrentId(id); + }; + + const handleTextResourceChange = (newTextResource: TextResource): void => { + const newList = changeTextResourceInList(textResources, newTextResource); + setTextResources(newList); + onChangeTextResource(newTextResource); + }; + + const rootClass = cn(givenClass, classes.container); + + return ( +
+ + + +
+ ); + }, +); - return ( -
- - - -
- ); -} +StudioTextResourceInput.displayName = 'StudioTextResourceInput'; -enum InputMode { +enum Mode { EditValue = 'editValue', Search = 'search', } type InputBoxProps = StudioTextResourceInputProps & { - inputMode: InputMode; + mode: Mode; }; -function InputBox({ - currentId, - inputMode, - onChangeCurrentId, - onChangeTextResource, - textResources, - texts, -}: InputBoxProps): ReactElement { - const currentTextResource = getTextResourceById(textResources, currentId); - - switch (inputMode) { - case InputMode.EditValue: - return ( - - ); - case InputMode.Search: - return ( - - ); - } -} - -type ValueFieldProps = { - label: string; +const InputBox = forwardRef( + ( + { + currentId, + inputClass, + mode, + onChangeCurrentId, + onChangeTextResource, + onKeyDown, + textResources, + texts, + ...rest + }, + ref, + ): ReactElement => { + const currentTextResource = getTextResourceById(textResources, currentId); + const className = cn(inputClass, classes.inputbox); + + switch (mode) { + case Mode.EditValue: + return ( + + ); + case Mode.Search: + return ( + + ); + } + }, +); + +InputBox.displayName = 'InputBox'; + +type ValueFieldProps = StudioTextfieldProps & { textResource: TextResource; - onChange: (textResource: TextResource) => void; + onChangeTextResource: (textResource: TextResource) => void; }; -function ValueField({ textResource, onChange, label }: ValueFieldProps): ReactElement { - const handleChange = (event: ChangeEvent) => { - const { value } = event.target; - const newTextResource = editTextResourceValue(textResource, value); - onChange(newTextResource); - }; +const ValueField = forwardRef( + ({ textResource, onChange, onChangeTextResource, ...rest }, ref): ReactElement => { + const handleChange = (event: ChangeEvent): void => { + const { value } = event.target; + const newTextResource = editTextResourceValue(textResource, value); + onChangeTextResource(newTextResource); + onChange?.(event); + }; + + return ( + + ); + }, +); - return ( - - ); -} +ValueField.displayName = 'ValueField'; type InputModeToggleProps = { - inputMode: InputMode; - onToggle: (mode: InputMode) => void; + className?: string; + inputMode: Mode; + onToggle: (mode: Mode) => void; texts: TextResourceInputTexts; }; -function InputModeToggle({ inputMode, onToggle, texts }: InputModeToggleProps): ReactElement { +function ModeToggle({ + className: givenClass, + inputMode, + onToggle, + texts, +}: InputModeToggleProps): ReactElement { + const className = cn(givenClass, classes.toggle); return ( - - + + - + @@ -144,13 +200,15 @@ function InputModeToggle({ inputMode, onToggle, texts }: InputModeToggleProps): } type CurrentIdProps = { + className?: string; currentId: string; label: string; }; -function CurrentId({ currentId, label }: CurrentIdProps): ReactElement { +function CurrentId({ className: givenClass, currentId, label }: CurrentIdProps): ReactElement { + const className = cn(givenClass, classes.id); return ( -
+
{label} {currentId}