@leabankab/component-library 8.9.1 → 8.10.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.
- package/CHANGELOG.md +15 -0
- package/dist/cookie-consent/copy.d.ts +8 -2
- package/dist/cookie-consent/core.d.ts +18 -3
- package/dist/cookie-consent/react.d.ts +35 -2
- package/dist/cookie-consent-core-BYak3SKZ.cjs +2 -0
- package/dist/cookie-consent-core-BYak3SKZ.cjs.map +1 -0
- package/dist/{cookie-consent-core-lX0QAEAo.mjs → cookie-consent-core-DMnn4IZW.mjs} +25 -15
- package/dist/cookie-consent-core-DMnn4IZW.mjs.map +1 -0
- package/dist/cookie-consent-react.cjs +1 -1
- package/dist/cookie-consent-react.cjs.map +1 -1
- package/dist/cookie-consent-react.mjs +168 -151
- package/dist/cookie-consent-react.mjs.map +1 -1
- package/dist/cookie-consent.cjs +1 -1
- package/dist/cookie-consent.mjs +2 -2
- package/dist/website/alert-banner/alert-banner.d.ts +21 -4
- package/dist/website.cjs +1 -1
- package/dist/website.cjs.map +1 -1
- package/dist/website.css +1 -1
- package/dist/website.mjs +12 -11
- package/dist/website.mjs.map +1 -1
- package/package.json +2 -1
- package/dist/cookie-consent-core-fntGUdFt.cjs +0 -2
- package/dist/cookie-consent-core-fntGUdFt.cjs.map +0 -1
- package/dist/cookie-consent-core-lX0QAEAo.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cookie-consent-react.cjs","names":[],"sources":["../src/utils/merge-class-name.ts","../src/components/buttons/button.module.css","../src/components/buttons/button-base.tsx","../src/components/buttons/button/button.tsx","../src/components/feedback/dialog/dialog.module.css","../src/i18n/language-context.tsx","../src/i18n/create-translation.ts","../src/components/feedback/use-close-label.ts","../src/components/feedback/dialog/dialog.tsx","../src/components/form/switch/switch.module.css","../src/components/form/switch/switch.tsx","../src/components/surfaces/accordion/accordion.module.css","../src/components/surfaces/accordion/accordion.tsx","../src/cookie-consent/consent-dialog.module.css","../src/cookie-consent/strapi-blocks.tsx","../src/cookie-consent/consent-dialog.tsx","../src/cookie-consent/react.tsx"],"sourcesContent":["type ClassNameProp<State> =\n | string\n | ((state: State) => string | undefined)\n | undefined;\n\n/**\n * Base UI replaces `className` wholesale, which would drop the module class of\n * a wrapper component. Returning a callback keeps the state argument working\n * for consumers who pass a function.\n *\n * Internal helper — not part of the public API, so it isn't re-exported from\n * `src/utils/index.ts`.\n */\nexport const mergeClassName =\n <State,>(base: string, className: ClassNameProp<State>) =>\n (state: State) =>\n [base, typeof className === \"function\" ? className(state) : className]\n .filter(Boolean)\n .join(\" \");\n",".Button {\n --button-solid: var(--lea-palette-primary-base);\n --button-solid-hover: var(--lea-palette-primary-hover);\n --button-on-solid: var(--lea-palette-primary-contrast);\n --button-fg: var(--lea-palette-primary-base);\n --button-border: var(--lea-palette-primary-base);\n --button-tint: color-mix(\n in srgb,\n var(--button-fg) calc(var(--lea-opacity-hover) * 100%),\n color-mix(\n in srgb,\n var(--lea-color-white) calc(var(--lea-opacity-highlight) * 100%),\n transparent\n )\n );\n\n --button-height: 2.75rem;\n --button-icon-size: 1.25rem;\n --button-padding-inline: calc(var(--lea-spacing) * 2.5);\n\n box-sizing: border-box;\n display: inline-flex;\n min-height: var(--button-height);\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n gap: var(--lea-spacing);\n padding-block: calc(var(--lea-spacing) * 0.5);\n padding-inline: var(--button-padding-inline);\n margin: 0;\n border: 1px solid transparent;\n border-radius: var(--lea-radius-full);\n background-color: transparent;\n color: var(--button-fg);\n font-family: inherit;\n font-size: var(--lea-text-label-lg-font-size);\n font-weight: var(--lea-text-label-lg-font-weight);\n line-height: var(--lea-text-label-lg-line-height);\n letter-spacing: var(--lea-text-label-lg-letter-spacing);\n text-align: center;\n text-decoration: none;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n transition: background-color var(--lea-duration-normal)\n var(--lea-easing-standard),\n border-color var(--lea-duration-normal) var(--lea-easing-standard),\n color var(--lea-duration-normal) var(--lea-easing-standard),\n opacity var(--lea-duration-normal) var(--lea-easing-standard);\n}\n\n.Button svg {\n width: var(--button-icon-size);\n height: var(--button-icon-size);\n flex-shrink: 0;\n}\n\n.Button:focus-visible {\n outline: 2px solid var(--lea-palette-text-base);\n outline-offset: 0.125rem;\n}\n\n/* Colours */\n\n.Button[data-color=\"secondary\"] {\n --button-solid: var(--lea-palette-secondary-base);\n --button-solid-hover: var(--lea-palette-secondary-hover);\n --button-on-solid: var(--lea-palette-secondary-contrast);\n --button-fg: var(--lea-palette-secondary-base);\n --button-border: var(--lea-palette-secondary-base);\n}\n\n.Button[data-color=\"error\"] {\n --button-solid: var(--lea-color-red-600);\n --button-solid-hover: var(--lea-color-red-500);\n --button-on-solid: var(--lea-palette-text-contrast);\n --button-fg: var(--lea-color-red-600);\n --button-border: var(--lea-color-red-600);\n}\n\n/* Variants */\n\n.Button[data-variant=\"solid\"] {\n background-color: var(--button-solid);\n color: var(--button-on-solid);\n}\n\n.Button[data-variant=\"outline\"] {\n border-color: var(--button-border);\n}\n\n/* `IconButton` only: a white disc that floats over a picture or a section,\n with the website's soft shadow. */\n.Button[data-variant=\"elevated\"] {\n background-color: var(--lea-palette-bg-base);\n box-shadow: var(--lea-shadow-soft);\n}\n\n@media (hover: hover) {\n .Button[data-variant=\"solid\"]:not([data-disabled]):hover {\n background-color: var(--button-solid-hover);\n }\n\n .Button[data-variant=\"outline\"]:not([data-disabled]):hover,\n .Button[data-variant=\"ghost\"]:not([data-disabled]):hover {\n background-color: var(--button-tint);\n }\n\n /* The tint mixed into white, so the disc stays opaque over a picture. */\n .Button[data-variant=\"elevated\"]:not([data-disabled]):hover {\n background-color: color-mix(\n in srgb,\n var(--button-fg) calc(var(--lea-opacity-hover) * 100%),\n var(--lea-palette-bg-base)\n );\n }\n}\n\n/* Sizes */\n\n.Button[data-size=\"sm\"] {\n --button-height: 2.25rem;\n --button-icon-size: 1rem;\n --button-padding-inline: calc(var(--lea-spacing) * 2);\n}\n\n.Button[data-size=\"lg\"] {\n --button-height: 3.25rem;\n --button-icon-size: 1.5rem;\n --button-padding-inline: calc(var(--lea-spacing) * 2.5);\n}\n\n.Button[data-start-icon] {\n padding-inline-start: calc(var(--button-padding-inline) - var(--lea-spacing));\n}\n\n.Button[data-end-icon] {\n padding-inline-end: calc(var(--button-padding-inline) - var(--lea-spacing));\n}\n\n.Button.IconButton {\n width: var(--button-height);\n height: var(--button-height);\n padding: 0;\n}\n\n/* The website's 40px disc (carousel arrows, video button); sm and lg keep the\n heights of the other variants. */\n.Button.IconButton[data-variant=\"elevated\"][data-size=\"md\"] {\n --button-height: 2.5rem;\n}\n\n.Button[data-disabled]:not([data-loading]) {\n color: var(--lea-color-neutral-400);\n cursor: not-allowed;\n}\n\n.Button[data-variant=\"solid\"][data-disabled]:not([data-loading]) {\n background-color: var(--lea-color-neutral-200);\n color: var(--lea-color-neutral-600);\n}\n\n.Button[data-variant=\"outline\"][data-disabled]:not([data-loading]) {\n border-color: var(--lea-palette-border-base);\n}\n\n.Button[data-loading] {\n cursor: progress;\n opacity: 0.7;\n}\n\n.Spinner {\n box-sizing: border-box;\n display: inline-block;\n width: var(--button-icon-size);\n height: var(--button-icon-size);\n flex-shrink: 0;\n border: 2px solid currentColor;\n border-inline-end-color: transparent;\n border-radius: var(--lea-radius-full);\n animation: button-spin 0.8s linear infinite;\n}\n\n@keyframes button-spin {\n to {\n rotate: 360deg;\n }\n}\n\n.SrOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .Button {\n transition-duration: 0.01ms;\n }\n\n .Spinner {\n animation: none;\n }\n}\n","\"use client\";\n\nimport { Button as BaseButton } from \"@base-ui/react/button\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport {\n isValidElement,\n type MouseEvent,\n type ReactElement,\n type Ref,\n} from \"react\";\n\nimport { mergeClassName } from \"../../utils/merge-class-name\";\nimport type { ColorRoles } from \"../color-roles\";\nimport styles from \"./button.module.css\";\n\nexport type ButtonVariant = \"solid\" | \"outline\" | \"ghost\";\nexport type ButtonColor = ColorRoles<\"primary\" | \"secondary\" | \"error\">;\nexport type ButtonSize = \"sm\" | \"md\" | \"lg\";\n\nexport type ButtonRootProps = Omit<BaseButton.Props, \"color\" | \"ref\"> & {\n /**\n * Shows the busy state: the button is disabled, keeps its colour at reduced\n * opacity and sets `aria-busy`.\n * @default false\n */\n loading?: boolean;\n /** The rendered element: the `<button>`, or the link passed to `render`. */\n ref?: Ref<HTMLElement>;\n};\n\nexport type ButtonBaseProps = ButtonRootProps & {\n /**\n * Visual weight.\n * - `\"solid\"` — filled, for the main action.\n * - `\"outline\"` — bordered, no fill.\n * - `\"ghost\"` — no border or fill until hovered.\n * @default \"solid\"\n */\n variant?: ButtonVariant;\n /**\n * Colour scheme: `\"primary\"` is the brand purple, `\"secondary\"` the\n * near-black, `\"error\"` is for destructive actions.\n * @default \"primary\"\n */\n color?: ButtonColor;\n /**\n * @default \"md\"\n */\n size?: ButtonSize;\n};\n\n/**\n * A `render` element carrying `href` (an `<a>`, Next.js `Link`) or `to`\n * (React Router `Link`) is a link. Base UI's `Button` would give it\n * `role=\"button\"`, so links are rendered without button semantics.\n */\nconst isLinkElement = (\n render: ButtonRootProps[\"render\"],\n): render is ReactElement<{ href?: unknown; to?: unknown }> =>\n isValidElement<{ href?: unknown; to?: unknown }>(render) &&\n (render.props.href != null || render.props.to != null);\n\nconst preventDefault = (event: MouseEvent) => event.preventDefault();\n\n/**\n * A button, or the link passed to `render`, with the disabled and busy states\n * wired. It has no look of its own: the class comes from the caller. Internal\n * — not exported from the package.\n */\nexport function ButtonRoot({\n loading = false,\n disabled,\n className,\n render,\n nativeButton,\n focusableWhenDisabled,\n ref,\n ...rest\n}: ButtonRootProps) {\n const isDisabled = Boolean(disabled || loading);\n const isLink = isLinkElement(render);\n\n const shared = {\n \"aria-busy\": loading || undefined,\n \"data-loading\": loading || undefined,\n };\n\n const link = useRender({\n enabled: isLink,\n render: isLink ? render : undefined,\n ref,\n defaultTagName: \"a\",\n props: {\n ...rest,\n ...shared,\n \"aria-disabled\": isDisabled || undefined,\n \"data-disabled\": isDisabled ? \"\" : undefined,\n className:\n typeof className === \"function\"\n ? className({ disabled: isDisabled })\n : className,\n onClick: isDisabled ? preventDefault : rest.onClick,\n },\n });\n\n if (link) return link;\n\n return (\n <BaseButton\n {...rest}\n {...shared}\n ref={ref}\n render={render}\n nativeButton={nativeButton}\n focusableWhenDisabled={focusableWhenDisabled}\n disabled={isDisabled}\n className={className}\n />\n );\n}\n\n/**\n * The root shared by `Button`, `IconButton`, `CloseButton` and\n * `DownloadTrigger`: `ButtonRoot` with the button look. Internal — not\n * exported from the package. It also takes `\"elevated\"`, which only\n * `IconButton` offers.\n */\nexport function ButtonBase({\n variant = \"solid\",\n color = \"primary\",\n size = \"md\",\n className,\n ...rest\n}: Omit<ButtonBaseProps, \"variant\"> & {\n variant?: ButtonVariant | \"elevated\";\n}) {\n return (\n <ButtonRoot\n {...rest}\n data-variant={variant}\n data-color={color}\n data-size={size}\n className={mergeClassName(styles.Button, className)}\n />\n );\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\n\nimport { ButtonBase, type ButtonBaseProps } from \"../button-base\";\nimport styles from \"../button.module.css\";\n\nexport type {\n ButtonColor,\n ButtonSize,\n ButtonVariant,\n} from \"../button-base\";\n\nexport type ButtonProps = ButtonBaseProps & {\n /** Icon before the label. Replaced by the spinner while `loading`. */\n startIcon?: ReactNode;\n /** Icon after the label. */\n endIcon?: ReactNode;\n /**\n * Text announced to screen readers while `loading`, through a\n * visually-hidden `role=\"status\"` region, e.g. \"Saving…\".\n */\n loadingLabel?: string;\n};\n\n/**\n * A button built on Base UI's `Button` primitive and styled with Lea's design\n * tokens. Pick a `variant` (`solid`, `outline`, `ghost`), a `color`\n * (`primary`, `secondary`, `error`) and a `size` (`sm`, `md`, `lg`).\n *\n * ```tsx\n * <Button variant=\"outline\" startIcon={<Plus aria-hidden />}>Add</Button>\n * ```\n *\n * To render a link that looks like a button, pass the link through `render`.\n * An element with `href` or `to` is rendered as a real link, without button\n * semantics:\n *\n * ```tsx\n * <Button render={<a href=\"/about\" />}>About</Button>\n * <Button render={<NextLink href=\"/about\" />}>About</Button>\n * <Button render={<RouterLink to=\"/about\" />}>About</Button>\n * ```\n *\n * For a link inside running text, use `Link` instead.\n */\nexport function Button({\n startIcon,\n endIcon,\n loading = false,\n loadingLabel,\n children,\n ...rest\n}: ButtonProps) {\n return (\n <>\n <ButtonBase\n {...rest}\n loading={loading}\n data-start-icon={loading || startIcon != null || undefined}\n data-end-icon={endIcon != null || undefined}\n >\n {loading ? <span className={styles.Spinner} aria-hidden /> : startIcon}\n {children}\n {endIcon}\n </ButtonBase>\n {loadingLabel != null && (\n <span role=\"status\" className={styles.SrOnly}>\n {loading ? loadingLabel : null}\n </span>\n )}\n </>\n );\n}\n","/*\n * Design tokens come from src/theme/tokens.css, generated from src/theme/tokens\n * by `pnpm tokens:css`. It is a plain stylesheet rather than runtime-injected\n * CSS variables, so this component renders correctly without a provider.\n * `--lea-spacing` is the 0.5rem base unit, multiplied to match `spacing(n)`.\n */\n\n.Backdrop {\n position: fixed;\n z-index: 1300;\n background-color: var(--lea-palette-bg-overlay);\n inset: 0;\n opacity: 1;\n transition: opacity var(--lea-duration-normal) var(--lea-easing-standard);\n}\n\n.Backdrop[data-starting-style],\n.Backdrop[data-ending-style] {\n opacity: 0;\n}\n\n.Viewport {\n position: fixed;\n z-index: 1300;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: calc(var(--lea-spacing) * 2);\n inset: 0;\n /*\n * The viewport covers the window so the popup can be centred, but only the\n * popup should swallow clicks — presses that land here are outside presses\n * and must dismiss the dialog.\n */\n pointer-events: none;\n}\n\n.Popup {\n display: flex;\n overflow: hidden;\n width: 100%;\n max-height: 100%;\n flex-direction: column;\n border-radius: var(--lea-radius-4xl);\n background-color: var(--lea-palette-bg-base);\n box-shadow: var(--lea-shadow-xl);\n color: var(--lea-palette-text-base);\n opacity: 1;\n pointer-events: auto;\n transform: scale(1);\n transition:\n opacity var(--lea-duration-normal) var(--lea-easing-standard),\n transform var(--lea-duration-normal) var(--lea-easing-standard);\n}\n\n.Popup[data-starting-style],\n.Popup[data-ending-style] {\n opacity: 0;\n transform: scale(0.95);\n}\n\n.Popup[data-size=\"xs\"] {\n max-width: 27.75rem;\n}\n\n.Popup[data-size=\"sm\"] {\n max-width: 37.5rem;\n}\n\n.Popup[data-size=\"md\"] {\n max-width: 56.25rem;\n}\n\n.Popup[data-size=\"lg\"] {\n max-width: 75rem;\n}\n\n.Header {\n display: flex;\n flex-shrink: 0;\n align-items: flex-start;\n justify-content: space-between;\n gap: var(--lea-spacing);\n padding: calc(var(--lea-spacing) * 3) calc(var(--lea-spacing) * 3)\n calc(var(--lea-spacing) * 2);\n}\n\n.Title {\n margin: 0;\n color: var(--lea-palette-text-base);\n font-size: var(--lea-text-heading-sm-font-size);\n font-weight: var(--lea-text-heading-sm-font-weight);\n line-height: var(--lea-text-heading-sm-line-height);\n letter-spacing: var(--lea-text-heading-sm-letter-spacing);\n}\n\n.Description {\n margin: 0 0 calc(var(--lea-spacing) * 2);\n color: inherit;\n}\n\n/*\n * Set when the description is the last thing in the body. A `:last-child`\n * selector would not do: body content is often a bare string, and text nodes\n * don't count as children.\n */\n.Description[data-last] {\n margin-bottom: 0;\n}\n\n.Content {\n overflow-y: auto;\n flex: 1;\n padding: 0 calc(var(--lea-spacing) * 3) calc(var(--lea-spacing) * 3);\n color: var(--lea-palette-text-base);\n}\n\n/* The footer's top padding takes over the gap, mirroring the header. */\n.Content:has(+ .Footer) {\n padding-bottom: 0;\n}\n\n.Footer {\n display: flex;\n flex-shrink: 0;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: var(--lea-spacing);\n padding: calc(var(--lea-spacing) * 2) calc(var(--lea-spacing) * 3)\n calc(var(--lea-spacing) * 2);\n background-color: var(--lea-palette-bg-base);\n}\n\n.CloseButton {\n display: inline-flex;\n width: 2.5rem;\n height: 2.5rem;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: 2px solid var(--lea-palette-border-base);\n border-radius: var(--lea-radius-full);\n margin-inline-end: calc(var(--lea-spacing) * -0.5);\n background-color: transparent;\n color: var(--lea-palette-text-base);\n cursor: pointer;\n transition:\n border-color var(--lea-duration-emphasis) var(--lea-easing-spring),\n background-color var(--lea-duration-emphasis) var(--lea-easing-spring),\n color var(--lea-duration-emphasis) var(--lea-easing-spring);\n}\n\n@media (hover: hover) {\n .CloseButton:hover {\n border-color: var(--lea-palette-primary-base);\n background-color: var(--lea-palette-primary-base);\n color: var(--lea-palette-primary-contrast);\n }\n}\n\n.CloseButton:focus-visible {\n outline: 2px solid var(--lea-palette-text-base);\n outline-offset: 0.125rem;\n}\n\n/*\n * Fills the screen rather than floating as a card. `100dvh` follows the mobile\n * browser chrome as it collapses, and the safe-area inset keeps the content\n * clear of the home indicator.\n */\n@media (max-width: 599.95px) {\n .Viewport:has(.Popup[data-full-screen-on-mobile]) {\n padding: 0;\n }\n\n .Popup[data-full-screen-on-mobile] {\n width: 100vw;\n max-width: none;\n height: 100dvh;\n max-height: none;\n border-radius: 0;\n }\n\n .Popup[data-full-screen-on-mobile] .Content {\n padding-bottom: calc(\n var(--lea-spacing) * 3 + env(safe-area-inset-bottom)\n );\n }\n\n .Popup[data-full-screen-on-mobile] .Content:has(+ .Footer) {\n padding-bottom: 0;\n }\n\n .Popup[data-full-screen-on-mobile] .Footer {\n padding-bottom: calc(\n var(--lea-spacing) * 3 + env(safe-area-inset-bottom)\n );\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .Backdrop,\n .Popup,\n .CloseButton {\n transition-duration: 0.01ms;\n }\n}\n","\"use client\";\n\nimport { createContext, useContext } from \"react\";\nimport { DEFAULT_LANGUAGE, type Language } from \"./languages\";\n\n/**\n * Holds the currently selected language. Provided by `LeaProvider` and read by\n * components (typically via `createTranslation`). Defaults to `DEFAULT_LANGUAGE`\n * so components still render sensibly when used without the provider.\n */\nexport const LanguageContext = createContext<Language>(DEFAULT_LANGUAGE);\n\n/** Returns the language from the nearest `LeaProvider`, or `DEFAULT_LANGUAGE`. */\nexport const useLanguage = (): Language => useContext(LanguageContext);\n","\"use client\";\n\nimport { useLanguage } from \"./language-context\";\nimport { DEFAULT_LANGUAGE, type Language } from \"./languages\";\n\n/**\n * Translations for a single string, keyed by language. The `no` key is required\n * so the `DEFAULT_LANGUAGE` fallback always resolves; the others are optional.\n */\nexport type TranslationMap = { no: string } & Partial<Record<Language, string>>;\n\n/** A named set of translatable strings, each keyed by language. */\nexport type TranslationSchema = Record<string, TranslationMap>;\n\n/**\n * Creates a hook that returns co-located strings for the current language,\n * each falling back to `no`. Define the schema at module scope and call the\n * returned hook inside a component:\n *\n * ```tsx\n * const useLabels = createTranslation({\n * increase: { no: \"Øk\", se: \"Öka\", fi: \"Lisää\", es: \"Aumentar\" },\n * decrease: { no: \"Reduser\", se: \"Minska\", fi: \"Vähennä\", es: \"Disminuir\" },\n * });\n *\n * function Stepper() {\n * const t = useLabels();\n * return <button aria-label={t.increase} />;\n * }\n * ```\n */\nexport function createTranslation<T extends TranslationSchema>(schema: T) {\n return function useTranslation(): Record<keyof T, string> {\n const language = useLanguage();\n const result = {} as Record<keyof T, string>;\n (Object.keys(schema) as (keyof T)[]).forEach((key) => {\n const map = schema[key];\n result[key] = map[language] ?? map[DEFAULT_LANGUAGE];\n });\n return result;\n };\n}\n","import { createTranslation } from \"../../i18n\";\n\n/**\n * Translated accessible name for the close (X) button shared by the feedback\n * overlays (`SimpleDialog`, `SimpleDrawer`). Consumers can override per-instance\n * via a `closeLabel` prop.\n */\nexport const useCloseLabel = createTranslation({\n close: {\n no: \"Lukk\",\n se: \"Stäng\",\n fi: \"Sulje\",\n es: \"Cerrar\",\n },\n});\n","\"use client\";\n\nimport { Dialog as BaseDialog } from \"@base-ui/react/dialog\";\nimport { X } from \"lucide-react\";\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { mergeClassName } from \"../../../utils/merge-class-name\";\nimport styles from \"./dialog.module.css\";\nimport { useCloseLabel } from \"../use-close-label\";\n\n/**\n * Maximum width of the popup. The names match the breakpoint scale the library\n * used before, so `size=\"xs\"` is still the compact confirm dialog.\n */\nexport type Size = \"xs\" | \"sm\" | \"md\" | \"lg\";\n\n/**\n * Groups all parts of the dialog and owns the open state. Renders no element\n * of its own. Control it with `open` + `onOpenChange`, or leave both off and\n * let a `Dialog.Trigger` drive it.\n */\nexport const Root = BaseDialog.Root;\nexport type RootProps<Payload = unknown> = BaseDialog.Root.Props<Payload>;\n\n/**\n * Opens the dialog. Renders a bare `<button>`, so pass a styled control\n * through `render` — `<Dialog.Trigger render={<Button />}>Open</Dialog.Trigger>`.\n */\nexport const Trigger = BaseDialog.Trigger;\nexport type TriggerProps<Payload = unknown> = BaseDialog.Trigger.Props<Payload>;\n\n/** Moves the backdrop and popup to the end of `<body>`. */\nexport const Portal = BaseDialog.Portal;\nexport type PortalProps = BaseDialog.Portal.Props;\n\nexport type BackdropProps = BaseDialog.Backdrop.Props;\n\n/** The dimmed overlay behind the popup. */\nexport function Backdrop({ className, ...rest }: BackdropProps) {\n return (\n <BaseDialog.Backdrop\n {...rest}\n className={mergeClassName(styles.Backdrop, className)}\n />\n );\n}\n\nexport type ViewportProps = BaseDialog.Viewport.Props;\n\n/** Centres the popup in the window and keeps a gutter at the screen edges. */\nexport function Viewport({ className, ...rest }: ViewportProps) {\n return (\n <BaseDialog.Viewport\n {...rest}\n className={mergeClassName(styles.Viewport, className)}\n />\n );\n}\n\nexport type PopupProps = BaseDialog.Popup.Props & {\n /**\n * Maximum width of the surface.\n * @default \"sm\"\n */\n size?: Size;\n /**\n * Fills the screen on narrow viewports (below 600px) instead of floating as\n * a card. Use it for flows that need the whole screen, such as a multi-step\n * form or a long consent list.\n */\n fullScreenOnMobile?: boolean;\n};\n\n/**\n * The dialog surface. Lays its children out in a column so a `Dialog.Content`\n * can scroll while the header stays put.\n */\nexport function Popup({\n size = \"sm\",\n fullScreenOnMobile,\n className,\n ...rest\n}: PopupProps) {\n return (\n <BaseDialog.Popup\n {...rest}\n data-size={size}\n data-full-screen-on-mobile={fullScreenOnMobile || undefined}\n className={mergeClassName(styles.Popup, className)}\n />\n );\n}\n\nexport type HeaderProps = ComponentPropsWithRef<\"div\">;\n\n/**\n * The title row. Anything after the `Dialog.Title` sits on the same line,\n * which is where `Dialog.CloseButton` belongs. Stays fixed while\n * `Dialog.Content` scrolls.\n */\nexport function Header({ className, ...rest }: HeaderProps) {\n return (\n <div\n {...rest}\n className={[styles.Header, className].filter(Boolean).join(\" \")}\n />\n );\n}\n\nexport type TitleProps = BaseDialog.Title.Props;\n\n/**\n * The heading. Base UI links it to the popup as the accessible name, so no id\n * wiring is needed.\n */\nexport function Title({ className, ...rest }: TitleProps) {\n return (\n <BaseDialog.Title\n {...rest}\n className={mergeClassName(styles.Title, className)}\n />\n );\n}\n\nexport type DescriptionProps = BaseDialog.Description.Props;\n\n/** Supporting text, linked to the popup as its accessible description. */\nexport function Description({ className, ...rest }: DescriptionProps) {\n return (\n <BaseDialog.Description\n {...rest}\n className={mergeClassName(styles.Description, className)}\n />\n );\n}\n\nexport type ContentProps = ComponentPropsWithRef<\"div\">;\n\n/** The body. Scrolls on its own once the popup runs out of height. */\nexport function Content({ className, ...rest }: ContentProps) {\n return (\n <div\n {...rest}\n className={[styles.Content, className].filter(Boolean).join(\" \")}\n />\n );\n}\n\nexport type FooterProps = ComponentPropsWithRef<\"div\">;\n\n/**\n * The action row, placed after `Dialog.Content`. Stays pinned to the bottom of\n * the popup while `Dialog.Content` scrolls, and lines its children up at the\n * end.\n */\nexport function Footer({ className, ...rest }: FooterProps) {\n return (\n <div\n {...rest}\n className={[styles.Footer, className].filter(Boolean).join(\" \")}\n />\n );\n}\n\n/**\n * Closes the dialog. Renders a bare `<button>` for text actions — use\n * `render` for a styled one: `<Dialog.Close render={<Button />}>Cancel</Dialog.Close>`.\n * For the X in the header corner, use `Dialog.CloseButton`.\n */\nexport const Close = BaseDialog.Close;\nexport type CloseProps = BaseDialog.Close.Props;\n\nexport type CloseButtonProps = Omit<\n BaseDialog.Close.Props,\n \"children\" | \"aria-label\"\n> & {\n /** Accessible name. Defaults to a translated \"Close\". */\n label?: string;\n};\n\n/**\n * The round X in the header corner. It renders its own icon and always has an\n * accessible name, so it cannot be built without one.\n */\nexport function CloseButton({ label, className, ...rest }: CloseButtonProps) {\n const t = useCloseLabel();\n\n return (\n <BaseDialog.Close\n {...rest}\n aria-label={label ?? t.close}\n className={mergeClassName(styles.CloseButton, className)}\n >\n <X size={20} aria-hidden />\n </BaseDialog.Close>\n );\n}\n","/*\n * Design tokens come from src/theme/tokens.css, generated from src/theme/tokens\n * by `pnpm tokens:css`. It is a plain stylesheet rather than runtime-injected\n * CSS variables, so this component renders correctly without a provider.\n */\n\n.Switch {\n display: inline-flex;\n width: 2.5rem;\n height: 1.5rem;\n flex-shrink: 0;\n align-items: center;\n padding: 0.125rem;\n border: none;\n border-radius: var(--lea-radius-full);\n background-color: var(--lea-color-neutral-200);\n cursor: pointer;\n transition: background-color var(--lea-duration-normal)\n var(--lea-easing-standard);\n}\n\n.Switch[data-checked] {\n background-color: var(--lea-palette-primary-base);\n}\n\n.Switch:focus-visible {\n outline: 2px solid var(--lea-palette-text-base);\n outline-offset: 0.125rem;\n}\n\n.Switch[data-disabled] {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.Thumb {\n display: block;\n width: 1.25rem;\n height: 1.25rem;\n border-radius: var(--lea-radius-full);\n background-color: var(--lea-palette-bg-base);\n transition: translate var(--lea-duration-normal) var(--lea-easing-standard);\n}\n\n.Thumb[data-checked] {\n translate: 1rem 0;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .Switch,\n .Thumb {\n transition-duration: 0.01ms;\n }\n}\n","\"use client\";\n\nimport { Switch as BaseSwitch } from \"@base-ui/react/switch\";\n\nimport { mergeClassName } from \"../../../utils/merge-class-name\";\nimport styles from \"./switch.module.css\";\n\n/**\n * Requires an accessible name so the switch is never nameless. Provide exactly\n * one of `aria-label` or `aria-labelledby`.\n */\ntype SwitchAccessibleName =\n | { \"aria-label\": string; \"aria-labelledby\"?: never }\n | { \"aria-labelledby\": string; \"aria-label\"?: never };\n\nexport type SwitchProps = Omit<\n BaseSwitch.Root.Props,\n \"children\" | \"aria-label\" | \"aria-labelledby\"\n> &\n SwitchAccessibleName;\n\n/**\n * An on/off toggle built on Base UI's accessible `Switch` primitive.\n *\n * Base UI renders the `switch` role, keeps `aria-checked` in sync and submits\n * a hidden input when `name` is set. The thumb is rendered internally so it\n * can never be left out, and the slide respects `prefers-reduced-motion`.\n *\n * Unlike a checkbox, a switch takes effect immediately — use it for settings\n * that apply on toggle, not for values confirmed by a submit button.\n *\n * ```tsx\n * <Switch\n * aria-label=\"Analyse-informasjonskapsler\"\n * defaultChecked\n * onCheckedChange={(checked) => save(checked)}\n * />\n * ```\n */\nexport function Switch({ className, ...rest }: SwitchProps) {\n return (\n <BaseSwitch.Root\n {...rest}\n className={mergeClassName(styles.Switch, className)}\n >\n <BaseSwitch.Thumb className={styles.Thumb} />\n </BaseSwitch.Root>\n );\n}\n","/*\n * Design tokens come from src/theme/tokens.css, generated from src/theme/tokens\n * by `pnpm tokens:css`. It is a plain stylesheet rather than runtime-injected\n * CSS variables, so this component renders correctly without a provider.\n * `--lea-spacing` is the 0.5rem base unit, multiplied to match `spacing(n)`.\n */\n\n/*\n * Each variant overrides the local properties below; the defaults are\n * `outline`. `.Item` and `.Header` only read them, so hover and active states\n * are one rule each rather than one per variant.\n */\n.Root {\n --accordion-gap: var(--lea-spacing);\n --accordion-item-bg: transparent;\n --accordion-item-border: var(--lea-palette-border-subtle);\n --accordion-item-radius: var(--lea-radius-2xl);\n --accordion-header-radius: var(--lea-radius-2xl);\n --accordion-hover-border: var(--lea-palette-border-strong);\n --accordion-hover-bg: transparent;\n --accordion-active-bg: var(--lea-color-purple-50);\n\n display: flex;\n width: 100%;\n flex-direction: column;\n gap: var(--accordion-gap);\n}\n\n.Root[data-variant=\"solid\"] {\n --accordion-item-bg: var(--lea-palette-bg-subtle);\n --accordion-item-border: transparent;\n --accordion-hover-border: transparent;\n --accordion-hover-bg: var(--lea-color-neutral-100);\n --accordion-active-bg: var(--lea-color-neutral-200);\n}\n\n/* White cards on a grey section: one step darker on hover and press. The\n website's FAQ: 16px apart, 16px inside the header, 18px semibold titles. */\n.Root[data-variant=\"card\"] {\n --accordion-gap: var(--lea-spacing-2);\n --accordion-item-bg: var(--lea-palette-bg-base);\n --accordion-item-border: transparent;\n --accordion-item-radius: var(--lea-radius-4xl);\n --accordion-header-radius: var(--lea-radius-4xl);\n --accordion-hover-border: transparent;\n --accordion-hover-bg: var(--lea-palette-bg-subtle);\n --accordion-active-bg: var(--lea-color-neutral-100);\n}\n\n/* No surface of their own, so hover and pressed lighten whatever the accordion\n sits on with translucent white; the hint of text colour keeps them visible\n on white. */\n.Root[data-variant=\"divided\"],\n.Root[data-variant=\"ghost\"] {\n --accordion-highlight: color-mix(\n in srgb,\n var(--lea-color-white) calc(var(--lea-opacity-highlight) * 100%),\n transparent\n );\n --accordion-item-border: transparent;\n --accordion-header-radius: var(--lea-radius-sm);\n --accordion-hover-border: transparent;\n --accordion-hover-bg: color-mix(\n in srgb,\n var(--lea-palette-text-base) calc(var(--lea-opacity-hover) * 100%),\n var(--accordion-highlight)\n );\n --accordion-active-bg: color-mix(\n in srgb,\n var(--lea-palette-text-base) calc(var(--lea-opacity-pressed) * 100%),\n var(--accordion-highlight)\n );\n}\n\n.Root[data-variant=\"divided\"] {\n --accordion-gap: 0;\n --accordion-item-radius: 0;\n}\n\n.Item {\n border: 1px solid var(--accordion-item-border);\n border-radius: var(--accordion-item-radius);\n background-color: var(--accordion-item-bg);\n}\n\n.Root[data-variant=\"divided\"] > .Item:not(:last-child) {\n border-block-end-color: var(--lea-palette-border-subtle);\n}\n\n/*\n * The row lives on the header rather than the trigger so a control rendered\n * beside the trigger (a Switch, say) sits inside the same hover outline.\n */\n.Header {\n display: flex;\n align-items: center;\n gap: calc(var(--lea-spacing) * 2);\n padding: calc(var(--lea-spacing) * 1.7) calc(var(--lea-spacing) * 2);\n border: 1px solid transparent;\n border-radius: var(--accordion-header-radius);\n margin: 0;\n transition: border-color var(--lea-duration-emphasis)\n var(--lea-easing-standard),\n background-color var(--lea-duration-emphasis) var(--lea-easing-standard);\n}\n\n@media (hover: hover) {\n .Header:has(.Trigger:hover:not([data-disabled])) {\n border-color: var(--accordion-hover-border);\n background-color: var(--accordion-hover-bg);\n }\n}\n\n.Header:has(.Trigger:active:not([data-disabled])) {\n background-color: var(--accordion-active-bg);\n}\n\n.Trigger {\n display: flex;\n min-width: 0;\n flex: 1;\n align-items: center;\n justify-content: space-between;\n gap: var(--lea-spacing);\n padding: 0;\n border: none;\n margin: 0;\n background-color: transparent;\n color: var(--lea-palette-text-base);\n cursor: pointer;\n font-family: inherit;\n /* label-lg with room to wrap: the label styles have a tight line height. */\n font-size: var(--lea-font-step-0);\n font-weight: var(--lea-font-weight-medium);\n line-height: var(--lea-line-height-normal);\n text-align: left;\n}\n\n.Trigger:focus-visible {\n outline: 2px solid var(--lea-palette-text-base);\n outline-offset: 0.125rem;\n}\n\n.Trigger[data-disabled] {\n color: var(--lea-palette-text-muted);\n cursor: not-allowed;\n}\n\n.Label {\n min-width: 0;\n}\n\n.Icon {\n flex-shrink: 0;\n transition: transform var(--lea-duration-emphasis) var(--lea-easing-standard);\n}\n\n.Trigger[data-panel-open] .Icon {\n transform: rotate(180deg);\n}\n\n/* Arrow before the title, so it stays next to the text it belongs to. */\n.Root[data-icon-position=\"start\"] .Trigger {\n justify-content: flex-start;\n}\n\n.Root[data-icon-position=\"start\"] .Icon {\n order: -1;\n}\n\n.Panel {\n height: var(--accordion-panel-height);\n overflow: hidden;\n transition: height var(--lea-duration-emphasis) var(--lea-easing-standard);\n}\n\n.Panel[data-starting-style],\n.Panel[data-ending-style] {\n height: 0;\n}\n\n.Content {\n padding-inline: calc(var(--lea-spacing) * 2);\n padding-block: var(--lea-spacing);\n color: var(--lea-palette-text-base);\n font-size: var(--lea-text-body-md-font-size);\n font-weight: var(--lea-text-body-md-font-weight);\n line-height: var(--lea-text-body-md-line-height);\n letter-spacing: var(--lea-text-body-md-letter-spacing);\n}\n\n/*\n * The card's header and answer are spaced as on leabank.no: 16px round the\n * title, 16px between title and arrow, 32px from the title to the answer and\n * under it. The title is 16px semibold on a phone and 18px from 768px, the\n * website's tablet width.\n */\n/* 16px from the card's edge to the title and to the answer: the card has no\n border, and the header's own 1px transparent border counts. */\n.Root[data-variant=\"card\"] .Item {\n border-width: 0;\n}\n\n.Root[data-variant=\"card\"] .Header {\n padding: calc(var(--lea-spacing-2) - 1px);\n}\n\n.Root[data-variant=\"card\"] .Trigger {\n gap: var(--lea-spacing-2);\n font-size: var(--lea-font-step-0);\n font-weight: var(--lea-font-weight-semibold);\n line-height: var(--lea-line-height-snug);\n}\n\n@media (min-width: 768px) {\n .Root[data-variant=\"card\"] .Trigger {\n font-size: var(--lea-font-step-1);\n }\n}\n\n.Root[data-variant=\"card\"] .Content {\n padding: var(--lea-spacing-2) var(--lea-spacing-2) var(--lea-spacing-4);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .Header,\n .Icon,\n .Panel {\n transition-duration: 0.01ms;\n }\n}\n","\"use client\";\n\nimport { Accordion as BaseAccordion } from \"@base-ui/react/accordion\";\nimport { ChevronDown } from \"lucide-react\";\n\nimport { mergeClassName } from \"../../../utils/merge-class-name\";\nimport styles from \"./accordion.module.css\";\n\nexport type Variant = \"outline\" | \"solid\" | \"divided\" | \"ghost\" | \"card\";\n\nexport type IconPosition = \"start\" | \"end\";\n\nexport type RootProps = BaseAccordion.Root.Props & {\n /**\n * Surface treatment of each item.\n * - `\"outline\"` — transparent cards with a light `border.subtle` border\n * (default). Hovering outlines the header.\n * - `\"solid\"` — borderless cards filled light grey (`bg.subtle`). Hovering\n * darkens the header instead of outlining it.\n * - `\"divided\"` — a flush list with no cards, items separated by thin lines.\n * Suits long FAQ lists.\n * - `\"ghost\"` — no border or fill; only the hover tint shows. Use it on\n * tinted backgrounds.\n * - `\"card\"` — borderless white cards with larger corners, for a list of\n * questions on a grey (`bg.subtle`) section of the website. Spaced as the\n * website's FAQ: 16px apart and inside, with 18px semibold titles (16px\n * below 768px).\n * @default \"outline\"\n */\n variant?: Variant;\n /**\n * Which side of the title the expand arrow sits on. Use `\"start\"` when the\n * header also holds a control (such as a `Switch`), so the arrow stays next\n * to the title it belongs to.\n * @default \"end\"\n */\n iconPosition?: IconPosition;\n};\n\n/**\n * A set of collapsible panels built on Base UI's accessible `Accordion`\n * primitive, styled with Lea's design tokens.\n *\n * Heading semantics, `aria-expanded`, `aria-controls` and the unique ids\n * linking each trigger to its panel are generated by Base UI, so the\n * relationships can never dangle and consumers never supply ids by hand.\n * Only one panel opens at a time unless you pass `multiple`.\n *\n * ```tsx\n * <Accordion.Root defaultValue={[\"shipping\"]}>\n * <Accordion.Item value=\"shipping\">\n * <Accordion.Header>\n * <Accordion.Trigger>How long does shipping take?</Accordion.Trigger>\n * </Accordion.Header>\n * <Accordion.Panel>Three to five business days.</Accordion.Panel>\n * </Accordion.Item>\n * </Accordion.Root>\n * ```\n *\n * For the common title-and-content case, reach for `SimpleAccordion` instead.\n */\nexport function Root({\n variant = \"outline\",\n iconPosition = \"end\",\n className,\n ...rest\n}: RootProps) {\n return (\n <BaseAccordion.Root\n {...rest}\n data-variant={variant}\n data-icon-position={iconPosition}\n className={mergeClassName(styles.Root, className)}\n />\n );\n}\n\nexport type ItemProps = BaseAccordion.Item.Props;\n\n/**\n * One panel of the accordion, pairing a `Header` with a `Panel`. Give it a\n * `value` to open it initially or to control it programmatically.\n */\nexport function Item({ className, ...rest }: ItemProps) {\n return (\n <BaseAccordion.Item\n {...rest}\n className={mergeClassName(styles.Item, className)}\n />\n );\n}\n\nexport type HeaderProps = BaseAccordion.Header.Props;\n\n/**\n * The heading that labels the panel. Renders an `<h3>` and must wrap a\n * `Trigger` so the accordion stays navigable by heading.\n *\n * Anything rendered after the `Trigger` sits on the same row, which is where a\n * per-item control belongs. It must go beside the trigger rather than inside\n * it, because the trigger is a `<button>` and nesting controls is invalid.\n *\n * ```tsx\n * <Accordion.Header>\n * <Accordion.Trigger>Analyse</Accordion.Trigger>\n * <Switch aria-label=\"Analyse-informasjonskapsler\" />\n * </Accordion.Header>\n * ```\n */\nexport function Header({ className, ...rest }: HeaderProps) {\n return (\n <BaseAccordion.Header\n {...rest}\n className={mergeClassName(styles.Header, className)}\n />\n );\n}\n\nexport type TriggerProps = BaseAccordion.Trigger.Props;\n\n/**\n * The button that opens and closes its panel. Pass the question or label as\n * children; the chevron is rendered internally and hidden from assistive\n * technology.\n */\nexport function Trigger({ children, className, ...rest }: TriggerProps) {\n return (\n <BaseAccordion.Trigger\n {...rest}\n className={mergeClassName(styles.Trigger, className)}\n >\n <span className={styles.Label}>{children}</span>\n <ChevronDown className={styles.Icon} size={20} aria-hidden />\n </BaseAccordion.Trigger>\n );\n}\n\nexport type PanelProps = BaseAccordion.Panel.Props;\n\n/**\n * The collapsible content of an item. Padding sits on an inner element so the\n * open/close height animation stays smooth.\n */\nexport function Panel({ children, className, ...rest }: PanelProps) {\n return (\n <BaseAccordion.Panel\n {...rest}\n className={mergeClassName(styles.Panel, className)}\n >\n <div className={styles.Content}>{children}</div>\n </BaseAccordion.Panel>\n );\n}\n","/*\n * Layout of the consent dialog body. The surface, header and close button\n * come from Dialog; the categories from Accordion; the controls from Switch\n * and Button.\n */\n\n.Intro {\n color: var(--lea-palette-text-base);\n}\n\n.Intro p,\n.Intro ul,\n.Intro ol {\n margin: 0;\n}\n\n.Intro > * + * {\n margin-top: var(--lea-spacing);\n}\n\n.Intro ul,\n.Intro ol {\n padding-inline-start: calc(var(--lea-spacing) * 2.5);\n}\n\n.Intro a {\n border-radius: var(--lea-radius-sm);\n color: var(--lea-palette-text-accent);\n font-weight: var(--lea-font-weight-semibold);\n text-decoration: underline;\n text-underline-offset: 0.15em;\n}\n\n.Intro a:focus-visible {\n outline: 2px solid var(--lea-palette-text-base);\n outline-offset: 0.125rem;\n}\n\n.Categories {\n margin-top: calc(var(--lea-spacing) * 3);\n}\n\n.CategoryDescription {\n margin: 0;\n color: var(--lea-palette-text-base);\n font-size: var(--lea-text-body-md-font-size);\n font-weight: var(--lea-text-body-md-font-weight);\n line-height: var(--lea-text-body-md-line-height);\n letter-spacing: var(--lea-text-body-md-letter-spacing);\n}\n\n.Actions {\n display: flex;\n flex-wrap: wrap;\n margin-top: calc(var(--lea-spacing) * 3);\n gap: var(--lea-spacing);\n border-top: 1px solid var(--lea-palette-border-subtle);\n button {\n border-radius: var(--lea-radius-2xl);\n }\n}\n\n.Save {\n margin-inline-start: auto;\n}\n\n@media (max-width: 599.95px) {\n .Actions {\n flex-direction: column;\n align-items: stretch;\n }\n\n .Save {\n margin-inline-start: 0;\n }\n}\n","import type { ReactNode } from \"react\";\n\nexport interface StrapiTextNode {\n type: \"text\";\n text: string;\n bold?: boolean;\n italic?: boolean;\n underline?: boolean;\n strikethrough?: boolean;\n code?: boolean;\n}\n\nexport interface StrapiLinkNode {\n type: \"link\";\n url: string;\n children: StrapiTextNode[];\n}\n\nexport type StrapiInlineNode = StrapiTextNode | StrapiLinkNode;\n\nexport interface StrapiParagraphNode {\n type: \"paragraph\";\n children: StrapiInlineNode[];\n}\n\nexport interface StrapiHeadingNode {\n type: \"heading\";\n level: 1 | 2 | 3 | 4 | 5 | 6;\n children: StrapiInlineNode[];\n}\n\nexport interface StrapiListItemNode {\n type: \"list-item\";\n children: StrapiInlineNode[];\n}\n\nexport interface StrapiListNode {\n type: \"list\";\n format: \"ordered\" | \"unordered\";\n children: (StrapiListItemNode | StrapiListNode)[];\n}\n\nexport type StrapiBlocksNode =\n | StrapiParagraphNode\n | StrapiHeadingNode\n | StrapiListNode;\n\n/**\n * Rewrites a link's URL, e.g. to make CMS-relative paths absolute. Return\n * `null` or `undefined` to render the link's label as plain text. The result\n * must still be `https:`, `mailto:` or relative to become a link.\n */\nexport type ResolveHref = (href: string) => string | null | undefined;\n\nconst RELATIVE_BASE = \"https://relative.invalid\";\n\n// Not `URL.canParse`, which Safari only has from 17.\nfunction parseUrl(value: string, base?: string): URL | null {\n try {\n return new URL(value, base);\n } catch {\n return null;\n }\n}\n\n/**\n * Allows `https:`, `mailto:` and URLs relative to the current origin. The URL\n * parser strips the whitespace and control characters browsers ignore, so\n * `\" javascript:\"` and `\"java\\tscript:\"` are caught, and a protocol-relative\n * `//host` (or `/\\host`) resolves to a foreign origin and is rejected.\n */\nexport function isSafeHref(href: string): boolean {\n const value = href.trim();\n if (!value) return false;\n\n const absolute = parseUrl(value);\n if (absolute) {\n return absolute.protocol === \"https:\" || absolute.protocol === \"mailto:\";\n }\n\n return parseUrl(value, RELATIVE_BASE)?.origin === RELATIVE_BASE;\n}\n\nexport interface SafeLinkProps {\n href: string;\n resolveHref?: ResolveHref;\n children: ReactNode;\n}\n\n/** A new-tab link, or its label as plain text when the URL isn't allowed. */\nexport function SafeLink({\n href,\n resolveHref = (value) => value,\n children,\n}: SafeLinkProps) {\n const resolved = resolveHref(href);\n if (!resolved || !isSafeHref(resolved)) return <>{children}</>;\n\n return (\n <a href={resolved.trim()} target=\"_blank\" rel=\"noopener noreferrer\">\n {children}\n </a>\n );\n}\n\nfunction Text({ node }: { node: StrapiTextNode }) {\n let content: ReactNode = node.text;\n if (node.underline) content = <u>{content}</u>;\n if (node.italic) content = <em>{content}</em>;\n if (node.bold) content = <strong>{content}</strong>;\n return <>{content}</>;\n}\n\nfunction Inline({\n nodes,\n resolveHref,\n}: {\n nodes: StrapiInlineNode[] | undefined;\n resolveHref?: ResolveHref;\n}) {\n return (\n <>\n {(nodes ?? []).map((node, index) => {\n if (node.type === \"text\") return <Text key={index} node={node} />;\n if (node.type === \"link\") {\n return (\n <SafeLink key={index} href={node.url ?? \"\"} resolveHref={resolveHref}>\n {(node.children ?? [])\n .filter((child) => child.type === \"text\")\n .map((child, childIndex) => (\n <Text key={childIndex} node={child} />\n ))}\n </SafeLink>\n );\n }\n return null;\n })}\n </>\n );\n}\n\nfunction List({\n node,\n resolveHref,\n}: {\n node: StrapiListNode;\n resolveHref?: ResolveHref;\n}) {\n const Tag = node.format === \"ordered\" ? \"ol\" : \"ul\";\n\n return (\n <Tag>\n {(node.children ?? []).map((child, index) => {\n if (child.type === \"list\") {\n return (\n <li key={index}>\n <List node={child} resolveHref={resolveHref} />\n </li>\n );\n }\n if (child.type === \"list-item\") {\n return (\n <li key={index}>\n <Inline nodes={child.children} resolveHref={resolveHref} />\n </li>\n );\n }\n return null;\n })}\n </Tag>\n );\n}\n\nexport interface StrapiBlocksProps {\n nodes: StrapiBlocksNode[];\n resolveHref?: ResolveHref;\n}\n\n/**\n * Renders Strapi Blocks rich text with only `p`, `strong`, `em`, `u`, `ul`,\n * `ol`, `li` and `a`. Headings become a bold paragraph, so they can't break\n * the dialog's heading order; any other node type is dropped.\n */\nexport function StrapiBlocks({ nodes, resolveHref }: StrapiBlocksProps) {\n return (\n <>\n {nodes.map((node, index) => {\n switch (node.type) {\n case \"paragraph\":\n return (\n <p key={index}>\n <Inline nodes={node.children} resolveHref={resolveHref} />\n </p>\n );\n case \"heading\":\n return (\n <p key={index}>\n <strong>\n <Inline nodes={node.children} resolveHref={resolveHref} />\n </strong>\n </p>\n );\n case \"list\":\n return <List key={index} node={node} resolveHref={resolveHref} />;\n default:\n return null;\n }\n })}\n </>\n );\n}\n","\"use client\";\n\nimport { useId, useState } from \"react\";\n\nimport { Button } from \"../components/buttons/button/button\";\nimport * as Dialog from \"../components/feedback/dialog/dialog\";\nimport { Switch } from \"../components/form/switch/switch\";\nimport * as Accordion from \"../components/surfaces/accordion/accordion\";\nimport styles from \"./consent-dialog.module.css\";\nimport {\n COOKIE_CONSENT_CATEGORIES,\n type ConsentState,\n type CookieConsentCategory,\n} from \"./constants\";\nimport type { CookieConsentCopy } from \"./copy\";\nimport { StrapiBlocks, type ResolveHref } from \"./strapi-blocks\";\n\nexport interface ConsentDialogProps {\n open: boolean;\n /** The stored choice. While `null`, the dialog can't be dismissed. */\n consent: ConsentState | null;\n copy: CookieConsentCopy;\n resolveHref?: ResolveHref;\n onAcceptAll: () => void;\n onAcceptNecessary: () => void;\n onSave: (categories: CookieConsentCategory[]) => void;\n onClose: () => void;\n}\n\nfunction initialSelection(consent: ConsentState | null): ConsentState {\n return {\n necessary: true,\n preferences: consent?.preferences ?? false,\n analytics: consent?.analytics ?? false,\n marketing: consent?.marketing ?? false,\n };\n}\n\nfunction CategoryRow({\n category,\n copy,\n checked,\n onCheckedChange,\n}: {\n category: CookieConsentCategory;\n copy: CookieConsentCopy;\n checked: boolean;\n onCheckedChange: (checked: boolean) => void;\n}) {\n const titleId = useId();\n const descriptionId = useId();\n const { title, description } = copy.categories[category];\n\n return (\n <Accordion.Item value={category}>\n <Accordion.Header>\n <Accordion.Trigger>\n <span id={titleId}>{title}</span>\n </Accordion.Trigger>\n <Switch\n aria-labelledby={titleId}\n aria-describedby={description ? descriptionId : undefined}\n checked={checked}\n disabled={category === \"necessary\"}\n onCheckedChange={onCheckedChange}\n />\n </Accordion.Header>\n {/* Kept mounted while collapsed so the switch's description resolves. */}\n <Accordion.Panel keepMounted>\n {description && (\n <p id={descriptionId} className={styles.CategoryDescription}>\n {description}\n </p>\n )}\n </Accordion.Panel>\n </Accordion.Item>\n );\n}\n\nexport function ConsentDialog({\n open,\n consent,\n copy,\n resolveHref,\n onAcceptAll,\n onAcceptNecessary,\n onSave,\n onClose,\n}: ConsentDialogProps) {\n const dismissible = consent !== null;\n const [selection, setSelection] = useState(() => initialSelection(consent));\n const [wasOpen, setWasOpen] = useState(open);\n\n if (open !== wasOpen) {\n setWasOpen(open);\n if (open) setSelection(initialSelection(consent));\n }\n\n return (\n <Dialog.Root\n open={open}\n onOpenChange={(next) => {\n if (!next && dismissible) onClose();\n }}\n disablePointerDismissal={!dismissible}\n >\n <Dialog.Portal>\n <Dialog.Backdrop />\n <Dialog.Viewport>\n <Dialog.Popup fullScreenOnMobile size=\"md\">\n <Dialog.Header>\n <Dialog.Title>{copy.modalTitle}</Dialog.Title>\n {dismissible && (\n <Dialog.CloseButton label={copy.closeIconLabel} />\n )}\n </Dialog.Header>\n <Dialog.Content>\n <Dialog.Description render={<div />} className={styles.Intro}>\n <StrapiBlocks nodes={copy.intro} resolveHref={resolveHref} />\n </Dialog.Description>\n\n <Accordion.Root\n variant=\"solid\"\n iconPosition=\"start\"\n multiple\n className={styles.Categories}\n >\n {COOKIE_CONSENT_CATEGORIES.map((category) => (\n <CategoryRow\n key={category}\n category={category}\n copy={copy}\n checked={selection[category]}\n onCheckedChange={(checked) =>\n setSelection((current) => ({\n ...current,\n [category]: checked,\n }))\n }\n />\n ))}\n </Accordion.Root>\n </Dialog.Content>\n <Dialog.Footer className={styles.Actions}>\n <Button onClick={onAcceptAll} color=\"secondary\" size=\"sm\">\n {copy.btnAcceptAll}\n </Button>\n <Button onClick={onAcceptNecessary} color=\"secondary\" size=\"sm\">\n {copy.btnAcceptNecessary}\n </Button>\n <Button\n variant=\"outline\"\n color=\"secondary\"\n size=\"sm\"\n className={styles.Save}\n onClick={() =>\n onSave(\n COOKIE_CONSENT_CATEGORIES.filter(\n (category) => selection[category],\n ),\n )\n }\n >\n {copy.btnSaveSettings}\n </Button>\n </Dialog.Footer>\n </Dialog.Popup>\n </Dialog.Viewport>\n </Dialog.Portal>\n </Dialog.Root>\n );\n}\n","\"use client\";\n\nimport \"../theme/tokens.css\";\n\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useSyncExternalStore,\n type ReactNode,\n} from \"react\";\n\nimport { ConsentDialog } from \"./consent-dialog\";\nimport type { ConsentState, Market } from \"./constants\";\nimport type { CookieConsentCopy } from \"./copy\";\nimport {\n closeCookieSettings,\n getServerSnapshot,\n getSnapshot,\n initCookieConsent,\n openCookieSettings,\n setConsent,\n subscribe,\n} from \"./core\";\nimport type { ResolveHref } from \"./strapi-blocks\";\n\nexport interface CookieConsentContextValue {\n /** `null` until the visitor has made a valid choice, and on the server. */\n consent: ConsentState | null;\n openSettings: () => void;\n}\n\nconst CookieConsentContext = createContext<CookieConsentContextValue | null>(\n null,\n);\n\nexport interface CookieConsentProviderProps {\n market: Market;\n copy: CookieConsentCopy;\n /** Rewrites link URLs in the copy, e.g. to make CMS-relative paths absolute. */\n resolveHref?: ResolveHref;\n children?: ReactNode;\n}\n\n/**\n * Reads the shared consent after mount and renders the settings dialog, which\n * opens by itself until the visitor has chosen. `market` is read once.\n */\nexport function CookieConsentProvider({\n market,\n copy,\n resolveHref,\n children,\n}: CookieConsentProviderProps) {\n const { consent, settingsOpen, ready } = useSyncExternalStore(\n subscribe,\n getSnapshot,\n getServerSnapshot,\n );\n\n useEffect(() => {\n initCookieConsent({ market });\n }, []);\n\n const value = useMemo(\n () => ({ consent, openSettings: openCookieSettings }),\n [consent],\n );\n\n return (\n <CookieConsentContext.Provider value={value}>\n {children}\n {ready && (\n <ConsentDialog\n open={settingsOpen}\n consent={consent}\n copy={copy}\n resolveHref={resolveHref}\n onAcceptAll={() => setConsent(\"all\")}\n onAcceptNecessary={() => setConsent([])}\n onSave={setConsent}\n onClose={closeCookieSettings}\n />\n )}\n </CookieConsentContext.Provider>\n );\n}\n\nexport function useCookieConsent(): CookieConsentContextValue {\n const value = useContext(CookieConsentContext);\n if (!value) {\n throw new Error(\n \"useCookieConsent must be used inside a CookieConsentProvider.\",\n );\n }\n return value;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cookie-consent-react.cjs","names":[],"sources":["../src/utils/merge-class-name.ts","../src/components/buttons/button.module.css","../src/components/buttons/button-base.tsx","../src/components/buttons/button/button.tsx","../src/components/feedback/dialog/dialog.module.css","../src/i18n/language-context.tsx","../src/i18n/create-translation.ts","../src/components/feedback/use-close-label.ts","../src/components/feedback/dialog/dialog.tsx","../src/components/form/switch/switch.module.css","../src/components/form/switch/switch.tsx","../src/components/surfaces/accordion/accordion.module.css","../src/components/surfaces/accordion/accordion.tsx","../src/cookie-consent/consent-dialog.module.css","../src/cookie-consent/strapi-blocks.tsx","../src/cookie-consent/consent-dialog.tsx","../src/cookie-consent/react.tsx"],"sourcesContent":["type ClassNameProp<State> =\n | string\n | ((state: State) => string | undefined)\n | undefined;\n\n/**\n * Base UI replaces `className` wholesale, which would drop the module class of\n * a wrapper component. Returning a callback keeps the state argument working\n * for consumers who pass a function.\n *\n * Internal helper — not part of the public API, so it isn't re-exported from\n * `src/utils/index.ts`.\n */\nexport const mergeClassName =\n <State,>(base: string, className: ClassNameProp<State>) =>\n (state: State) =>\n [base, typeof className === \"function\" ? className(state) : className]\n .filter(Boolean)\n .join(\" \");\n",".Button {\n --button-solid: var(--lea-palette-primary-base);\n --button-solid-hover: var(--lea-palette-primary-hover);\n --button-on-solid: var(--lea-palette-primary-contrast);\n --button-fg: var(--lea-palette-primary-base);\n --button-border: var(--lea-palette-primary-base);\n --button-tint: color-mix(\n in srgb,\n var(--button-fg) calc(var(--lea-opacity-hover) * 100%),\n color-mix(\n in srgb,\n var(--lea-color-white) calc(var(--lea-opacity-highlight) * 100%),\n transparent\n )\n );\n\n --button-height: 2.75rem;\n --button-icon-size: 1.25rem;\n --button-padding-inline: calc(var(--lea-spacing) * 2.5);\n\n box-sizing: border-box;\n display: inline-flex;\n min-height: var(--button-height);\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n gap: var(--lea-spacing);\n padding-block: calc(var(--lea-spacing) * 0.5);\n padding-inline: var(--button-padding-inline);\n margin: 0;\n border: 1px solid transparent;\n border-radius: var(--lea-radius-full);\n background-color: transparent;\n color: var(--button-fg);\n font-family: inherit;\n font-size: var(--lea-text-label-lg-font-size);\n font-weight: var(--lea-text-label-lg-font-weight);\n line-height: var(--lea-text-label-lg-line-height);\n letter-spacing: var(--lea-text-label-lg-letter-spacing);\n text-align: center;\n text-decoration: none;\n cursor: pointer;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n transition: background-color var(--lea-duration-normal)\n var(--lea-easing-standard),\n border-color var(--lea-duration-normal) var(--lea-easing-standard),\n color var(--lea-duration-normal) var(--lea-easing-standard),\n opacity var(--lea-duration-normal) var(--lea-easing-standard);\n}\n\n.Button svg {\n width: var(--button-icon-size);\n height: var(--button-icon-size);\n flex-shrink: 0;\n}\n\n.Button:focus-visible {\n outline: 2px solid var(--lea-palette-text-base);\n outline-offset: 0.125rem;\n}\n\n/* Colours */\n\n.Button[data-color=\"secondary\"] {\n --button-solid: var(--lea-palette-secondary-base);\n --button-solid-hover: var(--lea-palette-secondary-hover);\n --button-on-solid: var(--lea-palette-secondary-contrast);\n --button-fg: var(--lea-palette-secondary-base);\n --button-border: var(--lea-palette-secondary-base);\n}\n\n.Button[data-color=\"error\"] {\n --button-solid: var(--lea-color-red-600);\n --button-solid-hover: var(--lea-color-red-500);\n --button-on-solid: var(--lea-palette-text-contrast);\n --button-fg: var(--lea-color-red-600);\n --button-border: var(--lea-color-red-600);\n}\n\n/* Variants */\n\n.Button[data-variant=\"solid\"] {\n background-color: var(--button-solid);\n color: var(--button-on-solid);\n}\n\n.Button[data-variant=\"outline\"] {\n border-color: var(--button-border);\n}\n\n/* `IconButton` only: a white disc that floats over a picture or a section,\n with the website's soft shadow. */\n.Button[data-variant=\"elevated\"] {\n background-color: var(--lea-palette-bg-base);\n box-shadow: var(--lea-shadow-soft);\n}\n\n@media (hover: hover) {\n .Button[data-variant=\"solid\"]:not([data-disabled]):hover {\n background-color: var(--button-solid-hover);\n }\n\n .Button[data-variant=\"outline\"]:not([data-disabled]):hover,\n .Button[data-variant=\"ghost\"]:not([data-disabled]):hover {\n background-color: var(--button-tint);\n }\n\n /* The tint mixed into white, so the disc stays opaque over a picture. */\n .Button[data-variant=\"elevated\"]:not([data-disabled]):hover {\n background-color: color-mix(\n in srgb,\n var(--button-fg) calc(var(--lea-opacity-hover) * 100%),\n var(--lea-palette-bg-base)\n );\n }\n}\n\n/* Sizes */\n\n.Button[data-size=\"sm\"] {\n --button-height: 2.25rem;\n --button-icon-size: 1rem;\n --button-padding-inline: calc(var(--lea-spacing) * 2);\n}\n\n.Button[data-size=\"lg\"] {\n --button-height: 3.25rem;\n --button-icon-size: 1.5rem;\n --button-padding-inline: calc(var(--lea-spacing) * 2.5);\n}\n\n.Button[data-start-icon] {\n padding-inline-start: calc(var(--button-padding-inline) - var(--lea-spacing));\n}\n\n.Button[data-end-icon] {\n padding-inline-end: calc(var(--button-padding-inline) - var(--lea-spacing));\n}\n\n.Button.IconButton {\n width: var(--button-height);\n height: var(--button-height);\n padding: 0;\n}\n\n/* The website's 40px disc (carousel arrows, video button); sm and lg keep the\n heights of the other variants. */\n.Button.IconButton[data-variant=\"elevated\"][data-size=\"md\"] {\n --button-height: 2.5rem;\n}\n\n.Button[data-disabled]:not([data-loading]) {\n color: var(--lea-color-neutral-400);\n cursor: not-allowed;\n}\n\n.Button[data-variant=\"solid\"][data-disabled]:not([data-loading]) {\n background-color: var(--lea-color-neutral-200);\n color: var(--lea-color-neutral-600);\n}\n\n.Button[data-variant=\"outline\"][data-disabled]:not([data-loading]) {\n border-color: var(--lea-palette-border-base);\n}\n\n.Button[data-loading] {\n cursor: progress;\n opacity: 0.7;\n}\n\n.Spinner {\n box-sizing: border-box;\n display: inline-block;\n width: var(--button-icon-size);\n height: var(--button-icon-size);\n flex-shrink: 0;\n border: 2px solid currentColor;\n border-inline-end-color: transparent;\n border-radius: var(--lea-radius-full);\n animation: button-spin 0.8s linear infinite;\n}\n\n@keyframes button-spin {\n to {\n rotate: 360deg;\n }\n}\n\n.SrOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .Button {\n transition-duration: 0.01ms;\n }\n\n .Spinner {\n animation: none;\n }\n}\n","\"use client\";\n\nimport { Button as BaseButton } from \"@base-ui/react/button\";\nimport { useRender } from \"@base-ui/react/use-render\";\nimport {\n isValidElement,\n type MouseEvent,\n type ReactElement,\n type Ref,\n} from \"react\";\n\nimport { mergeClassName } from \"../../utils/merge-class-name\";\nimport type { ColorRoles } from \"../color-roles\";\nimport styles from \"./button.module.css\";\n\nexport type ButtonVariant = \"solid\" | \"outline\" | \"ghost\";\nexport type ButtonColor = ColorRoles<\"primary\" | \"secondary\" | \"error\">;\nexport type ButtonSize = \"sm\" | \"md\" | \"lg\";\n\nexport type ButtonRootProps = Omit<BaseButton.Props, \"color\" | \"ref\"> & {\n /**\n * Shows the busy state: the button is disabled, keeps its colour at reduced\n * opacity and sets `aria-busy`.\n * @default false\n */\n loading?: boolean;\n /** The rendered element: the `<button>`, or the link passed to `render`. */\n ref?: Ref<HTMLElement>;\n};\n\nexport type ButtonBaseProps = ButtonRootProps & {\n /**\n * Visual weight.\n * - `\"solid\"` — filled, for the main action.\n * - `\"outline\"` — bordered, no fill.\n * - `\"ghost\"` — no border or fill until hovered.\n * @default \"solid\"\n */\n variant?: ButtonVariant;\n /**\n * Colour scheme: `\"primary\"` is the brand purple, `\"secondary\"` the\n * near-black, `\"error\"` is for destructive actions.\n * @default \"primary\"\n */\n color?: ButtonColor;\n /**\n * @default \"md\"\n */\n size?: ButtonSize;\n};\n\n/**\n * A `render` element carrying `href` (an `<a>`, Next.js `Link`) or `to`\n * (React Router `Link`) is a link. Base UI's `Button` would give it\n * `role=\"button\"`, so links are rendered without button semantics.\n */\nconst isLinkElement = (\n render: ButtonRootProps[\"render\"],\n): render is ReactElement<{ href?: unknown; to?: unknown }> =>\n isValidElement<{ href?: unknown; to?: unknown }>(render) &&\n (render.props.href != null || render.props.to != null);\n\nconst preventDefault = (event: MouseEvent) => event.preventDefault();\n\n/**\n * A button, or the link passed to `render`, with the disabled and busy states\n * wired. It has no look of its own: the class comes from the caller. Internal\n * — not exported from the package.\n */\nexport function ButtonRoot({\n loading = false,\n disabled,\n className,\n render,\n nativeButton,\n focusableWhenDisabled,\n ref,\n ...rest\n}: ButtonRootProps) {\n const isDisabled = Boolean(disabled || loading);\n const isLink = isLinkElement(render);\n\n const shared = {\n \"aria-busy\": loading || undefined,\n \"data-loading\": loading || undefined,\n };\n\n const link = useRender({\n enabled: isLink,\n render: isLink ? render : undefined,\n ref,\n defaultTagName: \"a\",\n props: {\n ...rest,\n ...shared,\n \"aria-disabled\": isDisabled || undefined,\n \"data-disabled\": isDisabled ? \"\" : undefined,\n className:\n typeof className === \"function\"\n ? className({ disabled: isDisabled })\n : className,\n onClick: isDisabled ? preventDefault : rest.onClick,\n },\n });\n\n if (link) return link;\n\n return (\n <BaseButton\n {...rest}\n {...shared}\n ref={ref}\n render={render}\n nativeButton={nativeButton}\n focusableWhenDisabled={focusableWhenDisabled}\n disabled={isDisabled}\n className={className}\n />\n );\n}\n\n/**\n * The root shared by `Button`, `IconButton`, `CloseButton` and\n * `DownloadTrigger`: `ButtonRoot` with the button look. Internal — not\n * exported from the package. It also takes `\"elevated\"`, which only\n * `IconButton` offers.\n */\nexport function ButtonBase({\n variant = \"solid\",\n color = \"primary\",\n size = \"md\",\n className,\n ...rest\n}: Omit<ButtonBaseProps, \"variant\"> & {\n variant?: ButtonVariant | \"elevated\";\n}) {\n return (\n <ButtonRoot\n {...rest}\n data-variant={variant}\n data-color={color}\n data-size={size}\n className={mergeClassName(styles.Button, className)}\n />\n );\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\n\nimport { ButtonBase, type ButtonBaseProps } from \"../button-base\";\nimport styles from \"../button.module.css\";\n\nexport type {\n ButtonColor,\n ButtonSize,\n ButtonVariant,\n} from \"../button-base\";\n\nexport type ButtonProps = ButtonBaseProps & {\n /** Icon before the label. Replaced by the spinner while `loading`. */\n startIcon?: ReactNode;\n /** Icon after the label. */\n endIcon?: ReactNode;\n /**\n * Text announced to screen readers while `loading`, through a\n * visually-hidden `role=\"status\"` region, e.g. \"Saving…\".\n */\n loadingLabel?: string;\n};\n\n/**\n * A button built on Base UI's `Button` primitive and styled with Lea's design\n * tokens. Pick a `variant` (`solid`, `outline`, `ghost`), a `color`\n * (`primary`, `secondary`, `error`) and a `size` (`sm`, `md`, `lg`).\n *\n * ```tsx\n * <Button variant=\"outline\" startIcon={<Plus aria-hidden />}>Add</Button>\n * ```\n *\n * To render a link that looks like a button, pass the link through `render`.\n * An element with `href` or `to` is rendered as a real link, without button\n * semantics:\n *\n * ```tsx\n * <Button render={<a href=\"/about\" />}>About</Button>\n * <Button render={<NextLink href=\"/about\" />}>About</Button>\n * <Button render={<RouterLink to=\"/about\" />}>About</Button>\n * ```\n *\n * For a link inside running text, use `Link` instead.\n */\nexport function Button({\n startIcon,\n endIcon,\n loading = false,\n loadingLabel,\n children,\n ...rest\n}: ButtonProps) {\n return (\n <>\n <ButtonBase\n {...rest}\n loading={loading}\n data-start-icon={loading || startIcon != null || undefined}\n data-end-icon={endIcon != null || undefined}\n >\n {loading ? <span className={styles.Spinner} aria-hidden /> : startIcon}\n {children}\n {endIcon}\n </ButtonBase>\n {loadingLabel != null && (\n <span role=\"status\" className={styles.SrOnly}>\n {loading ? loadingLabel : null}\n </span>\n )}\n </>\n );\n}\n","/*\n * Design tokens come from src/theme/tokens.css, generated from src/theme/tokens\n * by `pnpm tokens:css`. It is a plain stylesheet rather than runtime-injected\n * CSS variables, so this component renders correctly without a provider.\n * `--lea-spacing` is the 0.5rem base unit, multiplied to match `spacing(n)`.\n */\n\n.Backdrop {\n position: fixed;\n z-index: 1300;\n background-color: var(--lea-palette-bg-overlay);\n inset: 0;\n opacity: 1;\n transition: opacity var(--lea-duration-normal) var(--lea-easing-standard);\n}\n\n.Backdrop[data-starting-style],\n.Backdrop[data-ending-style] {\n opacity: 0;\n}\n\n.Viewport {\n position: fixed;\n z-index: 1300;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: calc(var(--lea-spacing) * 2);\n inset: 0;\n /*\n * The viewport covers the window so the popup can be centred, but only the\n * popup should swallow clicks — presses that land here are outside presses\n * and must dismiss the dialog.\n */\n pointer-events: none;\n}\n\n.Popup {\n display: flex;\n overflow: hidden;\n width: 100%;\n max-height: 100%;\n flex-direction: column;\n border-radius: var(--lea-radius-4xl);\n background-color: var(--lea-palette-bg-base);\n box-shadow: var(--lea-shadow-xl);\n color: var(--lea-palette-text-base);\n opacity: 1;\n pointer-events: auto;\n transform: scale(1);\n transition:\n opacity var(--lea-duration-normal) var(--lea-easing-standard),\n transform var(--lea-duration-normal) var(--lea-easing-standard);\n}\n\n.Popup[data-starting-style],\n.Popup[data-ending-style] {\n opacity: 0;\n transform: scale(0.95);\n}\n\n.Popup[data-size=\"xs\"] {\n max-width: 27.75rem;\n}\n\n.Popup[data-size=\"sm\"] {\n max-width: 37.5rem;\n}\n\n.Popup[data-size=\"md\"] {\n max-width: 56.25rem;\n}\n\n.Popup[data-size=\"lg\"] {\n max-width: 75rem;\n}\n\n.Header {\n display: flex;\n flex-shrink: 0;\n align-items: flex-start;\n justify-content: space-between;\n gap: var(--lea-spacing);\n padding: calc(var(--lea-spacing) * 3) calc(var(--lea-spacing) * 3)\n calc(var(--lea-spacing) * 2);\n}\n\n.Title {\n margin: 0;\n color: var(--lea-palette-text-base);\n font-size: var(--lea-text-heading-sm-font-size);\n font-weight: var(--lea-text-heading-sm-font-weight);\n line-height: var(--lea-text-heading-sm-line-height);\n letter-spacing: var(--lea-text-heading-sm-letter-spacing);\n}\n\n.Description {\n margin: 0 0 calc(var(--lea-spacing) * 2);\n color: inherit;\n}\n\n/*\n * Set when the description is the last thing in the body. A `:last-child`\n * selector would not do: body content is often a bare string, and text nodes\n * don't count as children.\n */\n.Description[data-last] {\n margin-bottom: 0;\n}\n\n.Content {\n overflow-y: auto;\n flex: 1;\n padding: 0 calc(var(--lea-spacing) * 3) calc(var(--lea-spacing) * 3);\n color: var(--lea-palette-text-base);\n}\n\n/* The footer's top padding takes over the gap, mirroring the header. */\n.Content:has(+ .Footer) {\n padding-bottom: 0;\n}\n\n.Footer {\n display: flex;\n flex-shrink: 0;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: var(--lea-spacing);\n padding: calc(var(--lea-spacing) * 2) calc(var(--lea-spacing) * 3)\n calc(var(--lea-spacing) * 2);\n background-color: var(--lea-palette-bg-base);\n}\n\n.CloseButton {\n display: inline-flex;\n width: 2.5rem;\n height: 2.5rem;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n padding: 0;\n border: 2px solid var(--lea-palette-border-base);\n border-radius: var(--lea-radius-full);\n margin-inline-end: calc(var(--lea-spacing) * -0.5);\n background-color: transparent;\n color: var(--lea-palette-text-base);\n cursor: pointer;\n transition:\n border-color var(--lea-duration-emphasis) var(--lea-easing-spring),\n background-color var(--lea-duration-emphasis) var(--lea-easing-spring),\n color var(--lea-duration-emphasis) var(--lea-easing-spring);\n}\n\n@media (hover: hover) {\n .CloseButton:hover {\n border-color: var(--lea-palette-primary-base);\n background-color: var(--lea-palette-primary-base);\n color: var(--lea-palette-primary-contrast);\n }\n}\n\n.CloseButton:focus-visible {\n outline: 2px solid var(--lea-palette-text-base);\n outline-offset: 0.125rem;\n}\n\n/*\n * Fills the screen rather than floating as a card. `100dvh` follows the mobile\n * browser chrome as it collapses, and the safe-area inset keeps the content\n * clear of the home indicator.\n */\n@media (max-width: 599.95px) {\n .Viewport:has(.Popup[data-full-screen-on-mobile]) {\n padding: 0;\n }\n\n .Popup[data-full-screen-on-mobile] {\n width: 100vw;\n max-width: none;\n height: 100dvh;\n max-height: none;\n border-radius: 0;\n }\n\n .Popup[data-full-screen-on-mobile] .Content {\n padding-bottom: calc(\n var(--lea-spacing) * 3 + env(safe-area-inset-bottom)\n );\n }\n\n .Popup[data-full-screen-on-mobile] .Content:has(+ .Footer) {\n padding-bottom: 0;\n }\n\n .Popup[data-full-screen-on-mobile] .Footer {\n padding-bottom: calc(\n var(--lea-spacing) * 3 + env(safe-area-inset-bottom)\n );\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .Backdrop,\n .Popup,\n .CloseButton {\n transition-duration: 0.01ms;\n }\n}\n","\"use client\";\n\nimport { createContext, useContext } from \"react\";\nimport { DEFAULT_LANGUAGE, type Language } from \"./languages\";\n\n/**\n * Holds the currently selected language. Provided by `LeaProvider` and read by\n * components (typically via `createTranslation`). Defaults to `DEFAULT_LANGUAGE`\n * so components still render sensibly when used without the provider.\n */\nexport const LanguageContext = createContext<Language>(DEFAULT_LANGUAGE);\n\n/** Returns the language from the nearest `LeaProvider`, or `DEFAULT_LANGUAGE`. */\nexport const useLanguage = (): Language => useContext(LanguageContext);\n","\"use client\";\n\nimport { useLanguage } from \"./language-context\";\nimport { DEFAULT_LANGUAGE, type Language } from \"./languages\";\n\n/**\n * Translations for a single string, keyed by language. The `no` key is required\n * so the `DEFAULT_LANGUAGE` fallback always resolves; the others are optional.\n */\nexport type TranslationMap = { no: string } & Partial<Record<Language, string>>;\n\n/** A named set of translatable strings, each keyed by language. */\nexport type TranslationSchema = Record<string, TranslationMap>;\n\n/**\n * Creates a hook that returns co-located strings for the current language,\n * each falling back to `no`. Define the schema at module scope and call the\n * returned hook inside a component:\n *\n * ```tsx\n * const useLabels = createTranslation({\n * increase: { no: \"Øk\", se: \"Öka\", fi: \"Lisää\", es: \"Aumentar\" },\n * decrease: { no: \"Reduser\", se: \"Minska\", fi: \"Vähennä\", es: \"Disminuir\" },\n * });\n *\n * function Stepper() {\n * const t = useLabels();\n * return <button aria-label={t.increase} />;\n * }\n * ```\n */\nexport function createTranslation<T extends TranslationSchema>(schema: T) {\n return function useTranslation(): Record<keyof T, string> {\n const language = useLanguage();\n const result = {} as Record<keyof T, string>;\n (Object.keys(schema) as (keyof T)[]).forEach((key) => {\n const map = schema[key];\n result[key] = map[language] ?? map[DEFAULT_LANGUAGE];\n });\n return result;\n };\n}\n","import { createTranslation } from \"../../i18n\";\n\n/**\n * Translated accessible name for the close (X) button shared by the feedback\n * overlays (`SimpleDialog`, `SimpleDrawer`). Consumers can override per-instance\n * via a `closeLabel` prop.\n */\nexport const useCloseLabel = createTranslation({\n close: {\n no: \"Lukk\",\n se: \"Stäng\",\n fi: \"Sulje\",\n es: \"Cerrar\",\n },\n});\n","\"use client\";\n\nimport { Dialog as BaseDialog } from \"@base-ui/react/dialog\";\nimport { X } from \"lucide-react\";\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { mergeClassName } from \"../../../utils/merge-class-name\";\nimport styles from \"./dialog.module.css\";\nimport { useCloseLabel } from \"../use-close-label\";\n\n/**\n * Maximum width of the popup. The names match the breakpoint scale the library\n * used before, so `size=\"xs\"` is still the compact confirm dialog.\n */\nexport type Size = \"xs\" | \"sm\" | \"md\" | \"lg\";\n\n/**\n * Groups all parts of the dialog and owns the open state. Renders no element\n * of its own. Control it with `open` + `onOpenChange`, or leave both off and\n * let a `Dialog.Trigger` drive it.\n */\nexport const Root = BaseDialog.Root;\nexport type RootProps<Payload = unknown> = BaseDialog.Root.Props<Payload>;\n\n/**\n * Opens the dialog. Renders a bare `<button>`, so pass a styled control\n * through `render` — `<Dialog.Trigger render={<Button />}>Open</Dialog.Trigger>`.\n */\nexport const Trigger = BaseDialog.Trigger;\nexport type TriggerProps<Payload = unknown> = BaseDialog.Trigger.Props<Payload>;\n\n/** Moves the backdrop and popup to the end of `<body>`. */\nexport const Portal = BaseDialog.Portal;\nexport type PortalProps = BaseDialog.Portal.Props;\n\nexport type BackdropProps = BaseDialog.Backdrop.Props;\n\n/** The dimmed overlay behind the popup. */\nexport function Backdrop({ className, ...rest }: BackdropProps) {\n return (\n <BaseDialog.Backdrop\n {...rest}\n className={mergeClassName(styles.Backdrop, className)}\n />\n );\n}\n\nexport type ViewportProps = BaseDialog.Viewport.Props;\n\n/** Centres the popup in the window and keeps a gutter at the screen edges. */\nexport function Viewport({ className, ...rest }: ViewportProps) {\n return (\n <BaseDialog.Viewport\n {...rest}\n className={mergeClassName(styles.Viewport, className)}\n />\n );\n}\n\nexport type PopupProps = BaseDialog.Popup.Props & {\n /**\n * Maximum width of the surface.\n * @default \"sm\"\n */\n size?: Size;\n /**\n * Fills the screen on narrow viewports (below 600px) instead of floating as\n * a card. Use it for flows that need the whole screen, such as a multi-step\n * form or a long consent list.\n */\n fullScreenOnMobile?: boolean;\n};\n\n/**\n * The dialog surface. Lays its children out in a column so a `Dialog.Content`\n * can scroll while the header stays put.\n */\nexport function Popup({\n size = \"sm\",\n fullScreenOnMobile,\n className,\n ...rest\n}: PopupProps) {\n return (\n <BaseDialog.Popup\n {...rest}\n data-size={size}\n data-full-screen-on-mobile={fullScreenOnMobile || undefined}\n className={mergeClassName(styles.Popup, className)}\n />\n );\n}\n\nexport type HeaderProps = ComponentPropsWithRef<\"div\">;\n\n/**\n * The title row. Anything after the `Dialog.Title` sits on the same line,\n * which is where `Dialog.CloseButton` belongs. Stays fixed while\n * `Dialog.Content` scrolls.\n */\nexport function Header({ className, ...rest }: HeaderProps) {\n return (\n <div\n {...rest}\n className={[styles.Header, className].filter(Boolean).join(\" \")}\n />\n );\n}\n\nexport type TitleProps = BaseDialog.Title.Props;\n\n/**\n * The heading. Base UI links it to the popup as the accessible name, so no id\n * wiring is needed.\n */\nexport function Title({ className, ...rest }: TitleProps) {\n return (\n <BaseDialog.Title\n {...rest}\n className={mergeClassName(styles.Title, className)}\n />\n );\n}\n\nexport type DescriptionProps = BaseDialog.Description.Props;\n\n/** Supporting text, linked to the popup as its accessible description. */\nexport function Description({ className, ...rest }: DescriptionProps) {\n return (\n <BaseDialog.Description\n {...rest}\n className={mergeClassName(styles.Description, className)}\n />\n );\n}\n\nexport type ContentProps = ComponentPropsWithRef<\"div\">;\n\n/** The body. Scrolls on its own once the popup runs out of height. */\nexport function Content({ className, ...rest }: ContentProps) {\n return (\n <div\n {...rest}\n className={[styles.Content, className].filter(Boolean).join(\" \")}\n />\n );\n}\n\nexport type FooterProps = ComponentPropsWithRef<\"div\">;\n\n/**\n * The action row, placed after `Dialog.Content`. Stays pinned to the bottom of\n * the popup while `Dialog.Content` scrolls, and lines its children up at the\n * end.\n */\nexport function Footer({ className, ...rest }: FooterProps) {\n return (\n <div\n {...rest}\n className={[styles.Footer, className].filter(Boolean).join(\" \")}\n />\n );\n}\n\n/**\n * Closes the dialog. Renders a bare `<button>` for text actions — use\n * `render` for a styled one: `<Dialog.Close render={<Button />}>Cancel</Dialog.Close>`.\n * For the X in the header corner, use `Dialog.CloseButton`.\n */\nexport const Close = BaseDialog.Close;\nexport type CloseProps = BaseDialog.Close.Props;\n\nexport type CloseButtonProps = Omit<\n BaseDialog.Close.Props,\n \"children\" | \"aria-label\"\n> & {\n /** Accessible name. Defaults to a translated \"Close\". */\n label?: string;\n};\n\n/**\n * The round X in the header corner. It renders its own icon and always has an\n * accessible name, so it cannot be built without one.\n */\nexport function CloseButton({ label, className, ...rest }: CloseButtonProps) {\n const t = useCloseLabel();\n\n return (\n <BaseDialog.Close\n {...rest}\n aria-label={label ?? t.close}\n className={mergeClassName(styles.CloseButton, className)}\n >\n <X size={20} aria-hidden />\n </BaseDialog.Close>\n );\n}\n","/*\n * Design tokens come from src/theme/tokens.css, generated from src/theme/tokens\n * by `pnpm tokens:css`. It is a plain stylesheet rather than runtime-injected\n * CSS variables, so this component renders correctly without a provider.\n */\n\n.Switch {\n display: inline-flex;\n width: 2.5rem;\n height: 1.5rem;\n flex-shrink: 0;\n align-items: center;\n padding: 0.125rem;\n border: none;\n border-radius: var(--lea-radius-full);\n background-color: var(--lea-color-neutral-200);\n cursor: pointer;\n transition: background-color var(--lea-duration-normal)\n var(--lea-easing-standard);\n}\n\n.Switch[data-checked] {\n background-color: var(--lea-palette-primary-base);\n}\n\n.Switch:focus-visible {\n outline: 2px solid var(--lea-palette-text-base);\n outline-offset: 0.125rem;\n}\n\n.Switch[data-disabled] {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.Thumb {\n display: block;\n width: 1.25rem;\n height: 1.25rem;\n border-radius: var(--lea-radius-full);\n background-color: var(--lea-palette-bg-base);\n transition: translate var(--lea-duration-normal) var(--lea-easing-standard);\n}\n\n.Thumb[data-checked] {\n translate: 1rem 0;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .Switch,\n .Thumb {\n transition-duration: 0.01ms;\n }\n}\n","\"use client\";\n\nimport { Switch as BaseSwitch } from \"@base-ui/react/switch\";\n\nimport { mergeClassName } from \"../../../utils/merge-class-name\";\nimport styles from \"./switch.module.css\";\n\n/**\n * Requires an accessible name so the switch is never nameless. Provide exactly\n * one of `aria-label` or `aria-labelledby`.\n */\ntype SwitchAccessibleName =\n | { \"aria-label\": string; \"aria-labelledby\"?: never }\n | { \"aria-labelledby\": string; \"aria-label\"?: never };\n\nexport type SwitchProps = Omit<\n BaseSwitch.Root.Props,\n \"children\" | \"aria-label\" | \"aria-labelledby\"\n> &\n SwitchAccessibleName;\n\n/**\n * An on/off toggle built on Base UI's accessible `Switch` primitive.\n *\n * Base UI renders the `switch` role, keeps `aria-checked` in sync and submits\n * a hidden input when `name` is set. The thumb is rendered internally so it\n * can never be left out, and the slide respects `prefers-reduced-motion`.\n *\n * Unlike a checkbox, a switch takes effect immediately — use it for settings\n * that apply on toggle, not for values confirmed by a submit button.\n *\n * ```tsx\n * <Switch\n * aria-label=\"Analyse-informasjonskapsler\"\n * defaultChecked\n * onCheckedChange={(checked) => save(checked)}\n * />\n * ```\n */\nexport function Switch({ className, ...rest }: SwitchProps) {\n return (\n <BaseSwitch.Root\n {...rest}\n className={mergeClassName(styles.Switch, className)}\n >\n <BaseSwitch.Thumb className={styles.Thumb} />\n </BaseSwitch.Root>\n );\n}\n","/*\n * Design tokens come from src/theme/tokens.css, generated from src/theme/tokens\n * by `pnpm tokens:css`. It is a plain stylesheet rather than runtime-injected\n * CSS variables, so this component renders correctly without a provider.\n * `--lea-spacing` is the 0.5rem base unit, multiplied to match `spacing(n)`.\n */\n\n/*\n * Each variant overrides the local properties below; the defaults are\n * `outline`. `.Item` and `.Header` only read them, so hover and active states\n * are one rule each rather than one per variant.\n */\n.Root {\n --accordion-gap: var(--lea-spacing);\n --accordion-item-bg: transparent;\n --accordion-item-border: var(--lea-palette-border-subtle);\n --accordion-item-radius: var(--lea-radius-2xl);\n --accordion-header-radius: var(--lea-radius-2xl);\n --accordion-hover-border: var(--lea-palette-border-strong);\n --accordion-hover-bg: transparent;\n --accordion-active-bg: var(--lea-color-purple-50);\n\n display: flex;\n width: 100%;\n flex-direction: column;\n gap: var(--accordion-gap);\n}\n\n.Root[data-variant=\"solid\"] {\n --accordion-item-bg: var(--lea-palette-bg-subtle);\n --accordion-item-border: transparent;\n --accordion-hover-border: transparent;\n --accordion-hover-bg: var(--lea-color-neutral-100);\n --accordion-active-bg: var(--lea-color-neutral-200);\n}\n\n/* White cards on a grey section: one step darker on hover and press. The\n website's FAQ: 16px apart, 16px inside the header, 18px semibold titles. */\n.Root[data-variant=\"card\"] {\n --accordion-gap: var(--lea-spacing-2);\n --accordion-item-bg: var(--lea-palette-bg-base);\n --accordion-item-border: transparent;\n --accordion-item-radius: var(--lea-radius-4xl);\n --accordion-header-radius: var(--lea-radius-4xl);\n --accordion-hover-border: transparent;\n --accordion-hover-bg: var(--lea-palette-bg-subtle);\n --accordion-active-bg: var(--lea-color-neutral-100);\n}\n\n/* No surface of their own, so hover and pressed lighten whatever the accordion\n sits on with translucent white; the hint of text colour keeps them visible\n on white. */\n.Root[data-variant=\"divided\"],\n.Root[data-variant=\"ghost\"] {\n --accordion-highlight: color-mix(\n in srgb,\n var(--lea-color-white) calc(var(--lea-opacity-highlight) * 100%),\n transparent\n );\n --accordion-item-border: transparent;\n --accordion-header-radius: var(--lea-radius-sm);\n --accordion-hover-border: transparent;\n --accordion-hover-bg: color-mix(\n in srgb,\n var(--lea-palette-text-base) calc(var(--lea-opacity-hover) * 100%),\n var(--accordion-highlight)\n );\n --accordion-active-bg: color-mix(\n in srgb,\n var(--lea-palette-text-base) calc(var(--lea-opacity-pressed) * 100%),\n var(--accordion-highlight)\n );\n}\n\n.Root[data-variant=\"divided\"] {\n --accordion-gap: 0;\n --accordion-item-radius: 0;\n}\n\n.Item {\n border: 1px solid var(--accordion-item-border);\n border-radius: var(--accordion-item-radius);\n background-color: var(--accordion-item-bg);\n}\n\n.Root[data-variant=\"divided\"] > .Item:not(:last-child) {\n border-block-end-color: var(--lea-palette-border-subtle);\n}\n\n/*\n * The row lives on the header rather than the trigger so a control rendered\n * beside the trigger (a Switch, say) sits inside the same hover outline.\n */\n.Header {\n display: flex;\n align-items: center;\n gap: calc(var(--lea-spacing) * 2);\n padding: calc(var(--lea-spacing) * 1.7) calc(var(--lea-spacing) * 2);\n border: 1px solid transparent;\n border-radius: var(--accordion-header-radius);\n margin: 0;\n transition: border-color var(--lea-duration-emphasis)\n var(--lea-easing-standard),\n background-color var(--lea-duration-emphasis) var(--lea-easing-standard);\n}\n\n@media (hover: hover) {\n .Header:has(.Trigger:hover:not([data-disabled])) {\n border-color: var(--accordion-hover-border);\n background-color: var(--accordion-hover-bg);\n }\n}\n\n.Header:has(.Trigger:active:not([data-disabled])) {\n background-color: var(--accordion-active-bg);\n}\n\n.Trigger {\n display: flex;\n min-width: 0;\n flex: 1;\n align-items: center;\n justify-content: space-between;\n gap: var(--lea-spacing);\n padding: 0;\n border: none;\n margin: 0;\n background-color: transparent;\n color: var(--lea-palette-text-base);\n cursor: pointer;\n font-family: inherit;\n /* label-lg with room to wrap: the label styles have a tight line height. */\n font-size: var(--lea-font-step-0);\n font-weight: var(--lea-font-weight-medium);\n line-height: var(--lea-line-height-normal);\n text-align: left;\n}\n\n.Trigger:focus-visible {\n outline: 2px solid var(--lea-palette-text-base);\n outline-offset: 0.125rem;\n}\n\n.Trigger[data-disabled] {\n color: var(--lea-palette-text-muted);\n cursor: not-allowed;\n}\n\n.Label {\n min-width: 0;\n}\n\n.Icon {\n flex-shrink: 0;\n transition: transform var(--lea-duration-emphasis) var(--lea-easing-standard);\n}\n\n.Trigger[data-panel-open] .Icon {\n transform: rotate(180deg);\n}\n\n/* Arrow before the title, so it stays next to the text it belongs to. */\n.Root[data-icon-position=\"start\"] .Trigger {\n justify-content: flex-start;\n}\n\n.Root[data-icon-position=\"start\"] .Icon {\n order: -1;\n}\n\n.Panel {\n height: var(--accordion-panel-height);\n overflow: hidden;\n transition: height var(--lea-duration-emphasis) var(--lea-easing-standard);\n}\n\n.Panel[data-starting-style],\n.Panel[data-ending-style] {\n height: 0;\n}\n\n.Content {\n padding-inline: calc(var(--lea-spacing) * 2);\n padding-block: var(--lea-spacing);\n color: var(--lea-palette-text-base);\n font-size: var(--lea-text-body-md-font-size);\n font-weight: var(--lea-text-body-md-font-weight);\n line-height: var(--lea-text-body-md-line-height);\n letter-spacing: var(--lea-text-body-md-letter-spacing);\n}\n\n/*\n * The card's header and answer are spaced as on leabank.no: 16px round the\n * title, 16px between title and arrow, 32px from the title to the answer and\n * under it. The title is 16px semibold on a phone and 18px from 768px, the\n * website's tablet width.\n */\n/* 16px from the card's edge to the title and to the answer: the card has no\n border, and the header's own 1px transparent border counts. */\n.Root[data-variant=\"card\"] .Item {\n border-width: 0;\n}\n\n.Root[data-variant=\"card\"] .Header {\n padding: calc(var(--lea-spacing-2) - 1px);\n}\n\n.Root[data-variant=\"card\"] .Trigger {\n gap: var(--lea-spacing-2);\n font-size: var(--lea-font-step-0);\n font-weight: var(--lea-font-weight-semibold);\n line-height: var(--lea-line-height-snug);\n}\n\n@media (min-width: 768px) {\n .Root[data-variant=\"card\"] .Trigger {\n font-size: var(--lea-font-step-1);\n }\n}\n\n.Root[data-variant=\"card\"] .Content {\n padding: var(--lea-spacing-2) var(--lea-spacing-2) var(--lea-spacing-4);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .Header,\n .Icon,\n .Panel {\n transition-duration: 0.01ms;\n }\n}\n","\"use client\";\n\nimport { Accordion as BaseAccordion } from \"@base-ui/react/accordion\";\nimport { ChevronDown } from \"lucide-react\";\n\nimport { mergeClassName } from \"../../../utils/merge-class-name\";\nimport styles from \"./accordion.module.css\";\n\nexport type Variant = \"outline\" | \"solid\" | \"divided\" | \"ghost\" | \"card\";\n\nexport type IconPosition = \"start\" | \"end\";\n\nexport type RootProps = BaseAccordion.Root.Props & {\n /**\n * Surface treatment of each item.\n * - `\"outline\"` — transparent cards with a light `border.subtle` border\n * (default). Hovering outlines the header.\n * - `\"solid\"` — borderless cards filled light grey (`bg.subtle`). Hovering\n * darkens the header instead of outlining it.\n * - `\"divided\"` — a flush list with no cards, items separated by thin lines.\n * Suits long FAQ lists.\n * - `\"ghost\"` — no border or fill; only the hover tint shows. Use it on\n * tinted backgrounds.\n * - `\"card\"` — borderless white cards with larger corners, for a list of\n * questions on a grey (`bg.subtle`) section of the website. Spaced as the\n * website's FAQ: 16px apart and inside, with 18px semibold titles (16px\n * below 768px).\n * @default \"outline\"\n */\n variant?: Variant;\n /**\n * Which side of the title the expand arrow sits on. Use `\"start\"` when the\n * header also holds a control (such as a `Switch`), so the arrow stays next\n * to the title it belongs to.\n * @default \"end\"\n */\n iconPosition?: IconPosition;\n};\n\n/**\n * A set of collapsible panels built on Base UI's accessible `Accordion`\n * primitive, styled with Lea's design tokens.\n *\n * Heading semantics, `aria-expanded`, `aria-controls` and the unique ids\n * linking each trigger to its panel are generated by Base UI, so the\n * relationships can never dangle and consumers never supply ids by hand.\n * Only one panel opens at a time unless you pass `multiple`.\n *\n * ```tsx\n * <Accordion.Root defaultValue={[\"shipping\"]}>\n * <Accordion.Item value=\"shipping\">\n * <Accordion.Header>\n * <Accordion.Trigger>How long does shipping take?</Accordion.Trigger>\n * </Accordion.Header>\n * <Accordion.Panel>Three to five business days.</Accordion.Panel>\n * </Accordion.Item>\n * </Accordion.Root>\n * ```\n *\n * For the common title-and-content case, reach for `SimpleAccordion` instead.\n */\nexport function Root({\n variant = \"outline\",\n iconPosition = \"end\",\n className,\n ...rest\n}: RootProps) {\n return (\n <BaseAccordion.Root\n {...rest}\n data-variant={variant}\n data-icon-position={iconPosition}\n className={mergeClassName(styles.Root, className)}\n />\n );\n}\n\nexport type ItemProps = BaseAccordion.Item.Props;\n\n/**\n * One panel of the accordion, pairing a `Header` with a `Panel`. Give it a\n * `value` to open it initially or to control it programmatically.\n */\nexport function Item({ className, ...rest }: ItemProps) {\n return (\n <BaseAccordion.Item\n {...rest}\n className={mergeClassName(styles.Item, className)}\n />\n );\n}\n\nexport type HeaderProps = BaseAccordion.Header.Props;\n\n/**\n * The heading that labels the panel. Renders an `<h3>` and must wrap a\n * `Trigger` so the accordion stays navigable by heading.\n *\n * Anything rendered after the `Trigger` sits on the same row, which is where a\n * per-item control belongs. It must go beside the trigger rather than inside\n * it, because the trigger is a `<button>` and nesting controls is invalid.\n *\n * ```tsx\n * <Accordion.Header>\n * <Accordion.Trigger>Analyse</Accordion.Trigger>\n * <Switch aria-label=\"Analyse-informasjonskapsler\" />\n * </Accordion.Header>\n * ```\n */\nexport function Header({ className, ...rest }: HeaderProps) {\n return (\n <BaseAccordion.Header\n {...rest}\n className={mergeClassName(styles.Header, className)}\n />\n );\n}\n\nexport type TriggerProps = BaseAccordion.Trigger.Props;\n\n/**\n * The button that opens and closes its panel. Pass the question or label as\n * children; the chevron is rendered internally and hidden from assistive\n * technology.\n */\nexport function Trigger({ children, className, ...rest }: TriggerProps) {\n return (\n <BaseAccordion.Trigger\n {...rest}\n className={mergeClassName(styles.Trigger, className)}\n >\n <span className={styles.Label}>{children}</span>\n <ChevronDown className={styles.Icon} size={20} aria-hidden />\n </BaseAccordion.Trigger>\n );\n}\n\nexport type PanelProps = BaseAccordion.Panel.Props;\n\n/**\n * The collapsible content of an item. Padding sits on an inner element so the\n * open/close height animation stays smooth.\n */\nexport function Panel({ children, className, ...rest }: PanelProps) {\n return (\n <BaseAccordion.Panel\n {...rest}\n className={mergeClassName(styles.Panel, className)}\n >\n <div className={styles.Content}>{children}</div>\n </BaseAccordion.Panel>\n );\n}\n","/*\n * Layout of the consent dialog body. The surface, header and close button\n * come from Dialog; the categories from Accordion; the controls from Switch\n * and Button.\n */\n\n.Intro {\n color: var(--lea-palette-text-base);\n}\n\n.Intro p,\n.Intro ul,\n.Intro ol {\n margin: 0;\n}\n\n.Intro > * + * {\n margin-top: var(--lea-spacing);\n}\n\n.Intro ul,\n.Intro ol {\n padding-inline-start: calc(var(--lea-spacing) * 2.5);\n}\n\n.Intro a {\n border-radius: var(--lea-radius-sm);\n color: var(--lea-palette-text-accent);\n font-weight: var(--lea-font-weight-semibold);\n text-decoration: underline;\n text-underline-offset: 0.15em;\n}\n\n.Intro a:focus-visible {\n outline: 2px solid var(--lea-palette-text-base);\n outline-offset: 0.125rem;\n}\n\n.Categories {\n margin-top: calc(var(--lea-spacing) * 3);\n}\n\n.CategoryDescription {\n margin: 0;\n color: var(--lea-palette-text-base);\n font-size: var(--lea-text-body-md-font-size);\n font-weight: var(--lea-text-body-md-font-weight);\n line-height: var(--lea-text-body-md-line-height);\n letter-spacing: var(--lea-text-body-md-letter-spacing);\n}\n\n.Actions {\n display: flex;\n flex-wrap: wrap;\n margin-top: calc(var(--lea-spacing) * 3);\n gap: var(--lea-spacing);\n border-top: 1px solid var(--lea-palette-border-subtle);\n button {\n border-radius: var(--lea-radius-2xl);\n }\n}\n\n.Save {\n margin-inline-start: auto;\n}\n\n@media (max-width: 599.95px) {\n .Actions {\n flex-direction: column;\n align-items: stretch;\n }\n\n .Save {\n margin-inline-start: 0;\n }\n}\n","import type { ReactNode } from \"react\";\n\nexport interface StrapiTextNode {\n type: \"text\";\n text: string;\n bold?: boolean;\n italic?: boolean;\n underline?: boolean;\n strikethrough?: boolean;\n code?: boolean;\n}\n\nexport interface StrapiLinkNode {\n type: \"link\";\n url: string;\n children: StrapiTextNode[];\n}\n\nexport type StrapiInlineNode = StrapiTextNode | StrapiLinkNode;\n\nexport interface StrapiParagraphNode {\n type: \"paragraph\";\n children: StrapiInlineNode[];\n}\n\nexport interface StrapiHeadingNode {\n type: \"heading\";\n level: 1 | 2 | 3 | 4 | 5 | 6;\n children: StrapiInlineNode[];\n}\n\nexport interface StrapiListItemNode {\n type: \"list-item\";\n children: StrapiInlineNode[];\n}\n\nexport interface StrapiListNode {\n type: \"list\";\n format: \"ordered\" | \"unordered\";\n children: (StrapiListItemNode | StrapiListNode)[];\n}\n\nexport type StrapiBlocksNode =\n | StrapiParagraphNode\n | StrapiHeadingNode\n | StrapiListNode;\n\n/**\n * Rewrites a link's URL, e.g. to make CMS-relative paths absolute. Return\n * `null` or `undefined` to render the link's label as plain text. The result\n * must still be `https:`, `mailto:` or relative to become a link.\n */\nexport type ResolveHref = (href: string) => string | null | undefined;\n\nconst RELATIVE_BASE = \"https://relative.invalid\";\n\n// Not `URL.canParse`, which Safari only has from 17.\nfunction parseUrl(value: string, base?: string): URL | null {\n try {\n return new URL(value, base);\n } catch {\n return null;\n }\n}\n\n/**\n * Allows `https:`, `mailto:` and URLs relative to the current origin. The URL\n * parser strips the whitespace and control characters browsers ignore, so\n * `\" javascript:\"` and `\"java\\tscript:\"` are caught, and a protocol-relative\n * `//host` (or `/\\host`) resolves to a foreign origin and is rejected.\n */\nexport function isSafeHref(href: string): boolean {\n const value = href.trim();\n if (!value) return false;\n\n const absolute = parseUrl(value);\n if (absolute) {\n return absolute.protocol === \"https:\" || absolute.protocol === \"mailto:\";\n }\n\n return parseUrl(value, RELATIVE_BASE)?.origin === RELATIVE_BASE;\n}\n\nexport interface SafeLinkProps {\n href: string;\n resolveHref?: ResolveHref;\n children: ReactNode;\n}\n\n/** A new-tab link, or its label as plain text when the URL isn't allowed. */\nexport function SafeLink({\n href,\n resolveHref = (value) => value,\n children,\n}: SafeLinkProps) {\n const resolved = resolveHref(href);\n if (!resolved || !isSafeHref(resolved)) return <>{children}</>;\n\n return (\n <a href={resolved.trim()} target=\"_blank\" rel=\"noopener noreferrer\">\n {children}\n </a>\n );\n}\n\nfunction Text({ node }: { node: StrapiTextNode }) {\n let content: ReactNode = node.text;\n if (node.underline) content = <u>{content}</u>;\n if (node.italic) content = <em>{content}</em>;\n if (node.bold) content = <strong>{content}</strong>;\n return <>{content}</>;\n}\n\nfunction Inline({\n nodes,\n resolveHref,\n}: {\n nodes: StrapiInlineNode[] | undefined;\n resolveHref?: ResolveHref;\n}) {\n return (\n <>\n {(nodes ?? []).map((node, index) => {\n if (node.type === \"text\") return <Text key={index} node={node} />;\n if (node.type === \"link\") {\n return (\n <SafeLink key={index} href={node.url ?? \"\"} resolveHref={resolveHref}>\n {(node.children ?? [])\n .filter((child) => child.type === \"text\")\n .map((child, childIndex) => (\n <Text key={childIndex} node={child} />\n ))}\n </SafeLink>\n );\n }\n return null;\n })}\n </>\n );\n}\n\nfunction List({\n node,\n resolveHref,\n}: {\n node: StrapiListNode;\n resolveHref?: ResolveHref;\n}) {\n const Tag = node.format === \"ordered\" ? \"ol\" : \"ul\";\n\n return (\n <Tag>\n {(node.children ?? []).map((child, index) => {\n if (child.type === \"list\") {\n return (\n <li key={index}>\n <List node={child} resolveHref={resolveHref} />\n </li>\n );\n }\n if (child.type === \"list-item\") {\n return (\n <li key={index}>\n <Inline nodes={child.children} resolveHref={resolveHref} />\n </li>\n );\n }\n return null;\n })}\n </Tag>\n );\n}\n\nexport interface StrapiBlocksProps {\n nodes: StrapiBlocksNode[];\n resolveHref?: ResolveHref;\n}\n\n/**\n * Renders Strapi Blocks rich text with only `p`, `strong`, `em`, `u`, `ul`,\n * `ol`, `li` and `a`. Headings become a bold paragraph, so they can't break\n * the dialog's heading order; any other node type is dropped.\n */\nexport function StrapiBlocks({ nodes, resolveHref }: StrapiBlocksProps) {\n return (\n <>\n {nodes.map((node, index) => {\n switch (node.type) {\n case \"paragraph\":\n return (\n <p key={index}>\n <Inline nodes={node.children} resolveHref={resolveHref} />\n </p>\n );\n case \"heading\":\n return (\n <p key={index}>\n <strong>\n <Inline nodes={node.children} resolveHref={resolveHref} />\n </strong>\n </p>\n );\n case \"list\":\n return <List key={index} node={node} resolveHref={resolveHref} />;\n default:\n return null;\n }\n })}\n </>\n );\n}\n","\"use client\";\n\nimport { useId, useState } from \"react\";\n\nimport { Button } from \"../components/buttons/button/button\";\nimport * as Dialog from \"../components/feedback/dialog/dialog\";\nimport { Switch } from \"../components/form/switch/switch\";\nimport * as Accordion from \"../components/surfaces/accordion/accordion\";\nimport styles from \"./consent-dialog.module.css\";\nimport {\n COOKIE_CONSENT_CATEGORIES,\n type ConsentState,\n type CookieConsentCategory,\n} from \"./constants\";\nimport type { CookieConsentCopy } from \"./copy\";\nimport { StrapiBlocks, type ResolveHref } from \"./strapi-blocks\";\n\nexport interface ConsentDialogProps {\n open: boolean;\n /** The stored choice. While `null`, the dialog can't be dismissed. */\n consent: ConsentState | null;\n copy: CookieConsentCopy;\n resolveHref?: ResolveHref;\n onAcceptAll: () => void;\n onAcceptNecessary: () => void;\n onSave: (categories: CookieConsentCategory[]) => void;\n onClose: () => void;\n}\n\nfunction initialSelection(consent: ConsentState | null): ConsentState {\n return {\n necessary: true,\n preferences: consent?.preferences ?? false,\n analytics: consent?.analytics ?? false,\n marketing: consent?.marketing ?? false,\n };\n}\n\nfunction CategoryRow({\n category,\n copy,\n checked,\n onCheckedChange,\n}: {\n category: CookieConsentCategory;\n copy: CookieConsentCopy;\n checked: boolean;\n onCheckedChange: (checked: boolean) => void;\n}) {\n const titleId = useId();\n const descriptionId = useId();\n const { title, description } = copy.categories[category];\n\n return (\n <Accordion.Item value={category}>\n <Accordion.Header>\n <Accordion.Trigger>\n <span id={titleId}>{title}</span>\n </Accordion.Trigger>\n <Switch\n aria-labelledby={titleId}\n aria-describedby={description ? descriptionId : undefined}\n checked={checked}\n disabled={category === \"necessary\"}\n onCheckedChange={onCheckedChange}\n />\n </Accordion.Header>\n {/* Kept mounted while collapsed so the switch's description resolves. */}\n <Accordion.Panel keepMounted>\n {description && (\n <p id={descriptionId} className={styles.CategoryDescription}>\n {description}\n </p>\n )}\n </Accordion.Panel>\n </Accordion.Item>\n );\n}\n\nexport function ConsentDialog({\n open,\n consent,\n copy,\n resolveHref,\n onAcceptAll,\n onAcceptNecessary,\n onSave,\n onClose,\n}: ConsentDialogProps) {\n const dismissible = consent !== null;\n const [selection, setSelection] = useState(() => initialSelection(consent));\n const [wasOpen, setWasOpen] = useState(open);\n\n if (open !== wasOpen) {\n setWasOpen(open);\n if (open) setSelection(initialSelection(consent));\n }\n\n return (\n <Dialog.Root\n open={open}\n onOpenChange={(next) => {\n if (!next && dismissible) onClose();\n }}\n disablePointerDismissal={!dismissible}\n >\n <Dialog.Portal>\n <Dialog.Backdrop />\n <Dialog.Viewport>\n <Dialog.Popup fullScreenOnMobile size=\"md\">\n <Dialog.Header>\n <Dialog.Title>{copy.modalTitle}</Dialog.Title>\n {dismissible && (\n <Dialog.CloseButton label={copy.closeIconLabel} />\n )}\n </Dialog.Header>\n <Dialog.Content>\n <Dialog.Description render={<div />} className={styles.Intro}>\n <StrapiBlocks nodes={copy.intro} resolveHref={resolveHref} />\n </Dialog.Description>\n\n <Accordion.Root\n variant=\"solid\"\n iconPosition=\"start\"\n multiple\n className={styles.Categories}\n >\n {COOKIE_CONSENT_CATEGORIES.map((category) => (\n <CategoryRow\n key={category}\n category={category}\n copy={copy}\n checked={selection[category]}\n onCheckedChange={(checked) =>\n setSelection((current) => ({\n ...current,\n [category]: checked,\n }))\n }\n />\n ))}\n </Accordion.Root>\n </Dialog.Content>\n <Dialog.Footer className={styles.Actions}>\n <Button onClick={onAcceptAll} color=\"secondary\" size=\"sm\">\n {copy.btnAcceptAll}\n </Button>\n <Button onClick={onAcceptNecessary} color=\"secondary\" size=\"sm\">\n {copy.btnAcceptNecessary}\n </Button>\n <Button\n variant=\"outline\"\n color=\"secondary\"\n size=\"sm\"\n className={styles.Save}\n onClick={() =>\n onSave(\n COOKIE_CONSENT_CATEGORIES.filter(\n (category) => selection[category],\n ),\n )\n }\n >\n {copy.btnSaveSettings}\n </Button>\n </Dialog.Footer>\n </Dialog.Popup>\n </Dialog.Viewport>\n </Dialog.Portal>\n </Dialog.Root>\n );\n}\n","\"use client\";\n\nimport \"../theme/tokens.css\";\n\nimport {\n createContext,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n type ReactNode,\n} from \"react\";\n\nimport { ConsentDialog } from \"./consent-dialog\";\nimport type { ConsentState, Market } from \"./constants\";\nimport type { CookieConsentCopy } from \"./copy\";\nimport {\n closeCookieSettings,\n getServerSnapshot,\n getSnapshot,\n initCookieConsent,\n isCrawler,\n openCookieSettings,\n sameConsent,\n setConsent,\n subscribe,\n} from \"./core\";\nimport type { ResolveHref } from \"./strapi-blocks\";\n\n// `useLayoutEffect` warns when a component is rendered on the server.\nconst useIsomorphicLayoutEffect =\n typeof document === \"undefined\" ? useEffect : useLayoutEffect;\n\nexport interface CookieConsentContextValue {\n /** `null` until the visitor has made a valid choice, and on the server. */\n consent: ConsentState | null;\n openSettings: () => void;\n}\n\nconst CookieConsentContext = createContext<CookieConsentContextValue | null>(\n null,\n);\n\nexport interface CookieConsentProviderProps {\n market: Market;\n copy: CookieConsentCopy;\n /** Rewrites link URLs in the copy, e.g. to make CMS-relative paths absolute. */\n resolveHref?: ResolveHref;\n /**\n * Called with the visitor's choice, for example to update Google Consent\n * Mode or to start analytics. Called once after load when there is a valid\n * stored consent, then whenever the choice changes: the first choice, a\n * change, or a step back to necessary only.\n *\n * Never called with `null`, nor when the visitor saves the same choice\n * again. Called once per change, also under React StrictMode, and also when\n * `initCookieConsent` was called elsewhere first. The latest function is\n * always the one called, so an inline arrow function is fine. An error\n * thrown in it is passed to `reportError`, and the choice is still saved.\n *\n * A function can't be passed from a server component: render the provider\n * through a client component of your own.\n */\n onConsentChange?: (state: ConsentState) => void;\n /**\n * Whether the dialog opens by itself while the visitor has yet to choose.\n * Default `true`. Pass `false` on pages the dialog shouldn't cover, such as\n * the cookie policy, which the intro's link opens in a new tab.\n *\n * While it is `false` and there is no consent, the dialog stays closed and\n * only necessary cookies are used. `openSettings` and `openCookieSettings`\n * still open it, and it still can't be dismissed without a choice. It is\n * read on every render: when it turns `true` with no consent yet, the\n * dialog opens.\n *\n * The dialog never opens by itself for crawlers and audit tools (a user\n * agent with `bot`, `crawl`, `spider`, `slurp`, `teoma` or `lighthouse`).\n * Browser automation such as Playwright gets it as visitors do.\n */\n autoOpen?: boolean;\n children?: ReactNode;\n}\n\n/**\n * Reads the shared consent after mount and renders the settings dialog, which\n * opens by itself until the visitor has chosen, unless `autoOpen` is `false`.\n * `market` is read once.\n */\nexport function CookieConsentProvider({\n market,\n copy,\n resolveHref,\n onConsentChange,\n autoOpen = true,\n children,\n}: CookieConsentProviderProps) {\n const { consent, settingsOpen, ready } = useSyncExternalStore(\n subscribe,\n getSnapshot,\n getServerSnapshot,\n );\n\n const onConsentChangeRef = useRef(onConsentChange);\n useIsomorphicLayoutEffect(() => {\n onConsentChangeRef.current = onConsentChange;\n });\n\n // Reported from the shared store rather than through `initCookieConsent`,\n // which keeps only its first caller's function. The last choice reported is\n // kept in a ref, which survives StrictMode's second mount, so each choice is\n // reported once.\n const reportedRef = useRef<ConsentState | null>(null);\n useEffect(() => {\n const report = () => {\n const { consent } = getSnapshot();\n if (consent === null || sameConsent(consent, reportedRef.current)) return;\n reportedRef.current = consent;\n // The store is notifying its listeners: an error is reported, not\n // thrown, so the choice is still applied and the dialog still closes.\n try {\n onConsentChangeRef.current?.(consent);\n } catch (error) {\n reportError(error);\n }\n };\n\n report();\n return subscribe(report);\n }, []);\n\n useEffect(() => {\n initCookieConsent({ market });\n }, []);\n\n const value = useMemo(\n () => ({ consent, openSettings: openCookieSettings }),\n [consent],\n );\n\n // `navigator` is only read once `ready`, which is never on the server.\n const open =\n ready &&\n (settingsOpen ||\n (consent === null && autoOpen && !isCrawler(navigator.userAgent)));\n\n return (\n <CookieConsentContext.Provider value={value}>\n {children}\n {ready && (\n <ConsentDialog\n open={open}\n consent={consent}\n copy={copy}\n resolveHref={resolveHref}\n onAcceptAll={() => setConsent(\"all\")}\n onAcceptNecessary={() => setConsent([])}\n onSave={setConsent}\n onClose={closeCookieSettings}\n />\n )}\n </CookieConsentContext.Provider>\n );\n}\n\nexport function useCookieConsent(): CookieConsentContextValue {\n const value = useContext(CookieConsentContext);\n if (!value) {\n throw new Error(\n \"useCookieConsent must be used inside a CookieConsentProvider.\",\n );\n }\n return value;\n}\n"],"mappings":"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"}
|