-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support array of string for multilang fieldset (#911)
- Loading branch information
1 parent
04d3b4e
commit ada0ffd
Showing
5 changed files
with
124 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
export interface LocalizedStrings { | ||
[kode: string]: string; | ||
[code: string]: string | string[]; | ||
} | ||
|
||
export type ISOLanguage = 'nb' | 'nn' | 'no' | 'en'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,125 +1,175 @@ | ||
'use client'; | ||
|
||
import { ReactNode } from 'react'; | ||
import { ReactNode, useState } from 'react'; | ||
import { localization } from '@catalog-frontend/utils'; | ||
import { Fieldset, Button, Box, Paragraph, Textfield } from '@digdir/designsystemet-react'; | ||
import { Fieldset, Box, Paragraph, Textfield, ErrorMessage, Chip } from '@digdir/designsystemet-react'; | ||
import { FastField, useFormikContext } from 'formik'; | ||
|
||
import styles from './formik-language-fieldset.module.scss'; | ||
import { ISOLanguage, LocalizedStrings } from '@catalog-frontend/types'; | ||
import { PlusCircleIcon, TrashIcon } from '@navikt/aksel-icons'; | ||
import { TextareaWithPrefix } from '../textarea-with-prefix'; | ||
import _ from 'lodash'; | ||
import { AddButton, DeleteButton } from '../button'; | ||
|
||
type LanuguageFieldsetProps = { | ||
legend?: ReactNode; | ||
name: string; | ||
errorMessage?: string; | ||
errorArgs?: object; | ||
requiredLanguages?: Omit<ISOLanguage, 'no'>[]; | ||
as?: typeof Textfield | typeof TextareaWithPrefix; | ||
multiple?: boolean; | ||
}; | ||
|
||
const allowedLanguages = Object.freeze<ISOLanguage[]>(['nb', 'nn', 'en']); | ||
|
||
export const FormikLanguageFieldset = ({ | ||
legend, | ||
name, | ||
errorMessage, | ||
errorArgs, | ||
requiredLanguages, | ||
as: renderAs = Textfield, | ||
multiple = false, | ||
}: LanuguageFieldsetProps) => { | ||
const { errors, getFieldMeta, setFieldValue } = useFormikContext<Record<string, LocalizedStrings>>(); | ||
const { errors, values, getFieldMeta, setFieldValue } = useFormikContext<Record<string, LocalizedStrings>>(); | ||
const [textValue, setTextValue] = useState<Record<string, string>>({}); | ||
|
||
const handleAddLanguage = (lang: string) => { | ||
setFieldValue(`${name}.${lang}`, ''); | ||
setFieldValue(`${name}.${lang}`, multiple ? [] : ''); | ||
}; | ||
|
||
const handleRemoveLanguage = (lang: string) => { | ||
setFieldValue(`${name}.${lang}`, undefined); | ||
}; | ||
|
||
const handleOnChangeTextValue = (value: string, lang: string) => { | ||
setTextValue((prev) => ({ ...prev, ...{ [lang]: value } })); | ||
}; | ||
|
||
const handleAddTextValue = (lang: string) => { | ||
if (Boolean(textValue[lang]) === true) { | ||
const textValues = [...(values?.[name]?.[lang] as string[]), textValue[lang]]; | ||
setFieldValue(`${name}.${lang}`, textValues); | ||
setTextValue((prev) => ({ ...prev, ...{ [lang]: '' } })); | ||
} | ||
}; | ||
|
||
const handleRemoveTextValue = (index: number, lang: string) => { | ||
const textValues = [...(values?.[name]?.[lang] as string[])]; | ||
textValues.splice(index, 1); | ||
setFieldValue(`${name}.${lang}`, textValues); | ||
}; | ||
|
||
const visibleLanguageFields = allowedLanguages.filter((lang) => { | ||
const metadata = getFieldMeta(`${name}.${lang}`); | ||
return requiredLanguages?.includes(lang) || metadata.value !== undefined; | ||
}); | ||
|
||
const visibleLanguageButtons = allowedLanguages.filter((lang) => !visibleLanguageFields.includes(lang)); | ||
const languagesWithError = allowedLanguages | ||
.filter((lang) => _.get(errors, `${name}.${lang}`)) | ||
.map((lang) => localization.language[lang]); | ||
|
||
return ( | ||
<Fieldset legend={legend} size='sm'> | ||
<Fieldset | ||
legend={legend} | ||
size='sm' | ||
> | ||
{visibleLanguageFields.map((lang) => ( | ||
<div | ||
key={lang} | ||
className={styles.languageField} | ||
> | ||
<FastField | ||
as={renderAs} | ||
name={`${name}.${lang}`} | ||
size='sm' | ||
aria-label={localization.language[lang]} | ||
|
||
error={errors?.[name]?.[lang]} | ||
{...(renderAs === TextareaWithPrefix | ||
? { | ||
cols: 80, | ||
prefix: ( | ||
<> | ||
<Paragraph | ||
size='sm' | ||
variant='short' | ||
> | ||
{localization.language[lang]} | ||
</Paragraph> | ||
{!requiredLanguages?.includes(lang) && ( | ||
<Box> | ||
<Button | ||
variant='tertiary' | ||
<div key={lang}> | ||
{multiple ? ( | ||
<> | ||
<Box className={styles.languageField}> | ||
<Textfield | ||
size='sm' | ||
aria-label={localization.language[lang]} | ||
prefix={localization.language[lang]} | ||
value={textValue[lang]} | ||
onChange={(e) => handleOnChangeTextValue(e.target.value, lang)} | ||
onKeyDown={(e) => { | ||
if (e.code === 'Enter') { | ||
handleAddTextValue(lang); | ||
} | ||
}} | ||
onBlur={() => handleAddTextValue(lang)} | ||
/> | ||
<AddButton | ||
variant='secondary' | ||
disabled={Boolean(textValue[lang]) === false} | ||
onClick={() => handleAddTextValue(lang)} | ||
/> | ||
|
||
<DeleteButton | ||
variant='tertiary' | ||
onClick={() => handleRemoveLanguage(lang)} | ||
/> | ||
</Box> | ||
<Chip.Group size='sm'> | ||
{(values?.[name]?.[lang] as string[] | undefined)?.map((v, i) => ( | ||
<Chip.Removable onClick={() => handleRemoveTextValue(i, lang)}>{v}</Chip.Removable> | ||
))} | ||
</Chip.Group> | ||
</> | ||
) : ( | ||
<Box className={styles.languageField}> | ||
<FastField | ||
as={renderAs} | ||
name={`${name}.${lang}`} | ||
size='sm' | ||
aria-label={localization.language[lang]} | ||
error={Boolean(_.get(errors, `${name}.${lang}`))} | ||
{...(renderAs === TextareaWithPrefix | ||
? { | ||
cols: 110, | ||
prefix: ( | ||
<> | ||
<Paragraph | ||
size='sm' | ||
color='danger' | ||
onClick={() => handleRemoveLanguage(lang)} | ||
variant='short' | ||
> | ||
<TrashIcon | ||
title={localization.icon.trash} | ||
fontSize='1.5rem' | ||
/> | ||
{localization.button.delete} | ||
</Button> | ||
</Box> | ||
)} | ||
</> | ||
), | ||
} | ||
: { | ||
prefix: localization.language[lang], | ||
})} | ||
/> | ||
{!requiredLanguages?.includes(lang) && renderAs !== TextareaWithPrefix && ( | ||
<Button | ||
variant='tertiary' | ||
size='sm' | ||
color='danger' | ||
onClick={() => handleRemoveLanguage(lang)} | ||
> | ||
<TrashIcon | ||
title={localization.button.delete} | ||
fontSize='1.5rem' | ||
{localization.language[lang]} | ||
</Paragraph> | ||
{!requiredLanguages?.includes(lang) && ( | ||
<Box> | ||
<DeleteButton onClick={() => handleRemoveLanguage(lang)} /> | ||
</Box> | ||
)} | ||
</> | ||
), | ||
} | ||
: { | ||
prefix: localization.language[lang], | ||
})} | ||
/> | ||
{localization.button.delete} | ||
</Button> | ||
{!requiredLanguages?.includes(lang) && renderAs !== TextareaWithPrefix && ( | ||
<DeleteButton onClick={() => handleRemoveLanguage(lang)} /> | ||
)} | ||
</Box> | ||
)} | ||
</div> | ||
))} | ||
<div className={styles.languageButtons}> | ||
{visibleLanguageButtons.map((lang) => ( | ||
<Button | ||
<AddButton | ||
key={lang} | ||
variant='tertiary' | ||
color='first' | ||
size='sm' | ||
onClick={() => handleAddLanguage(lang)} | ||
> | ||
<PlusCircleIcon fontSize='1rem' /> | ||
{localization.language[lang] ?? '?'} | ||
</Button> | ||
</AddButton> | ||
))} | ||
</div> | ||
|
||
{languagesWithError.length > 0 && ( | ||
<ErrorMessage | ||
size='sm' | ||
error | ||
> | ||
{errorMessage | ||
? `${localization.formatString(errorMessage, { ...errorArgs, language: languagesWithError.join(', ') })}` | ||
: 'This field ({language}) is required'} | ||
</ErrorMessage> | ||
)} | ||
</Fieldset> | ||
); | ||
}; |