| name | sif-dialog-migration |
| type | action |
| description | Porter *ListAndDialog-komponenter fra sif-common-forms-ds (v1/Formik) til sif-soknad-forms (v2/RHF) via ModalFormAndList. |
sif-dialog-migration
Bruk nÄr
- Bruker ber om Ă„ porte en dialog fra
sif-common-forms-ds til sif-soknad-forms.
- Bruker nevner
porter dialog, migrer ListAndDialog, flytt til sif-soknad-forms, ModalFormAndList, eller en spesifikk v1-dialog.
- Bruker referer til en spesifikk v1-dialog som skal til v2.
Leveranse
- Domenetype i
src/dialogs/<navn>/index.ts
XxxDialogForm â RHF-form med felter, validering og konverteringsfunksjoner
XxxDialog â Aksel Dialog-shell
XxxList â liste med ItemListDarkside
XxxListAndDialog â kobler ModalFormAndList med domenekomponentene
- Story i
.stories.tsx
- i18n (
nb.ts og nn.ts) under src/dialogs/<navn>/i18n/
- Eksport fra
src/dialogs/<navn>/index.ts og src/dialogs/index.ts
Avgrensning
- Skillen gjelder én dialog om gangen.
- For generell FormikâRHF i app-steg (ikke dialoger i
sif-soknad-forms) â bruk sif-formik-to-rhf.
- For rene i18n-endringer â bruk
sif-intl.
Arkitektur
ModalFormAndList
Generisk komponent i packages/sif-soknad-forms/src/components/ModalFormAndList.tsx som hÄndterer:
- Dialog-state (Äpne for ny/rediger, lukke)
- Add/edit/delete-operasjoner pÄ en liste
- Fokusretur til legg-til-knapp etter ny oppfĂžring
maxItems-begrensning og emptyState
Alle *ListAndDialog-porter bruker denne som orkestreringskomponent. Domenelogikk holdes i egne filer.
Filstruktur per dialog
src/dialogs/<navn>/
index.ts â domenetype + barrel exports
XxxDialogForm.tsx â useForm + createSifFormComponents + useSifValidate
XxxDialog.tsx â Aksel Dialog shell rundt formen
XxxList.tsx â ItemListDarkside fra @navikt/sif-common-ui
XxxListAndDialog.tsx â tynn wrapper: mapper domeneprops til ModalFormAndList
XxxListAndDialog.stories.tsx
i18n/
nb.ts
nn.ts
Komponentansvar
| Fil | Ansvar |
|---|
XxxDialogForm | RHF-form: felter, validering, formValuesToXxx(), xxxToFormValues() |
XxxDialog | Aksel Dialog-shell: tittel, body (form), footer (knapper) |
XxxList | Render liste med ItemListDarkside, label-rendering, formattering |
XxxListAndDialog | Kobler ModalFormAndList med domenekomponentene via render-props |
ArbeidsrekkefĂžlge
For hver dialog som porteres:
- Les v1-kildene â type, form, list, dialog, i18n. Kartlegg felter, validering og submit-logikk.
- Opprett domenetype i
src/dialogs/<navn>/index.ts.
- Opprett
XxxDialogForm â RHF-form med useForm, createSifFormComponents, useSifValidate. Inkluder formValuesToXxx og xxxToFormValues.
- Opprett
XxxDialog â Aksel Dialog-shell rundt formen.
- Opprett
XxxList â bruker ItemListDarkside fra @navikt/sif-common-ui.
- Opprett
XxxListAndDialog â tynn wrapper som kobler ModalFormAndList med domenekomponentene.
- Opprett story â
XxxListAndDialog.stories.tsx med StoryFrame. Intl hÄndteres globalt.
- Legg til i18n â nb.ts og nn.ts under
src/dialogs/<navn>/i18n/, aggregert i src/i18n/index.tsx.
- Eksporter fra
src/dialogs/<navn>/index.ts og src/dialogs/index.ts.
- Verifiser teknisk â
get_errors pÄ alle nye filer + lint:tsc.
- Verifiser innholdsparitet â sammenlign v1 og v2 komponent for komponent: form, list og summary (se Innholdsverifisering under).
Dialogform-regler
Validering
- Bruk
useSifValidate('@sifSoknadForms.<xxxForm>') â scope fĂžlger @sifSoknadForms-prefiksen.
- Bruk
getDateValidator, getRequiredFieldValidator etc. fra @navikt/sif-validation.
- For
DateRangePicker: getDateValidator per felt + validate-prop pÄ gruppen for cross-field validering.
Datakonvertering
- Bruk
crypto.randomUUID() for nye elementer.
- Bruk aktiv locale fra
sifIntl.locale (via useSifSoknadFormsIntl()) nÄr displaydata bygges, f.eks. getCountryName(landkode, sifIntl.locale).
- Ikke hardkod
'nb' i mapper-funksjoner som bygger brukervendt tekst.
DatohÄndtering i dialoger
Dato-APIet er delt i to lag:
| Lag | Pakke | Funksjon | BruksomrÄde |
|---|
| Datepicker-parsing | @sif/rhf â datePickerUtils | parseDatePickerValue(value) | Konverterer brukerinput fra datepicker (ISO-streng eller norske datoformater) til Date | undefined |
| Generell ISO-konvertering | @navikt/sif-common-utils â dateUtils | dateToISODate(date), ISODateToDate(iso), isISODateString(value) | Konvertering mellom Date og ISODate-strenger |
Regler:
- I
formValuesToXxx()-funksjoner der verdier kommer fra datepicker-felter, bruk datePickerUtils.parseDatePickerValue(value) for Ă„ parse til Date.
- I
xxxToFormValues()-funksjoner der Date skal til formverdi, bruk dateUtils.dateToISODate(date) for Ă„ konvertere til ISO-streng.
- For typeguard/sjekk om en streng er gyldig ISO-dato, bruk
dateUtils.isISODateString(value).
new Date(...) er blokkert i portert runtime-kode for skjemaer med mindre bruker eksplisitt har godkjent det.
ISODateToDate kan brukes i tester og mock-data, men ikke som erstatning for parseDatePickerValue i skjemaflyt.
validationUtils.getDateFromDateString (fra @navikt/sif-validation) brukes fortsatt internt i validatorer â ikke bland den inn i dialog-mapping.
MiljĂžavhengigheter
- UnngÄ Ä lese app-miljÞ direkte via
@navikt/sif-common-env i packages/sif-soknad-forms uten at det er strengt nĂždvendig.
- Husk at dialoger i
sif-soknad-forms ogsÄ kjÞres i Storybook, hvor full appSettings ikke alltid finnes.
- Hvis en dialog trenger miljĂžstyrt oppfĂžrsel, foretrekk eksplisitt prop eller en liten lokal fallback fremfor Ă„ binde dialogen til app-env.
Dialog-shell
- Bruk Aksel
Dialog med Dialog.Popup, Dialog.Header, Dialog.Body, Dialog.Footer.
onOpenChange skal wrappe cancel: onOpenChange={(open) => { if (!open) onCancel(); }}.
- NÄr en dialog ikke trenger Ä bevare intern state mens den er lukket, la dialogkomponenten returnere
null nÄr isOpen === false slik at innholdet unmountes mellom Äpninger.
- Dette er spesielt nyttig for liste/dialog-mÞnstre der mange dialoginstanser ellers kan bli stÄende rendret i lukket tilstand.
- For RHF-baserte dialoger gir dette ogsÄ ferske
useForm({ defaultValues }) ved ny mount, og er ofte enklere enn manuell reset(...)-logikk.
- Behold dialogen mounted mens den er lukket bare nÄr det finnes en konkret grunn, for eksempel bevart lokal state, spesifikk fokus-/animasjonshÄndtering eller annen eksplisitt oppfÞrsel.
- Submit-knapp bruker
form={formId} for Ă„ koble til formen i body.
- Hvis samme formkonfig-props mÄ sendes gjennom flere lag (
XxxDialogForm â XxxDialog â XxxListAndDialog), eksporter en egen delt config-type fra form-filen og gjenbruk den i wrapperne.
- Foretrekk en eksplisitt delt type som
XxxDialogFormConfig fremfor Ä repetere feltene eller bygge wrapper-API pÄ Pick<Props, ...> fra hele form-komponenten.
Storybook-konvensjoner
NÄr du lager stories i packages/sif-soknad-forms:
- Ikke legg lokal
IntlProvider â intl er globalt satt opp i .storybook/preview.js via withIntl.
- Bruk
StoryFrame fra src/storybook/components/StoryFrame.tsx for layout (maxWidth, minHeight).
- Bruk
withRHFForm fra src/storybook/decorators/withRHFForm.tsx hvis storyen trenger RHF-kontekst, med parameters.rhf.defaultValues.
- Hold lokal state kun for det som demonstreres (
useState for liste eller Äpen dialog).
- Send inn sentrale props som pÄvirker dialoglogikken i storyen, ikke bare minimumsoppsett. Eksempler:
disallowedFĂždselsnumre, placeholders, hjelpetekst og andre domeneprops som styrer validering eller visning.
Story-eksempel
import { StoryFrame } from '../../storybook/components/StoryFrame';
function XxxListAndDialogStory({ items }: StoryProps) {
const [data, setData] = useState(items);
return <XxxListAndDialog items={data} onChange={setData} addButtonLabel="Legg til" />;
}
const meta = {
title: 'Dialogs/Xxx/ListAndDialog',
component: XxxListAndDialogStory,
decorators: [
(Story) => (
<StoryFrame maxWidth={720} minHeight={700}>
<Story />
</StoryFrame>
),
],
args: { items: exampleItems },
} satisfies Meta<typeof XxxListAndDialogStory>;
Innholdsverifisering (BLOKKERENDE)
Etter porting av en dialog mĂ„ alle renderbare komponenter verifiseres mot v1-originalen. Denne sjekken er blokkerende â dialogen regnes ikke som ferdig portert fĂžr den er bestĂ„tt.
Tekstparitet â v1 er autoritativ kilde
Alle brukersynlige tekster og valideringsmeldinger i v2 skal vĂŠre identiske med v1, inkludert:
- Labels, legends, descriptions og hjelpetekster
- Valideringsmeldinger (feilmeldinger) i
nb.ts og nn.ts
- Interpolasjonsparametere (f.eks.
{land}, {navn}, {dato}, {min}, {maks})
Unntaket er strukturendringer: Hvis v2 slĂ„r sammen felter, endrer feltrekkefĂžlge eller dropper felter av strukturelle Ă„rsaker, mĂ„ tekstene tilpasses â men avviket skal dokumenteres eksplisitt og begrunnes. Hvis du er usikker pĂ„ om noe er strukturendring, ta det opp med bruker.
Eksempel: v1 validerer erSammenMedBarnet med { land: getCountryName(...) } som interpolasjonsparameter. v2 mÄ sende samme parameter i extraValuesMapper og teksten mÄ inneholde {land}.
Dette gjelder alle komponenttyper som rendrer brukersynlig innhold:
- DialogForm â felter, labels, legends, hjelpetekst, betinget visning, validering
- List â label-rendering, tittel, formatering av verdier
- Summary â alle verdier, betingede blokker, formatering
FremgangsmÄte
For hver komponenttype som finnes i v1:
- Les v1-komponenten â Ă„pne
sif-common-forms-ds/src/forms/<xxx>/Xxx*.tsx og list opp alt innhold som rendres.
- Les v2-komponenten â Ă„pne
sif-soknad-forms/src/dialogs/<xxx>/Xxx*.tsx og list opp alt innhold som rendres.
- Sammenlign felt for felt â hvert felt, label, betinget blokk og formatert verdi i v1 skal ha en tilsvarende del i v2.
- Rapporter avvik â hvis noe mangler eller avviker, fiks det fĂžr oppgaven avsluttes.
Sjekkliste â Form
Sjekkliste â List
Sjekkliste â Summary
Typiske feil
- YesOrNo widened til string:
YesOrNo-felt i FormValues typet som string i stedet for YesOrNo. RHF stĂžtter enum-typer direkte â bruk YesOrNo-typen og unngĂ„ as YesOrNo-caster i konverteringsfunksjonene.
- Manglende felt i form: v1 har et felt som v2 mangler helt â ofte betingede felter som vises under spesifikke tilstander.
- Manglende verdier i list: v1 label-rendering viser flere verdier enn v2 (f.eks. dato, type, detaljer).
- Manglende ytre Ja/Nei-svar i summary: v1 viser bÄde spÞrsmÄlet (Ja/Nei) og detaljene, v2 hopper over spÞrsmÄlet og viser bare detaljene.
- Manglende betinget blokk: v1 har en blokk som vises under en spesifikk betingelse, v2 mangler den helt.
- Feil betingelse: v2 bruker en annen betingelse enn v1 for Ă„ vise en blokk.
- Invertert betingelse: v2 bruker
=== YES der v1 bruker === NO eller omvendt.
NÄr v2-typen avviker fra v1-API-typen
v1 bruker ofte en API-data-type (flat/nested DTO), mens v2 bruker en form-nÊr type med YesOrNo-felter. SÞrg for at alle verdier fra DTO-en som v1 rendrer, ogsÄ er tilgjengelig og rendret via v2-typen.
i18n-konvensjoner
- Prefiks:
@sifSoknadForms.<xxx>. for tekster, @sifSoknadForms.<xxxForm>.validation. for feilmeldinger.
nn.ts types mot nb.ts med Record<keyof typeof ..._nb, string>.
- Ikke oversett tekster automatisk â kopier nb som utgangspunkt i nn.
- Aggreger i
src/i18n/index.tsx sammen med andre dialogs meldinger.
Referanseeksempel â BostedUtland
XxxListAndDialog er ~30 linjer:
export const BostedUtlandListAndDialog = ({ minDate, maxDate, bosteder, addButtonLabel, onChange }: Props) => (
<ModalFormAndList
items={bosteder}
getItemId={(b) => b.id}
addButtonLabel={addButtonLabel}
onChange={onChange}
listRenderer={({ items, onEdit, onDelete }) => (
<BostedUtlandList bosteder={items} onEdit={onEdit} onDelete={onDelete} />
)}
dialogRenderer={({ item, allItems, isOpen, onSubmit, onCancel }) => (
<BostedUtlandFormDialog
minDate={minDate}
maxDate={maxDate}
bosted={item}
alleBosteder={allItems}
isOpen={isOpen}
onValidSubmit={onSubmit}
onCancel={onCancel}
/>
)}
/>
);
Dialoger som skal porteres
Fra packages/sif-common-forms-ds/src/forms/:
| v1-mappe | Porteringsstatus |
|---|
bosted-utland | Ferdig |
utenlandsopphold | Ikke startet |
tidsperiode | Ikke startet |
ferieuttak | Ikke startet |
opptjening-utland | Ikke startet |
enkeltdatoer | Ikke startet |
annet-barn | Ferdig |
utenlandsk-nĂŠring | Ikke startet |
fravĂŠr (perioder + dager) | Ikke startet |
fosterbarn | Ikke startet |