diff --git a/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-card-largecardv2--illustration-desktop-linux-desktop-linux.png b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-card-largecardv2--illustration-desktop-linux-desktop-linux.png index 7d809149f..1ac0d1181 100644 Binary files a/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-card-largecardv2--illustration-desktop-linux-desktop-linux.png and b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-card-largecardv2--illustration-desktop-linux-desktop-linux.png differ diff --git a/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-headers-themedpageheader--default-desktop-linux-desktop-linux.png b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-headers-themedpageheader--default-desktop-linux-desktop-linux.png index 8e0341773..6020589ff 100644 Binary files a/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-headers-themedpageheader--default-desktop-linux-desktop-linux.png and b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-headers-themedpageheader--default-desktop-linux-desktop-linux.png differ diff --git a/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-headers-themedpageheader--overview-page-desktop-linux-desktop-linux.png b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-headers-themedpageheader--overview-page-desktop-linux-desktop-linux.png index 993c0f033..25202cb0c 100644 Binary files a/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-headers-themedpageheader--overview-page-desktop-linux-desktop-linux.png and b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-headers-themedpageheader--overview-page-desktop-linux-desktop-linux.png differ diff --git a/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-headers-themedpageheader--with-time-stamp-desktop-linux-desktop-linux.png b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-headers-themedpageheader--with-time-stamp-desktop-linux-desktop-linux.png index 8e0341773..6020589ff 100644 Binary files a/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-headers-themedpageheader--with-time-stamp-desktop-linux-desktop-linux.png and b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-common-headers-themedpageheader--with-time-stamp-desktop-linux-desktop-linux.png differ diff --git a/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-pages-contact-step-page-contactsteppage--default-desktop-linux-desktop-linux.png b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-pages-contact-step-page-contactsteppage--default-desktop-linux-desktop-linux.png new file mode 100644 index 000000000..df8c86d45 Binary files /dev/null and b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-pages-contact-step-page-contactsteppage--default-desktop-linux-desktop-linux.png differ diff --git a/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-pages-contact-step-page-contactsteppage--editor-view-desktop-linux-desktop-linux.png b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-pages-contact-step-page-contactsteppage--editor-view-desktop-linux-desktop-linux.png new file mode 100644 index 000000000..df8c86d45 Binary files /dev/null and b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-pages-contact-step-page-contactsteppage--editor-view-desktop-linux-desktop-linux.png differ diff --git a/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-pages-contact-step-page-contactsteppage--english-desktop-linux-desktop-linux.png b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-pages-contact-step-page-contactsteppage--english-desktop-linux-desktop-linux.png new file mode 100644 index 000000000..df8c86d45 Binary files /dev/null and b/packages/nextjs/playwright/screenshot.spec.ts-snapshots/components-pages-contact-step-page-contactsteppage--english-desktop-linux-desktop-linux.png differ diff --git a/packages/nextjs/src/components/ContentMapper.tsx b/packages/nextjs/src/components/ContentMapper.tsx index 3b9b05875..31065a4f7 100644 --- a/packages/nextjs/src/components/ContentMapper.tsx +++ b/packages/nextjs/src/components/ContentMapper.tsx @@ -30,6 +30,7 @@ import { FormIntermediateStepPage } from './pages/form-intermediate-step-page/Fo import { CalculatorPage } from './pages/calculator-page/CalculatorPage'; import { AlertInContextPage } from './pages/alert-in-context-page/AlertInContextPage'; import { OfficePage } from './pages/office-page/OfficePage'; +import { ContactStepPage } from './pages/contact-step-page/ContactStepPage'; const contentToReactComponent: { [key in ContentType]?: React.FunctionComponent>; @@ -61,6 +62,7 @@ const contentToReactComponent: { [ContentType.Calculator]: CalculatorPage, [ContentType.UserTestsConfig]: UserTestsConfigPreviewPage, [ContentType.AlertInContext]: AlertInContextPage, + [ContentType.ContactStepPage]: ContactStepPage, [ContentType.AreaPage]: DynamicPage, [ContentType.FrontPage]: DynamicPage, diff --git a/packages/nextjs/src/components/_common/headers/headerWithParent/HeaderWithParent.module.scss b/packages/nextjs/src/components/_common/headers/headerWithParent/HeaderWithParent.module.scss new file mode 100644 index 000000000..78be26eff --- /dev/null +++ b/packages/nextjs/src/components/_common/headers/headerWithParent/HeaderWithParent.module.scss @@ -0,0 +1,18 @@ +.headerContainer { + display: flex; + flex-direction: column; + justify-content: center; + + .textAboveTitle { + color: var(--a-text-subtle); + font-weight: 400; + font-size: 20px; + line-height: 28px; + letter-spacing: -0.1%; + } + + .header { + font-size: 40px; + line-height: 52px; + } +} diff --git a/packages/nextjs/src/components/_common/headers/headerWithParent/HeaderWithParent.tsx b/packages/nextjs/src/components/_common/headers/headerWithParent/HeaderWithParent.tsx new file mode 100644 index 000000000..e6718c371 --- /dev/null +++ b/packages/nextjs/src/components/_common/headers/headerWithParent/HeaderWithParent.tsx @@ -0,0 +1,28 @@ +import { BodyShort, Heading } from '@navikt/ds-react'; +import { ProductDataMixin } from 'types/component-props/_mixins'; +import { ContentProps } from 'types/content-props/_content-common'; +import { classNames } from 'utils/classnames'; +import style from './HeaderWithParent.module.scss'; + +type Props = { + contentProps: Pick & { + data: Pick; + }; + textAboveTitle?: string; + className?: string; +}; + +export const HeaderWithParent = ({ contentProps, textAboveTitle, className }: Props) => { + const { data } = contentProps; + + return ( +
+ + {textAboveTitle} + + + {data.title} + +
+ ); +}; diff --git a/packages/nextjs/src/components/_common/illustration/Illustration.module.scss b/packages/nextjs/src/components/_common/illustration/Illustration.module.scss index 461c4abbf..d7330d36f 100644 --- a/packages/nextjs/src/components/_common/illustration/Illustration.module.scss +++ b/packages/nextjs/src/components/_common/illustration/Illustration.module.scss @@ -3,6 +3,10 @@ display: flex; justify-content: center; align-items: center; + width: 100%; + height: 100%; + min-width: 4rem; + min-height: 4rem; &:empty { display: none; diff --git a/packages/nextjs/src/components/_common/lenkepanel-liste/LenkepanelListe.module.scss b/packages/nextjs/src/components/_common/lenkepanel-liste/LenkepanelListe.module.scss deleted file mode 100644 index 6f903a0be..000000000 --- a/packages/nextjs/src/components/_common/lenkepanel-liste/LenkepanelListe.module.scss +++ /dev/null @@ -1,35 +0,0 @@ -@use 'common' as common; - -.lenkepanelListe { - width: 100%; - margin-bottom: 2rem; - - .tittel { - text-align: center; - margin-bottom: 2rem; - } - - .ingress { - text-align: center; - margin-top: 1rem; - } - - .items { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - margin-top: 1rem; - width: 100%; - } - - .item { - text-align: start; - margin-bottom: 1rem; - @media #{common.$mq-screen-mobile} { - width: 100%; - } - @media #{common.$mq-screen-tablet-and-desktop} { - width: calc(50% - 0.5rem); - } - } -} diff --git a/packages/nextjs/src/components/_common/lenkepanel-liste/LenkepanelListe.tsx b/packages/nextjs/src/components/_common/lenkepanel-liste/LenkepanelListe.tsx deleted file mode 100644 index b8fe56be8..000000000 --- a/packages/nextjs/src/components/_common/lenkepanel-liste/LenkepanelListe.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import { BodyLong, Heading, Ingress } from '@navikt/ds-react'; -import { LinkPanel } from 'types/link-panel'; -import LenkepanelNavNo from 'components/_common/lenkepanel-legacy/LenkepanelNavNo'; -import { getUrlFromContent } from 'utils/links-from-content'; - -import style from './LenkepanelListe.module.scss'; - -type Props = { - title?: string; - ingress?: string; - items?: LinkPanel[]; -}; - -export const LenkepanelListe = ({ title, ingress, items }: Props) => { - return ( -
- {title && ( -
- - {title} - -
- )} - {ingress && {ingress}} - {items && ( -
- {items.map((item) => { - const url = item.url?.ref - ? getUrlFromContent(item.url.ref) - : item.url?.text; - - if (!url) { - return null; - } - - return ( - - {item.ingress && {item.ingress}} - - ); - })} -
- )} -
- ); -}; diff --git a/packages/nextjs/src/components/_common/metatags/HeadWithMetatags.tsx b/packages/nextjs/src/components/_common/metatags/HeadWithMetatags.tsx index 6c516de9b..b5c0451df 100644 --- a/packages/nextjs/src/components/_common/metatags/HeadWithMetatags.tsx +++ b/packages/nextjs/src/components/_common/metatags/HeadWithMetatags.tsx @@ -23,7 +23,11 @@ const getDescription = (content: ContentProps) => { return description.slice(0, DESCRIPTION_MAX_LENGTH); }; -const contentTypesWithNoIndex = new Set([ContentType.Error, ContentType.FormIntermediateStepPage]); +const contentTypesWithNoIndex = new Set([ + ContentType.Error, + ContentType.FormIntermediateStepPage, + ContentType.ContactStepPage, +]); const isNoIndex = (content: ContentProps) => content.isPagePreview || contentTypesWithNoIndex.has(content.type) || content.data?.noindex; diff --git a/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.module.scss b/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.module.scss new file mode 100644 index 000000000..61c44c1e0 --- /dev/null +++ b/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.module.scss @@ -0,0 +1,63 @@ +.contactStepPage { + padding-bottom: 1rem; + column-gap: 1.75rem; + margin: 1.25rem auto 2.5rem; + display: grid; + grid-template-columns: 1fr 40rem 1fr; + grid-template-areas: + 'pictogram header .' + '. content .' + '. backLink .'; + + @media only screen and (max-width: 1024px) { + grid-template-columns: none; + grid-template-areas: + '. pictogram .' + '. header .' + '. content .' + '. backLink .'; + } + .pictogram { + grid-area: pictogram; + display: block; + justify-self: end; + width: 4.5rem; + height: auto; + position: relative; + align-self: flex-start; + + @media only screen and (max-width: 1024px) { + width: 4rem; + justify-self: start; + } + } + .header { + grid-area: header; + } + + .content { + grid-area: content; + + .linkPanels { + display: flex; + flex-direction: column; + gap: 0.5rem; + list-style-type: none; + padding: 0; + + .linkPanel { + border-radius: 0.5rem; + border: 1px solid var(--a-border-subtle); + + & :global(.navds-link-panel__title) { + color: var(--default-action-color); + font-size: 1.25rem; + } + } + } + } + + .backLink { + grid-area: backLink; + } +} diff --git a/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.stories.tsx b/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.stories.tsx new file mode 100644 index 000000000..418408625 --- /dev/null +++ b/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.stories.tsx @@ -0,0 +1,149 @@ +import React from 'react'; +import { Provider } from 'react-redux'; +import type { Decorator, Meta, StoryObj } from '@storybook/react'; +import { mockStore } from 'store/store'; +import { ContactStepPage } from './ContactStepPage'; + +const withStore: Decorator = (Story) => ( + + + +); + +const meta = { + component: ContactStepPage, + decorators: [withStore], + //Ikke en komplett side. Ikke sikkert vi skal sette opp hele sider i storybook. + //Bør kanskje finne en bedre måte å gjøre dette på om vi skal prøve å ha hele sider i storybook. + //Sjekk ut: https://storybook.js.org/docs/writing-stories/build-pages-with-storybook + args: { + _id: '4b4f44c8-d82c-4046-80e8-2b49ff9feca3', + _name: 'skrivtiloss', + _path: '/www.nav.no/tania/skrivtiloss', + creator: 'user:system:su', + modifier: 'user:system:su', + createdTime: '2025-03-06T09:12:39.492046Z', + modifiedTime: '2025-03-13T11:26:18.994311Z', + owner: 'user:system:su', + // @ts-ignore + type: 'no.nav.navno:contact-step-page', + displayName: 'Skriv til oss', + hasChildren: true, + language: 'no', + valid: true, + childOrder: 'modifiedtime DESC', + data: { + audience: { + person: {}, + // @ts-ignore + _selected: 'person', + }, + steps: { + nextStep: { + _selected: 'internal', + internal: { + internalContent: '5f679435-e468-4196-aa4a-5c86be9438a0', + }, + }, + label: 'Test', + }, + customPath: '/test-kontaktside', + title: 'Skriv til oss', + textAboveTitle: 'Kontakt oss', + ingress: 'yrdyr', + html: '

Hva vil du gjøre?

\n', + target: '4b4f44c8-d82c-4046-80e8-2b49ff9feca3', + text: 'Skriv til oss om noe annet', + link: { + internal: { + // @ts-ignore + target: '419e1783-25af-4513-aebb-2cce0b6bd7da', + text: 'Kontakt oss på en annen måte', + }, + _selected: 'internal', + }, + links: [ + { + label: 'Melde fra om endringer i saken din', + explanation: + 'Når du får støtte fra Nav, må du melde fra om endringer som kan ha betydning for saken din. Det kan for eksempel være endringer i inntekt, bosted, jobb- eller familiesituasjon, eller ferie og opphold i utlandet.', + link: { + internal: { + internalContent: { + // @ts-ignore + createdTime: '2025-03-13T11:18:33.082857Z', + displayName: '"Melde fra om endringer i saken din"', + language: 'no', + modifiedTime: '2025-03-13T11:23:51.173Z', + publish: { + first: '2025-03-13T11:24:01.903Z', + from: '2025-03-13T11:24:01.903Z', + }, + type: 'no.nav.navno:contact-step-page', + _id: 'ad33a6f6-403b-4ba4-bd7e-04cc25a473f2', + _path: '/privatperson/meldeifraomendringer', + }, + }, + _selected: 'internal', + }, + }, + { + label: 'Få svar på spørsmål', + explanation: + 'Du kan blant annet stille oss spørsmål om saken din, hvilke rettigheter du har, hvilke regler som gjelder og hvordan du går frem for å søke.', + link: { + internal: { + internalContent: { + // @ts-ignore + createdTime: '2025-03-13T11:18:33.082857Z', + displayName: 'Skriv til oss', + language: 'no', + modifiedTime: '2025-03-13T11:23:51.173Z', + publish: { + first: '2025-03-13T11:24:01.903Z', + from: '2025-03-13T11:24:01.903Z', + }, + type: 'no.nav.navno:contact-step-page', + _id: '"4b4f44c8-d82c-4046-80e8-2b49ff9feca3"', + _path: '/privatperson/velgpengestotteellertjeneste', + }, + }, + _selected: 'internal', + }, + }, + ], + }, + x: { + 'no-nav-navno': { + redirectToLayer: {}, + virtualParent: {}, + previewOnly: {}, + searchOrder: {}, + }, + }, + // @ts-ignore + page: {}, + attachments: {}, + publish: { + from: '2025-03-11T09:40:07.350Z', + first: '2025-03-11T09:40:07.350Z', + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const English: Story = { + args: { + language: 'en', + }, +}; + +export const EditorView: Story = { + args: { + editorView: 'edit', + }, +}; diff --git a/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.tsx b/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.tsx new file mode 100644 index 000000000..16cf81c8e --- /dev/null +++ b/packages/nextjs/src/components/pages/contact-step-page/ContactStepPage.tsx @@ -0,0 +1,82 @@ +import { BodyShort, Heading, LinkPanel } from '@navikt/ds-react'; +import { HeaderWithParent } from 'components/_common/headers/headerWithParent/HeaderWithParent'; +import { ParsedHtml } from 'components/_common/parsedHtml/ParsedHtml'; +import { ContentType, ContentCommonProps } from 'types/content-props/_content-common'; +import { IllustrationStatic } from 'components/_common/illustration/static/IllustrationStatic'; +import { PictogramsProps } from 'types/content-props/pictograms'; +import { LenkeBase } from 'components/_common/lenke/lenkeBase/LenkeBase'; +import { LenkeInline } from 'components/_common/lenke/lenkeInline/LenkeInline'; +import { InternalLinkMixin } from 'types/component-props/_mixins'; +import style from './ContactStepPage.module.scss'; + +export type ContactStepPageProps = ContentCommonProps & { + type: ContentType.ContactStepPage; + data: { + title: string; + illustration: PictogramsProps; + textAboveTitle?: string; + html: string; + linkPanelsHeading: string; + linkPanelsSubHeading: string; + linkPanels: (InternalLinkMixin & { ingress?: string })[]; + backLink: InternalLinkMixin; + }; +}; + +export const ContactStepPage = ({ data }: ContactStepPageProps) => { + const { + title, + illustration, + textAboveTitle, + html, + linkPanelsHeading, + linkPanelsSubHeading, + linkPanels, + backLink, + } = data; + + return ( +
+ + +
+ + + {linkPanelsHeading && ( + + {linkPanelsHeading} + + )} + {linkPanelsSubHeading && {linkPanelsSubHeading}} + +
    + {linkPanels.map((linkPanel, index) => ( +
  • + + + {linkPanel.text ?? linkPanel.target.displayName} + + {linkPanel.ingress && ( + + {linkPanel.ingress} + + )} + +
  • + ))} +
+
+ + {backLink.text ?? backLink.target.displayName} + +
+ ); +}; diff --git a/packages/nextjs/src/types/content-props/_content-common.ts b/packages/nextjs/src/types/content-props/_content-common.ts index 837d9a114..98bffe8ef 100644 --- a/packages/nextjs/src/types/content-props/_content-common.ts +++ b/packages/nextjs/src/types/content-props/_content-common.ts @@ -9,6 +9,7 @@ import { TemplateProps } from 'types/content-props/template-props'; import { SiteProps } from 'types/content-props/site-props'; import { FormsOverviewProps } from 'types/content-props/forms-overview'; import { OverviewPageProps } from 'types/content-props/overview-props'; +import { ContactStepPageProps } from 'components/pages/contact-step-page/ContactStepPage'; import { ExternalLinkProps } from './external-link-props'; import { InternalLinkProps } from './internal-link-props'; import { ContentListProps } from './content-list-props'; @@ -93,6 +94,7 @@ export enum ContentType { AlertInContext = 'no.nav.navno:alert-in-context', OfficePage = 'no.nav.navno:office-page', FallbackPage = 'no.nav.navno:fallback-page', + ContactStepPage = 'no.nav.navno:contact-step-page', } export const innholdsTypeMap: Record = { @@ -137,6 +139,7 @@ export const innholdsTypeMap: Record = { [ContentType.Video]: 'Qbrick Video', [ContentType.AlertInContext]: 'Varsel i kontekst', [ContentType.OfficePage]: 'Kontorside (gammel)', + [ContentType.ContactStepPage]: 'Mellomsteg for kontaktside', [ContentType.Error]: `Ugyldig type: [${ContentType.Error}]`, [ContentType.Site]: `Ugyldig type: [${ContentType.Site}]`, @@ -209,6 +212,7 @@ type SpecificContentProps = | SiteProps | TemplateProps | ContentListProps + | ContactStepPageProps | ErrorProps | ExternalLinkProps | InternalLinkProps diff --git a/packages/nextjs/src/utils/appearance.ts b/packages/nextjs/src/utils/appearance.ts index fdbdce8bd..45c75ec09 100644 --- a/packages/nextjs/src/utils/appearance.ts +++ b/packages/nextjs/src/utils/appearance.ts @@ -11,6 +11,7 @@ const contentTypeWithWhiteBackground: ReadonlySet = new Set([ ContentType.ThemedArticlePage, ContentType.GenericPage, ContentType.SituationPage, + ContentType.ContactStepPage, ]); const contentTypesWithWhiteHeader: ReadonlySet = new Set([