@kystverket/styrbord-consent 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +145 -0
  2. package/dist/src/components/ConsentBanner/ConsentBanner.d.ts +10 -0
  3. package/dist/src/components/ConsentBanner/ConsentBanner.stories.d.ts +16 -0
  4. package/dist/src/components/ConsentPreferencesDialog/ConsentPreferencesDialog.d.ts +13 -0
  5. package/dist/src/components/ConsentPreferencesDialog/ConsentPreferencesDialog.stories.d.ts +13 -0
  6. package/dist/src/components/ConsentProvider/ConsentProvider.d.ts +9 -0
  7. package/dist/src/components/ConsentProvider/ConsentProvider.types.d.ts +13 -0
  8. package/dist/src/components/ConsentSettingsButton/ConsentSettingsButton.d.ts +13 -0
  9. package/dist/src/components/ConsentSettingsButton/ConsentSettingsButton.stories.d.ts +11 -0
  10. package/dist/src/components/CookieConsent/CookieConsent.d.ts +18 -0
  11. package/dist/src/components/CookieConsent/CookieConsent.stories.d.ts +25 -0
  12. package/dist/src/components/shared/Button/Button.d.ts +22 -0
  13. package/dist/src/components/shared/Switch/Switch.d.ts +15 -0
  14. package/dist/src/hooks/useConsent.d.ts +26 -0
  15. package/dist/src/hooks/useStoreValue.d.ts +10 -0
  16. package/dist/src/main.d.ts +19 -0
  17. package/dist/src/utility/consent.types.d.ts +78 -0
  18. package/dist/src/utility/consentContext.d.ts +23 -0
  19. package/dist/src/utility/consentStore.d.ts +40 -0
  20. package/dist/src/utility/dialogRegistry.d.ts +26 -0
  21. package/dist/src/utility/services.d.ts +112 -0
  22. package/dist/src/utility/translations.d.ts +72 -0
  23. package/dist/storybook/ConsentDemo.d.ts +51 -0
  24. package/dist/storybook/styrbordDecorator.d.ts +14 -0
  25. package/dist/style.css +1 -0
  26. package/dist/style.js +447 -0
  27. package/dist/style.umd.cjs +12 -0
  28. package/package.json +54 -0
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # Styrbord Consent
2
+
3
+ Samtykke for informasjonskapsler (cookie-banner, innstillingsdialog og tilhørende logikk) for
4
+ Kystverkets interne og eksterne applikasjoner.
5
+
6
+ Pakken står på egne bein: den bruker ikke `@kystverket/styrbord`, bare designtokenene, slik at
7
+ også applikasjoner som ikke kan ta inn hele designsystemet får et samtykkebanner som ser ut som
8
+ resten av Kystverket. Kontrollene den trenger — knapp, bryter, dialog — ligger i pakken og er
9
+ bygget på de samme tokenene.
10
+
11
+ Bygger på [c15t](https://c15t.com) i offline-modus: ingen backend, ingen nettverkskall, alt
12
+ lagres i nettleseren.
13
+
14
+ ## Versjonering
15
+
16
+ - Prosjektet følger semantisk versjonering (`major.minor.patch`).
17
+ - Major inkrementeres ved knekkende endringer.
18
+ - Minor inkrementeres ved ny funksjonalitet bakoverkompatibelt.
19
+ - Patch inkrementeres ved feilrettinger og mindre forbedringer.
20
+
21
+ ## Bruk
22
+
23
+ Importer CSS globalt én gang. Designtokenene følger med i denne fila, så det er den eneste
24
+ importen som trengs — også i en applikasjon som ikke bruker Styrbord ellers.
25
+
26
+ ```js
27
+ import '@kystverket/styrbord-consent/style.css';
28
+ ```
29
+
30
+ > Bruker applikasjonen allerede `@kystverket/styrbord`, laster den tokenene to ganger. Verdiene
31
+ > er de samme, så det koster noen kilobyte og ingenting annet.
32
+
33
+ Legg `ConsentProvider` rundt applikasjonen og `CookieConsent` inni. Sistnevnte gir banner,
34
+ innstillingsdialog og den flytende knappen som åpner innstillingene igjen.
35
+
36
+ > Plasserer du flatene hver for seg i stedet, må `ConsentPreferencesDialog` alltid være med.
37
+ > Både banneret og innstillingsknappen skjuler seg selv når de åpner innstillingene, så uten
38
+ > dialogen forsvinner flaten uten at noe kommer i stedet. Pakken advarer i konsollet om den
39
+ > oppdager det.
40
+
41
+ ```tsx
42
+ import { ConsentProvider, CookieConsent, hotjarService, plausibleService } from '@kystverket/styrbord-consent';
43
+
44
+ <ConsentProvider
45
+ language="nb-NO"
46
+ cookieDomain=".kystverket.no"
47
+ services={[plausibleService({ domain: 'kystverket.no' }), hotjarService({ siteId: 1234567 })]}
48
+ >
49
+ {children}
50
+ <CookieConsent />
51
+ </ConsentProvider>;
52
+ ```
53
+
54
+ Les samtykke i egen kode med `useConsent`:
55
+
56
+ ```tsx
57
+ const { hasConsent, showPreferences } = useConsent();
58
+
59
+ if (hasConsent('measurement')) {
60
+ // ...
61
+ }
62
+ ```
63
+
64
+ ### Felles samtykke på tvers av subdomener
65
+
66
+ `cookieDomain` er mekanismen som gjør at ett svar gjelder flere applikasjoner: settes den til
67
+ `.kystverket.no`, deler alle tjenester under domenet den samme informasjonskapselen, og
68
+ brukeren slipper å ta stilling til det samme flere ganger.
69
+
70
+ Appene må da bruke samme `storageKey` (standard `kystverket_consent`) og være enige om hvilken
71
+ kategori hver tjeneste hører til — bruk `services.ts` framfor å definere tjenestene på nytt i
72
+ hver app.
73
+
74
+ La `cookieDomain` stå udefinert lokalt; da settes kapselen på gjeldende vertsnavn.
75
+
76
+ > Send alltid inn `cookieDomain` som en prop utenfra. Biblioteket leser aldri miljøvariabler
77
+ > selv, fordi rammeverk som Next.js og Vite baker dem inn på byggetidspunktet — en verdi lest
78
+ > inne i biblioteket ville blitt låst til byggemiljøet.
79
+
80
+ ### Tjenester
81
+
82
+ `plausibleService`, `hotjarService`, `postHogService` og `consentCookieService` er ferdige
83
+ oppsett med riktig kategori og kapselliste. Andre tjenester defineres som vanlige
84
+ `ConsentService`-objekter.
85
+
86
+ Plausible lastes uten samtykke (`alwaysLoad`), fordi det verken setter informasjonskapsler
87
+ eller lagrer noe som kan knyttes til enheten. `plausibleService({ domain, requireConsent: true })`
88
+ legger det bak statistikk-kategorien i stedet.
89
+
90
+ PostHog ligger bak statistikk, fordi det alltid teller sidevisninger og klikk:
91
+
92
+ ```tsx
93
+ postHogService({
94
+ apiKey: 'phc_...',
95
+ sessionReplay: true, // sesjonsopptak og varmekart
96
+ capturePageview: false, // appen teller sidevisninger selv ved ruteendring
97
+ });
98
+ ```
99
+
100
+ Med `sessionReplay` gjør PostHog i tillegg det samme som Hotjar, og det hører hjemme under
101
+ brukeropplevelse. Siden en tjeneste bare kan stå i én kategori i dialogen, løses det i kjøretid:
102
+ skriptet starter med opptak avslått og slår det på når brukeren gir brukeropplevelse — også uten
103
+ at siden lastes på nytt. Varmekart settes ved oppstart og er først med ved neste sidelast.
104
+
105
+ API-nøkkelen er offentlig og hører hjemme i klientkoden, men send den likevel inn som en prop
106
+ framfor å lese den i biblioteket — se avsnittet om `cookieDomain` over. CSP må åpne for både
107
+ `eu.i.posthog.com` og `eu-assets.i.posthog.com`.
108
+
109
+ ### Kategorier
110
+
111
+ Kategoriene er c15t sitt faste vokabular: `necessary`, `functionality`, `experience`,
112
+ `measurement`, `marketing`. Det kan ikke utvides. Etikettene er våre egne, så det er usynlig
113
+ for brukeren — og et fast vokabular er nettopp det som gjør at flere apper kan lese den samme
114
+ informasjonskapselen og tolke den likt.
115
+
116
+ ### Tekster
117
+
118
+ Bokmål, nynorsk og engelsk følger med. Overstyr enkelttekster med `translations`-propen:
119
+
120
+ ```tsx
121
+ <ConsentProvider translations={{ banner: { heading: 'Egen overskrift' } }} …>
122
+ ```
123
+
124
+ Tekstene ligger i biblioteket framfor i applikasjonens i18n-oppsett, slik at komponentene
125
+ fungerer uavhengig av om appen bruker i18next, `@kystverket/sprak-react` eller ingenting.
126
+
127
+ ### Utseende
128
+
129
+ Alt av farger, avstander, typografi og skygger leses fra `@kystverket/styrbord-tokens`. Flatene
130
+ setter selv `data-color` på rotelementet sitt, slik at de ser like ut uansett hva applikasjonen
131
+ rundt gjør.
132
+
133
+ Skriften er `--ds-font-family` (Museo Sans) med en systemfont som reserve. Laster applikasjonen
134
+ webfonten, brukes den; ellers faller teksten pent tilbake.
135
+
136
+ Mørk modus følger `data-color-scheme` på et element lenger opp, på samme måte som i Styrbord.
137
+
138
+ ## Avhengigheter
139
+
140
+ Følgende peer dependencies må være tilgjengelige i applikasjonen:
141
+
142
+ - `react` (18.2 eller nyere)
143
+ - `react-dom` (18.2 eller nyere)
144
+
145
+ `c15t` og `@kystverket/styrbord-tokens` følger med som vanlige avhengigheter.
@@ -0,0 +1,10 @@
1
+ import type { ReactElement } from 'react';
2
+ /**
3
+ * Samtykkebanneret som vises til brukeren har tatt et valg.
4
+ *
5
+ * Bevisst ikke en modal: den låser ikke fokus og blokkerer ikke siden. Å sperre innholdet til
6
+ * brukeren har svart presser fram et samtykke, og et framtvunget samtykke er ikke gyldig etter
7
+ * GDPR. De tre valgene er likestilte — «Kun nødvendige» skal være like lett å treffe som
8
+ * «Godta alle».
9
+ */
10
+ export declare function ConsentBanner(): ReactElement | null;
@@ -0,0 +1,16 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { type ConsentStoryArgs } from '../../../storybook/ConsentDemo';
3
+ declare const meta: Meta<ConsentStoryArgs>;
4
+ export default meta;
5
+ type Story = StoryObj<ConsentStoryArgs>;
6
+ /**
7
+ * Banneret er ikke en modal: det låser verken fokus eller siden bak seg. Et samtykke som er
8
+ * framtvunget ved å sperre innholdet er ikke gyldig etter GDPR, og de tre valgene er derfor
9
+ * også likestilte visuelt.
10
+ *
11
+ * `ConsentPreferencesDialog` står mountet ved siden av, som den må gjøre i en applikasjon også:
12
+ * «Velg selv» setter `activeUI` til `dialog`, og da skjuler banneret seg selv. Er ikke dialogen
13
+ * der til å ta over, forsvinner banneret uten at noe kommer i stedet, og brukeren sitter igjen
14
+ * uten vei videre.
15
+ */
16
+ export declare const Default: Story;
@@ -0,0 +1,13 @@
1
+ import { type ReactElement } from 'react';
2
+ /**
3
+ * Innstillingsdialogen, der brukeren slår kategorier av og på enkeltvis.
4
+ *
5
+ * Kategoriene utledes av tjenestene applikasjonen faktisk laster — vi viser ikke valg som ikke
6
+ * styrer noe. Tjenester som går uten samtykke får sin egen «alltid på»-seksjon, slik at de er
7
+ * synlige selv om de ikke kan slås av; ellers ville en kapselfri tjeneste som Plausible vært
8
+ * usynlig for brukeren.
9
+ *
10
+ * Bygget på `<dialog>` direkte. `showModal()` gir fokusfelle, Escape og backdrop gratis, som er
11
+ * hele grunnen til at vi ikke trenger et dialogbibliotek for å klare oss uten Styrbord.
12
+ */
13
+ export declare function ConsentPreferencesDialog(): ReactElement | null;
@@ -0,0 +1,13 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { type ConsentStoryArgs } from '../../../storybook/ConsentDemo';
3
+ declare const meta: Meta<ConsentStoryArgs>;
4
+ export default meta;
5
+ type Story = StoryObj<ConsentStoryArgs>;
6
+ /**
7
+ * Dialogen åpnet med én gang.
8
+ *
9
+ * Kategoriene og kapseltabellene er utledet av tjenestelista — det er ingen fast liste i
10
+ * komponenten. Plausible havner i «alltid på»-seksjonen fordi den går uten samtykke, mens
11
+ * Hotjar og PostHog får hver sin bryter.
12
+ */
13
+ export declare const Default: Story;
@@ -0,0 +1,9 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { ConsentProviderProps } from './ConsentProvider.types';
3
+ /**
4
+ * Setter opp samtykkelageret og gjør det tilgjengelig for komponentene under.
5
+ *
6
+ * Legg `<CookieConsent />` inni for å få banner, dialog og innstillingsknapp, eller bygg ditt
7
+ * eget grensesnitt med `useConsent`.
8
+ */
9
+ export declare function ConsentProvider({ children, language, translations: translationOverrides, ...config }: Readonly<ConsentProviderProps>): ReactElement;
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ConsentConfig } from '../../utility/consent.types';
3
+ import type { ConsentTranslations } from '../../utility/translations';
4
+ export interface ConsentProviderProps extends ConsentConfig {
5
+ children: ReactNode;
6
+ /** Språkkode, f.eks. `nb-NO`. Styrer standardtekstene. Ukjente språk faller til bokmål. */
7
+ language?: string;
8
+ /**
9
+ * Overstyrer enkelttekster. Utelatte felter beholder standardteksten for språket, så det
10
+ * holder å sende inn det man faktisk vil endre.
11
+ */
12
+ translations?: Partial<ConsentTranslations>;
13
+ }
@@ -0,0 +1,13 @@
1
+ import type { ReactElement } from 'react';
2
+ /**
3
+ * Flytende knapp nederst til høyre som åpner samtykkeinnstillingene igjen.
4
+ *
5
+ * Å trekke tilbake et samtykke skal være like enkelt som å gi det, så inngangen må være
6
+ * tilgjengelig fra hvor som helst på siden — ikke bare nederst i bunnteksten, der den krever
7
+ * at brukeren scroller helt ned for å finne den.
8
+ *
9
+ * Vises ikke mens banneret er oppe: banneret har allerede en «velg selv»-knapp, og to knapper
10
+ * som gjør det samme i samme hjørne er bare forvirrende. Vises heller ikke når applikasjonen
11
+ * ikke har noen tjenester som avhenger av samtykke, for da er det ingenting å administrere.
12
+ */
13
+ export declare function ConsentSettingsButton(): ReactElement | null;
@@ -0,0 +1,11 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { type ConsentStoryArgs } from '../../../storybook/ConsentDemo';
3
+ declare const meta: Meta<ConsentStoryArgs>;
4
+ export default meta;
5
+ type Story = StoryObj<ConsentStoryArgs>;
6
+ /**
7
+ * Knappen slik den ser ut etter at brukeren har svart på banneret. Den vises ikke mens banneret
8
+ * står oppe — banneret har allerede en «Velg selv»-knapp, og to innganger til det samme i
9
+ * samme hjørne er bare forvirrende.
10
+ */
11
+ export declare const Default: Story;
@@ -0,0 +1,18 @@
1
+ import type { ReactElement } from 'react';
2
+ /**
3
+ * Samtykkeflatene samlet: banner, innstillingsdialog og knappen som åpner den igjen.
4
+ *
5
+ * Legges én gang inne i `<ConsentProvider>`, typisk nederst i applikasjonens layout.
6
+ */
7
+ export declare function CookieConsent(): ReactElement;
8
+ /**
9
+ * Samtykkeinnstillingene som en vanlig tekstlenke, til bruk i løpende tekst — for eksempel i
10
+ * en personvernerklæring som omtaler informasjonskapsler.
11
+ *
12
+ * Et alternativ til knappen i hjørnet for de som heller vil ha inngangen i bunnteksten eller
13
+ * midt i en tekst. Det er en `<button>`, ikke en `<a>`: den navigerer ingen steder, og en lenke
14
+ * uten mål er både uventet for skjermlesere og ubrukelig å åpne i ny fane.
15
+ */
16
+ export declare function ManageConsentLink({ className }: Readonly<{
17
+ className?: string;
18
+ }>): ReactElement;
@@ -0,0 +1,25 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { type ConsentStoryArgs } from '../../../storybook/ConsentDemo';
3
+ declare const meta: Meta<ConsentStoryArgs>;
4
+ export default meta;
5
+ type Story = StoryObj<ConsentStoryArgs>;
6
+ /**
7
+ * Alle tre flatene på én gang, slik en applikasjon normalt mounter dem: banneret vises til
8
+ * brukeren har svart, dialogen åpnes derfra, og innstillingsknappen står igjen i hjørnet
9
+ * etterpå.
10
+ *
11
+ * Trykk «Nullstill samtykke» for å få banneret tilbake — svaret lagres i en informasjonskapsel
12
+ * og forsvinner ellers ikke ved reload.
13
+ */
14
+ export declare const Default: Story;
15
+ /**
16
+ * Uten tjenester som avhenger av samtykke er det ingenting å spørre om, og verken banner eller
17
+ * innstillingsknapp vises. Resten av samtykkeløsningen mountes likevel, så `useConsent` virker
18
+ * som før.
19
+ */
20
+ export declare const UtenSporingstjenester: Story;
21
+ /**
22
+ * `ManageConsentLink` er alternativet til den flytende knappen: en vanlig tekstlenke som kan
23
+ * stå i bunnteksten eller midt i en personvernerklæring.
24
+ */
25
+ export declare const MedTekstlenke: Story;
@@ -0,0 +1,22 @@
1
+ import type { ButtonHTMLAttributes, ReactElement } from 'react';
2
+ /**
3
+ * Knappevariantene samtykkeflatene bruker. Navnene er de samme som i `@kystverket/styrbord`,
4
+ * ikke Designsystemets `primary`/`secondary`/`tertiary` — se `ButtonProps.variant` der for
5
+ * hvorfor Styrbord navngir formen framfor viktighetsnivået.
6
+ */
7
+ export type ConsentButtonVariant = 'filled' | 'outline' | 'ghost';
8
+ export interface ConsentButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
9
+ variant?: ConsentButtonVariant;
10
+ }
11
+ /**
12
+ * Knappen samtykkeflatene bruker.
13
+ *
14
+ * Pakken har med vilje ingen avhengighet til `@kystverket/styrbord`, slik at applikasjoner som
15
+ * ikke kan ta inn hele designsystemet likevel kan vise et samtykkebanner. Prisen er at de få
16
+ * kontrollene vi trenger må finnes her — bygget på de samme designtokenene, så resultatet ser
17
+ * likt ut ved siden av en Styrbord-app.
18
+ *
19
+ * Forutsetter at en forelder setter `data-color`, slik Designsystemet gjør: det er den som gir
20
+ * `--ds-color-base-*` en verdi. Samtykkeflatene setter den selv på rotelementet sitt.
21
+ */
22
+ export declare function Button({ variant, className, type, ...rest }: Readonly<ConsentButtonProps>): ReactElement;
@@ -0,0 +1,15 @@
1
+ import { type ChangeEvent, type ReactElement } from 'react';
2
+ export interface ConsentSwitchProps {
3
+ label: string;
4
+ description?: string;
5
+ checked: boolean;
6
+ onChange: (event: ChangeEvent<HTMLInputElement>) => void;
7
+ }
8
+ /**
9
+ * Av/på-bryteren for en samtykkekategori.
10
+ *
11
+ * En vanlig avkrysningsboks med `role="switch"` framfor et eget kontrollelement: da følger
12
+ * tastaturbruk, skjermlesere og nettleserens autofyll med av seg selv, og det eneste vi
13
+ * faktisk trenger å gjøre er å tegne den om.
14
+ */
15
+ export declare function Switch({ label, description, checked, onChange }: Readonly<ConsentSwitchProps>): ReactElement;
@@ -0,0 +1,26 @@
1
+ import type { ConsentState, ConsentUI } from '../utility/consent.types';
2
+ import type { ConsentStore } from '../utility/consentStore';
3
+ export interface UseConsentResult {
4
+ /**
5
+ * Sant når brukeren har samtykket til kategorien. Tar også sammensatte uttrykk, f.eks.
6
+ * `hasConsent({ or: ['measurement', 'marketing'] })`.
7
+ */
8
+ hasConsent: (category: Parameters<ReturnType<ConsentStore['getState']>['has']>[0]) => boolean;
9
+ /** Samtykkestatus per kategori. */
10
+ consents: ConsentState;
11
+ /** Sant inntil brukeren har tatt et valg. */
12
+ needsDecision: boolean;
13
+ /** Åpner innstillingsdialogen. */
14
+ showPreferences: () => void;
15
+ /** Lukker banner og dialog uten å lagre. */
16
+ close: () => void;
17
+ /** Hvilken flate som vises nå. */
18
+ activeUI: ConsentUI;
19
+ }
20
+ /**
21
+ * Leser og endrer samtykke.
22
+ *
23
+ * Dette er inngangen applikasjonskode skal bruke. Den sier ingenting om hvordan samtykket
24
+ * lagres, så implementasjonen under kan byttes ut uten at kallstedene må skrives om.
25
+ */
26
+ export declare function useConsent(): UseConsentResult;
@@ -0,0 +1,10 @@
1
+ import type { ConsentStore } from '../utility/consentStore';
2
+ /**
3
+ * Abonnerer på en utledet verdi fra samtykkelageret.
4
+ *
5
+ * `getServerSnapshot` må returnere den samme verdien hver gang den kalles — React bruker den
6
+ * både ved serverrendring og under hydrering, og en verdi som endrer seg gir enten en
7
+ * hydreringsfeil eller en uendelig renderløkke. Derfor låses den første verdien i et ref.
8
+ * Selve samtykkeflatene venter uansett på `mounted`, så verdien påvirker ikke markupen.
9
+ */
10
+ export declare function useStoreValue<T>(store: ConsentStore, selector: (state: ReturnType<ConsentStore['getState']>) => T): T;
@@ -0,0 +1,19 @@
1
+ import './css/index.css';
2
+ export { ConsentProvider } from './components/ConsentProvider/ConsentProvider';
3
+ export type { ConsentProviderProps } from './components/ConsentProvider/ConsentProvider.types';
4
+ export { CookieConsent, ManageConsentLink } from './components/CookieConsent/CookieConsent';
5
+ export { ConsentBanner } from './components/ConsentBanner/ConsentBanner';
6
+ export { ConsentPreferencesDialog } from './components/ConsentPreferencesDialog/ConsentPreferencesDialog';
7
+ export { ConsentSettingsButton } from './components/ConsentSettingsButton/ConsentSettingsButton';
8
+ export { useConsent } from './hooks/useConsent';
9
+ export type { UseConsentResult } from './hooks/useConsent';
10
+ export { useStoreValue } from './hooks/useStoreValue';
11
+ export { useConsentStore } from './utility/consentContext';
12
+ export type { ConsentContextValue } from './utility/consentContext';
13
+ export { clearServiceCookies, createConsentStore, getSelectableCategories, hasGatedServices, DEFAULT_EXPIRY_DAYS, DEFAULT_STORAGE_KEY, } from './utility/consentStore';
14
+ export type { ConsentStore } from './utility/consentStore';
15
+ export { consentCookieService, hotjarService, plausibleService, postHogService } from './utility/services';
16
+ export type { HotjarOptions, PlausibleOptions, PostHogOptions } from './utility/services';
17
+ export { consentTranslations, defaultConsentLanguage, formatCookieDuration, getConsentTranslations, } from './utility/translations';
18
+ export type { ConsentLanguage, ConsentTranslations } from './utility/translations';
19
+ export type { ConsentCategory, ConsentConfig, ConsentService, ConsentState, ConsentUI, ServiceCookie, } from './utility/consent.types';
@@ -0,0 +1,78 @@
1
+ import type { AllConsentNames, Script } from 'c15t';
2
+ /**
3
+ * Samtykkekategoriene c15t opererer med. Vokabularet er fast og kan ikke utvides — det er en
4
+ * fordel når flere applikasjoner deler den samme informasjonskapselen, siden de da er nødt
5
+ * til å tolke nøklene likt.
6
+ */
7
+ export type ConsentCategory = AllConsentNames;
8
+ /**
9
+ * En informasjonskapsel en tjeneste setter, slik den vises i samtykkedialogen.
10
+ *
11
+ * Brukeren har krav på å vite hva som lagres, av hvem og hvor lenge, før de tar stilling til
12
+ * samtykket. Ved å henge informasjonen på tjenesten den hører til, havner den i dialogen der
13
+ * valget faktisk tas.
14
+ */
15
+ export interface ServiceCookie {
16
+ /** Navn, eventuelt med `*` for en familie av kapsler, f.eks. `_hjSession*`. */
17
+ name: string;
18
+ /** Levetid i dager, eller `'session'` for kapsler som forsvinner når nettleseren lukkes. */
19
+ duration: number | 'session';
20
+ }
21
+ /**
22
+ * En tjeneste som lastes (eller ikke lastes) avhengig av samtykke.
23
+ *
24
+ * Dette er c15t sin `Script`-type med feltene vi trenger for å kunne vise tjenesten i
25
+ * dialogen og rydde opp etter den.
26
+ */
27
+ export type ConsentService = Script & {
28
+ /**
29
+ * Informasjonskapslene tjenesten setter, til visning i dialogen. En tom liste betyr at
30
+ * tjenesten er kapselfri, og vises som nettopp det — ikke som at vi ikke vet.
31
+ */
32
+ cookies?: ServiceCookie[];
33
+ /**
34
+ * Navn slik det vises i dialogen. Faller tilbake til `id` når det ikke er satt.
35
+ */
36
+ displayName?: string;
37
+ /**
38
+ * Informasjonskapsler tjenesten setter, og som skal slettes når samtykket trekkes tilbake.
39
+ * Prefiks-match: `_hj` treffer `_hjSession`, `_hjIncludedInSessionSample`, osv.
40
+ *
41
+ * c15t laster ut selve skriptet og laster siden på nytt, men sletter ikke kapslene
42
+ * tredjeparten allerede har satt.
43
+ */
44
+ clearCookiePrefixes?: string[];
45
+ };
46
+ /**
47
+ * Konfigurasjon av samtykkeløsningen. Alt som varierer mellom miljøer og applikasjoner sendes
48
+ * inn her — biblioteket leser aldri `process.env` selv.
49
+ *
50
+ * Det er et bevisst krav: i rammeverk som bygger ett artefakt for flere miljøer (Next.js med
51
+ * `NEXT_PUBLIC_*`, Vite med `import.meta.env`) bakes miljøvariabler inn på byggetidspunktet,
52
+ * og en verdi lest inne i biblioteket ville blitt låst til byggemiljøet.
53
+ */
54
+ export interface ConsentConfig {
55
+ /**
56
+ * Domenet informasjonskapselen settes på. Dette er mekanismen som gir ett felles samtykke
57
+ * på tvers av subdomener: `.example.no` gjør at alle tjenester under domenet deler svaret.
58
+ *
59
+ * La stå udefinert lokalt, da settes kapselen på gjeldende vertsnavn.
60
+ *
61
+ * Merk: bruk denne fremfor c15t sin `crossSubdomain: true`. Den utleder domenet fra de to
62
+ * siste leddene i vertsnavnet, som gir riktig `.example.no` i produksjon, men et bredere
63
+ * domene enn ønsket når testmiljøene ligger dypere nestet.
64
+ */
65
+ cookieDomain?: string;
66
+ /** Navn på informasjonskapselen. Må være likt i alle apper som skal dele samtykke. */
67
+ storageKey?: string;
68
+ /** Hvor lenge samtykket varer. Datatilsynet anbefaler høyst 12 måneder. */
69
+ expiryDays?: number;
70
+ /** Tjenestene applikasjonen laster. Se `services.ts` for ferdige oppsett. */
71
+ services: ConsentService[];
72
+ /** Slår på `[c15t]`-logging i konsollet. */
73
+ debug?: boolean;
74
+ }
75
+ /** Samtykkestatus per kategori. */
76
+ export type ConsentState = Record<ConsentCategory, boolean>;
77
+ /** Hvilken del av samtykke-grensesnittet som vises. */
78
+ export type ConsentUI = 'none' | 'banner' | 'dialog';
@@ -0,0 +1,23 @@
1
+ import type { ConsentStore } from './consentStore';
2
+ import type { ConsentTranslations } from './translations';
3
+ export interface ConsentContextValue {
4
+ store: ConsentStore;
5
+ translations: ConsentTranslations;
6
+ /** Falsk når ingen av tjenestene faktisk avhenger av samtykke. Da vises ikke banneret. */
7
+ showsBanner: boolean;
8
+ /**
9
+ * Falsk under serverrendring og fram til første klientrender er ferdig.
10
+ *
11
+ * Samtykket ligger i en informasjonskapsel som bare leses i nettleseren, så serveren kan
12
+ * ikke vite om banneret skal vises. Render vi ut fra klienttilstanden med én gang, spriker
13
+ * markupen fra det serveren sendte, og React forkaster hele treet med en hydreringsfeil.
14
+ * Samtykkeflatene venter derfor til etter mount.
15
+ */
16
+ mounted: boolean;
17
+ }
18
+ export declare const ConsentContext: import("react").Context<ConsentContextValue | null>;
19
+ /**
20
+ * Tilgang til hele samtykkekonteksten. Brukes av komponentene i pakken; applikasjonskode
21
+ * klarer seg som regel med `useConsent`.
22
+ */
23
+ export declare function useConsentStore(): ConsentContextValue;
@@ -0,0 +1,40 @@
1
+ import { createConsentManagerStore } from 'c15t';
2
+ import type { ConsentConfig, ConsentService, ConsentState } from './consent.types';
3
+ /** Navn på informasjonskapselen dersom kallstedet ikke overstyrer det. */
4
+ export declare const DEFAULT_STORAGE_KEY = "kystverket_consent";
5
+ /** Halvår. Kortere enn c15t sin standard på 365 dager. */
6
+ export declare const DEFAULT_EXPIRY_DAYS = 182;
7
+ export type ConsentStore = ReturnType<typeof createConsentManagerStore>;
8
+ /**
9
+ * Kategoriene som faktisk kan slås av og på av brukeren, utledet fra tjenestelista.
10
+ *
11
+ * `necessary` er alltid på og vises ikke som et valg. Tjenester med `alwaysLoad` lastes
12
+ * uansett samtykke, så de bidrar ikke med en kategori brukeren kan styre.
13
+ */
14
+ export declare function getSelectableCategories(services: ConsentService[]): ("experience" | "functionality" | "marketing" | "measurement")[];
15
+ /**
16
+ * Sant når applikasjonen har minst én tjeneste som faktisk avhenger av samtykke.
17
+ *
18
+ * Saksbehandling laster i dag bare Plausible, som er informasjonskapselfritt og går med
19
+ * `alwaysLoad`. Da har banneret ingenting å gate, og skal ikke vises — men resten av
20
+ * samtykkeløsningen mountes likevel, slik at status kan leses og endres.
21
+ */
22
+ export declare function hasGatedServices(services: ConsentService[]): boolean;
23
+ /**
24
+ * Sletter informasjonskapsler satt av tjenester brukeren ikke har samtykket til.
25
+ *
26
+ * c15t laster ut skriptet og laster siden på nytt, men rører ikke kapslene tjenesten
27
+ * allerede har skrevet. Matcher på prefiks, siden Hotjar & co. setter et ukjent antall
28
+ * kapsler med felles prefiks (`_hjSession`, `_hjIncludedInSessionSample`, ...).
29
+ *
30
+ * Kjøres også ved oppstart, slik at kapsler som ligger igjen fra et tidligere samtykke
31
+ * blir ryddet bort.
32
+ */
33
+ export declare function clearServiceCookies(services: ConsentService[], consents: Partial<ConsentState>, cookieDomain?: string): void;
34
+ /**
35
+ * Setter opp samtykkelageret i offline-modus: ingen nettverkskall, ingen backend, alt lagres
36
+ * i nettleseren. Informasjonskapselen er fasit, og c15t speiler den til localStorage — som
37
+ * er viktig her, siden localStorage er bundet til origin og derfor ikke deles mellom
38
+ * subdomener slik kapselen gjør.
39
+ */
40
+ export declare function createConsentStore(config: ConsentConfig): ConsentStore;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Teller over hvor mange `ConsentPreferencesDialog` som står mountet.
3
+ *
4
+ * Et muterbart objekt framfor tilstand: ingen skal rendre på nytt fordi dialogen registrerer
5
+ * seg. Dette brukes bare til å advare utviklere om et oppsett som ikke henger sammen.
6
+ */
7
+ export interface ConsentDialogRegistry {
8
+ count: number;
9
+ }
10
+ export declare const ConsentDialogRegistryProvider: import("react").Provider<ConsentDialogRegistry | null>;
11
+ /** Lager registeret. Kalles én gang, av `ConsentProvider`. */
12
+ export declare function useConsentDialogRegistry(): ConsentDialogRegistry;
13
+ /** Melder dialogen inn i registeret så lenge den står mountet. */
14
+ export declare function useRegisterConsentDialog(): void;
15
+ /**
16
+ * Advarer når en flate som åpner innstillingsdialogen vises uten at dialogen er mountet.
17
+ *
18
+ * Både banneret og innstillingsknappen skjuler seg selv når de setter `activeUI` til `dialog`.
19
+ * Er det ingen dialog til å ta over, forsvinner flaten uten at noe kommer i stedet, og brukeren
20
+ * sitter igjen uten vei videre — verken til å gi eller trekke tilbake samtykke. Det er lett å gå
21
+ * i når flatene mountes hver for seg i stedet for gjennom `CookieConsent`.
22
+ *
23
+ * @param componentName Navnet som skal stå i advarselen.
24
+ * @param active Om flaten faktisk vises. En skjult flate kan ingen klikke seg fast i.
25
+ */
26
+ export declare function useMissingConsentDialogWarning(componentName: string, active: boolean): void;