@leabankab/component-library 7.3.0 → 8.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/CHANGELOG.md +745 -0
  2. package/README.md +2 -5
  3. package/dist/component-library.es.js +2948 -5676
  4. package/dist/component-library.es.js.map +1 -1
  5. package/dist/component-library.umd.js +2 -28
  6. package/dist/component-library.umd.js.map +1 -1
  7. package/dist/components/animations/icons/status-icon.d.ts +4 -0
  8. package/dist/components/animations/illustrations/check-animation.d.ts +8 -0
  9. package/dist/components/animations/illustrations/pulse-animation.d.ts +5 -1
  10. package/dist/components/animations/illustrations/status-animation.d.ts +8 -1
  11. package/dist/components/animations/shared.d.ts +6 -32
  12. package/dist/components/animations/spinner.d.ts +18 -0
  13. package/dist/components/animations/use-animation-labels.d.ts +0 -5
  14. package/dist/components/buttons/action-button.d.ts +8 -10
  15. package/dist/components/buttons/button-base.d.ts +26 -12
  16. package/dist/components/buttons/button.d.ts +2 -2
  17. package/dist/components/buttons/close-button.d.ts +1 -1
  18. package/dist/components/buttons/icon-button.d.ts +1 -13
  19. package/dist/components/buttons/skip-link.d.ts +21 -4
  20. package/dist/components/color-roles.d.ts +8 -0
  21. package/dist/components/data-display/card.d.ts +54 -4
  22. package/dist/components/data-display/chip.d.ts +50 -8
  23. package/dist/components/data-display/data-field.d.ts +15 -4
  24. package/dist/components/data-display/tooltip.d.ts +74 -22
  25. package/dist/components/feedback/alert-parts.d.ts +10 -9
  26. package/dist/components/feedback/alert.d.ts +1 -1
  27. package/dist/components/feedback/simple-dialog.d.ts +1 -1
  28. package/dist/components/form/amount-slider.d.ts +68 -0
  29. package/dist/components/form/autocomplete.d.ts +56 -59
  30. package/dist/components/form/checkbox-box.d.ts +11 -0
  31. package/dist/components/form/checkbox.d.ts +67 -7
  32. package/dist/components/form/combo-field.d.ts +68 -0
  33. package/dist/components/form/email-input.d.ts +28 -26
  34. package/dist/components/form/field-parts.d.ts +50 -0
  35. package/dist/components/form/index.d.ts +3 -9
  36. package/dist/components/form/input.d.ts +57 -13
  37. package/dist/components/form/multi-select.d.ts +12 -14
  38. package/dist/components/form/number-input-unit.d.ts +28 -0
  39. package/dist/components/form/number-input.d.ts +46 -18
  40. package/dist/components/form/phone-number-input/phone-number-input.d.ts +44 -14
  41. package/dist/components/form/radio-card.d.ts +94 -43
  42. package/dist/components/form/radio-circle.d.ts +14 -13
  43. package/dist/components/form/radio-group-root.d.ts +88 -0
  44. package/dist/components/form/radio.d.ts +68 -24
  45. package/dist/components/form/select-field.d.ts +78 -0
  46. package/dist/components/form/select.d.ts +30 -42
  47. package/dist/components/form/slider-parts.d.ts +34 -0
  48. package/dist/components/form/slider.d.ts +40 -16
  49. package/dist/components/form/use-autocomplete-labels.d.ts +3 -16
  50. package/dist/components/form/use-blur-outside.d.ts +7 -0
  51. package/dist/components/form/use-focus-target-ref.d.ts +11 -0
  52. package/dist/components/form/use-tooltip-trigger-labels.d.ts +5 -0
  53. package/dist/components/index.d.ts +0 -1
  54. package/dist/components/navigation/form-stepper.d.ts +1 -1
  55. package/dist/components/navigation/index.d.ts +1 -1
  56. package/dist/components/navigation/linear-form-stepper.d.ts +1 -1
  57. package/dist/components/navigation/progress-circle.d.ts +14 -8
  58. package/dist/components/navigation/tabs.d.ts +37 -34
  59. package/dist/components/navigation/use-stepper-labels.d.ts +1 -1
  60. package/dist/components/typography/link.d.ts +25 -16
  61. package/dist/components/typography/lists.d.ts +20 -23
  62. package/dist/components/typography/text.d.ts +46 -10
  63. package/dist/components/ui/app-header.d.ts +9 -1
  64. package/dist/cookie-consent/react.d.ts +0 -1
  65. package/dist/cookie-consent-react.cjs +1 -1
  66. package/dist/cookie-consent-react.cjs.map +1 -1
  67. package/dist/cookie-consent-react.mjs +106 -96
  68. package/dist/cookie-consent-react.mjs.map +1 -1
  69. package/dist/cookie-consent.css +1 -1
  70. package/dist/date-picker/datepicker.d.ts +140 -0
  71. package/dist/date-picker/index.d.ts +18 -0
  72. package/dist/date-picker/theme/field-base.d.ts +23 -0
  73. package/dist/date-picker/theme/form-control.theme.d.ts +7 -0
  74. package/dist/date-picker/theme/index.d.ts +12 -0
  75. package/dist/date-picker/theme/input.theme.d.ts +6 -0
  76. package/dist/{components/form/datepicker → date-picker}/use-view-toggle-labels.d.ts +1 -1
  77. package/dist/date-picker.cjs +2 -0
  78. package/dist/date-picker.cjs.map +1 -0
  79. package/dist/date-picker.mjs +1012 -0
  80. package/dist/date-picker.mjs.map +1 -0
  81. package/dist/{components/dev-panel → dev-panel}/dev-panel-payload.d.ts +1 -1
  82. package/dist/{components/dev-panel → dev-panel}/dev-panel-status.d.ts +2 -2
  83. package/dist/{components/dev-panel → dev-panel}/dev-panel-steps.d.ts +2 -1
  84. package/dist/dev-panel/dev-panel.d.ts +118 -0
  85. package/dist/dev-panel/index.d.ts +10 -0
  86. package/dist/{components/dev-panel → dev-panel}/json-highlight.d.ts +2 -2
  87. package/dist/dev-panel/parts.d.ts +9 -0
  88. package/dist/{components/dev-panel → dev-panel}/segmented-control.d.ts +5 -4
  89. package/dist/{components/dev-panel → dev-panel}/use-dev-panel-labels.d.ts +1 -1
  90. package/dist/{components/dev-panel → dev-panel}/use-roving-radio.d.ts +2 -2
  91. package/dist/dev-panel.cjs +3 -0
  92. package/dist/dev-panel.cjs.map +1 -0
  93. package/dist/dev-panel.css +1 -0
  94. package/dist/dev-panel.mjs +919 -0
  95. package/dist/dev-panel.mjs.map +1 -0
  96. package/dist/fonts/ClashGrotesk-Variable.woff2 +0 -0
  97. package/dist/index.css +1 -1
  98. package/dist/index.d.ts +1 -3
  99. package/dist/provider/lea-provider.d.ts +4 -7
  100. package/dist/react-hook-form/form-field.d.ts +76 -0
  101. package/dist/react-hook-form/index.d.ts +1 -0
  102. package/dist/react-hook-form.cjs +2 -0
  103. package/dist/react-hook-form.cjs.map +1 -0
  104. package/dist/react-hook-form.mjs +33 -0
  105. package/dist/react-hook-form.mjs.map +1 -0
  106. package/dist/tailwind.css +103 -17
  107. package/dist/theme/index.d.ts +0 -4
  108. package/dist/theme/token-types.d.ts +15 -3
  109. package/dist/theme/tokens/colors.d.ts +13 -0
  110. package/dist/theme/tokens/font-family.d.ts +9 -0
  111. package/dist/theme/tokens/font-sizes.d.ts +8 -0
  112. package/dist/theme/tokens/index.d.ts +4 -1
  113. package/dist/theme/tokens/line-heights.d.ts +11 -0
  114. package/dist/theme/tokens/spacing.d.ts +6 -0
  115. package/dist/theme/tokens/text-styles.d.ts +61 -0
  116. package/dist/theme/tokens/type-scale.d.ts +21 -0
  117. package/dist/theme/types.d.ts +19 -30
  118. package/dist/validation/email.d.ts +18 -0
  119. package/dist/validation/mobile-patterns.d.ts +10 -0
  120. package/dist/validation/national-id.d.ts +26 -0
  121. package/dist/validation/phone.d.ts +30 -0
  122. package/package.json +45 -6
  123. package/dist/components/dev-panel/dev-panel.d.ts +0 -61
  124. package/dist/components/dev-panel/index.d.ts +0 -9
  125. package/dist/components/form/datepicker/datepicker.d.ts +0 -99
  126. package/dist/components/form/datepicker/index.d.ts +0 -1
  127. package/dist/components/form/fields/autocomplete-field.d.ts +0 -17
  128. package/dist/components/form/fields/boolean-radio-field.d.ts +0 -3
  129. package/dist/components/form/fields/checkbox-field.d.ts +0 -12
  130. package/dist/components/form/fields/datepicker-field.d.ts +0 -9
  131. package/dist/components/form/fields/email-field.d.ts +0 -10
  132. package/dist/components/form/fields/field-types.d.ts +0 -18
  133. package/dist/components/form/fields/index.d.ts +0 -14
  134. package/dist/components/form/fields/input-field.d.ts +0 -9
  135. package/dist/components/form/fields/multi-select-field.d.ts +0 -9
  136. package/dist/components/form/fields/number-field.d.ts +0 -10
  137. package/dist/components/form/fields/phone-number-field.d.ts +0 -11
  138. package/dist/components/form/fields/radio-card-field.d.ts +0 -22
  139. package/dist/components/form/fields/radio-field.d.ts +0 -16
  140. package/dist/components/form/fields/select-field.d.ts +0 -9
  141. package/dist/components/form/fields/use-field-control.d.ts +0 -6
  142. package/dist/components/form/fields/use-tooltip-trigger-labels.d.ts +0 -5
  143. package/dist/components/form/form-control/form-control-context.d.ts +0 -35
  144. package/dist/components/form/form-control/form-control-label.d.ts +0 -1
  145. package/dist/components/form/form-control/form-control.d.ts +0 -13
  146. package/dist/components/form/form-control/form-helper-text.d.ts +0 -9
  147. package/dist/components/form/form-control/form-label.d.ts +0 -9
  148. package/dist/components/form/form-control/index.d.ts +0 -5
  149. package/dist/components/form/input-label.d.ts +0 -9
  150. package/dist/theme/components/accordion.theme.d.ts +0 -7
  151. package/dist/theme/components/action-button.theme.d.ts +0 -45
  152. package/dist/theme/components/alert.theme.d.ts +0 -2
  153. package/dist/theme/components/autocomplete.theme.d.ts +0 -2
  154. package/dist/theme/components/baseline-css.d.ts +0 -2
  155. package/dist/theme/components/button.theme.d.ts +0 -2
  156. package/dist/theme/components/card.theme.d.ts +0 -2
  157. package/dist/theme/components/checkbox.theme.d.ts +0 -2
  158. package/dist/theme/components/chip.theme.d.ts +0 -2
  159. package/dist/theme/components/dev-panel.theme.d.ts +0 -95
  160. package/dist/theme/components/dialog.theme.d.ts +0 -2
  161. package/dist/theme/components/drawer.theme.d.ts +0 -4
  162. package/dist/theme/components/field-base.d.ts +0 -31
  163. package/dist/theme/components/form-control-label.theme.d.ts +0 -2
  164. package/dist/theme/components/form-control.theme.d.ts +0 -2
  165. package/dist/theme/components/index.d.ts +0 -2
  166. package/dist/theme/components/input.theme.d.ts +0 -3
  167. package/dist/theme/components/link.theme.d.ts +0 -2
  168. package/dist/theme/components/menu-item.theme.d.ts +0 -2
  169. package/dist/theme/components/number-input.theme.d.ts +0 -23
  170. package/dist/theme/components/phone-number-input.theme.d.ts +0 -43
  171. package/dist/theme/components/radio.theme.d.ts +0 -2
  172. package/dist/theme/components/select.theme.d.ts +0 -2
  173. package/dist/theme/components/slider.theme.d.ts +0 -2
  174. package/dist/theme/components/tabs.theme.d.ts +0 -34
  175. package/dist/theme/components/tooltip.theme.d.ts +0 -110
  176. package/dist/theme/components/typography.theme.d.ts +0 -2
  177. package/dist/theme/mui-augmentation.d.ts +0 -147
  178. package/dist/theme/sx-config.d.ts +0 -10
  179. /package/dist/{components/form/datepicker → date-picker}/calendar-header.d.ts +0 -0
  180. /package/dist/{components/form/datepicker → date-picker}/drawer-toolbar.d.ts +0 -0
  181. /package/dist/{theme/components → date-picker/theme}/date-picker.theme.d.ts +0 -0
  182. /package/dist/{theme/components → date-picker/theme}/icon-button.theme.d.ts +0 -0
  183. /package/dist/{theme/tokens → date-picker/theme}/typography.d.ts +0 -0
  184. /package/dist/{components/form/datepicker → date-picker}/use-calendar-header-labels.d.ts +0 -0
  185. /package/dist/{components/form/datepicker → date-picker}/use-datepicker-title-labels.d.ts +0 -0
  186. /package/dist/{components/form/datepicker → date-picker}/view-renderers.d.ts +0 -0
  187. /package/dist/{components/form/datepicker → date-picker}/view-toggle.d.ts +0 -0
  188. /package/dist/{components/dev-panel → dev-panel}/dev-panel-country.d.ts +0 -0
  189. /package/dist/{components/dev-panel → dev-panel}/dev-panel-flow.d.ts +0 -0
  190. /package/dist/{components/dev-panel → dev-panel}/dev-panel-footer.d.ts +0 -0
  191. /package/dist/{components/dev-panel → dev-panel}/dev-panel-step-state.d.ts +0 -0
  192. /package/dist/{components/dev-panel → dev-panel}/dev-panel-variant.d.ts +0 -0
  193. /package/dist/{components/dev-panel → dev-panel}/use-copy.d.ts +0 -0
@@ -1,23 +1,32 @@
1
- import { LinkProps as MuiLinkProps } from '@mui/material';
2
- import { ElementType } from 'react';
3
- type LinkColor = "black" | "purple" | "teal" | "white";
4
- type LinkProps<C extends ElementType = "a"> = Omit<MuiLinkProps<C>, "color"> & {
5
- /** The component to render the link as. */
6
- component?: C;
7
- /** The colour of the link. */
1
+ import { useRender } from '@base-ui/react/use-render';
2
+ import { Ref } from 'react';
3
+ import { ColorRoles } from '../color-roles';
4
+ export type LinkColor = ColorRoles<"secondary" | "primary" | "tertiary" | "contrast">;
5
+ export type LinkProps = Omit<useRender.ComponentProps<"a">, "ref" | "color"> & {
6
+ /**
7
+ * The colour of the link: `secondary` is the near-black of body text,
8
+ * `primary` the brand purple, `tertiary` the brand teal. Use `contrast`
9
+ * (white) on a dark surface.
10
+ * @default "secondary"
11
+ */
8
12
  color?: LinkColor;
13
+ /** The rendered element: the `<a>`, or the element passed to `render`. */
14
+ ref?: Ref<HTMLElement>;
9
15
  };
10
16
  /**
11
- * A link component — link-only in styling and behaviour.
12
- *
13
- * Supports polymorphic rendering via the `component` prop, which enables
14
- * type-safe usage with any router link component:
17
+ * A link inside running text: underlined, so it is told apart from the words
18
+ * around it by more than its colour, and in the size and line height of the
19
+ * text around it. Renders an `a`; pass a router link
20
+ * through `render`:
15
21
  *
16
22
  * ```tsx
17
- * <Link href="/about">About</Link> // Plain anchor
18
- * <Link component={RouterLink} to="/about">About</Link> // React Router
19
- * <Link component={NextLink} href="/about">About</Link> // Next.js
23
+ * <Link href="/om-oss">Om oss</Link>
24
+ * <Link render={<NextLink href="/om-oss" />}>Om oss</Link>
25
+ * <Link render={<RouterLink to="/om-oss" />}>Om oss</Link>
20
26
  * ```
27
+ *
28
+ * For an action that only looks like a link, render a button:
29
+ * `<Link render={<button type="button" />} onClick={…}>`. For a link that
30
+ * looks like a button, use `Button` with `render`.
21
31
  */
22
- export declare const Link: <C extends ElementType = "a">({ color, children, sx, ...rest }: LinkProps<C>) => import("react").JSX.Element;
23
- export {};
32
+ export declare function Link({ color, render, ref, className, ...rest }: LinkProps): import('react').ReactElement<unknown, string | import('react').JSXElementConstructor<any>>;
@@ -1,5 +1,4 @@
1
- import { SxProps, Theme } from '@mui/material';
2
- import { ReactNode } from 'react';
1
+ import { ComponentProps, ReactNode } from 'react';
3
2
  /**
4
3
  * List is a composition component for rendering ordered or unordered lists.
5
4
  *
@@ -25,30 +24,28 @@ import { ReactNode } from 'react';
25
24
  * </List.Root>
26
25
  */
27
26
  type ListVariant = "ul" | "ol";
28
- interface RootProps {
29
- /** The type of list to render. */
27
+ type RootProps = Omit<ComponentProps<"ul">, "ref"> & {
28
+ /**
29
+ * The type of list to render.
30
+ * @default "ul"
31
+ */
30
32
  variant?: ListVariant;
31
- /** The content of the list. */
32
- children: ReactNode;
33
- /** The style of the list. */
34
- sx?: SxProps<Theme>;
35
- /** The class name of the list. */
36
- className?: string;
37
- /** The gap between the list items. */
33
+ /**
34
+ * The gap between the list items, as a multiple of the spacing step.
35
+ * @default 1.5
36
+ */
38
37
  gap?: number;
39
- }
40
- export declare const Root: ({ variant, children, sx, gap, ...rest }: RootProps) => import("react").JSX.Element;
41
- interface ItemProps {
38
+ };
39
+ export declare const Root: ({ variant, gap, className, style, children, ...rest }: RootProps) => import("react").JSX.Element;
40
+ type ItemProps = Omit<ComponentProps<"li">, "ref"> & {
42
41
  /** Custom marker rendered in place of the default dot/number. */
43
42
  icon?: ReactNode;
44
- /** The content of the list item. */
45
- children: ReactNode;
46
- /** The style of the list item. */
47
- sx?: SxProps<Theme>;
48
- /** The class name of the list item. */
49
- className?: string;
50
- /** The gap between the list items. */
43
+ /**
44
+ * The gap between a custom `icon` and the text, as a multiple of the spacing
45
+ * step. The dot and the number keep their own spacing.
46
+ * @default 1.5
47
+ */
51
48
  gap?: number;
52
- }
53
- export declare const Item: ({ icon, children, sx, gap, ...rest }: ItemProps) => import("react").JSX.Element;
49
+ };
50
+ export declare const Item: ({ icon, gap, className, style, children, ...rest }: ItemProps) => import("react").JSX.Element;
54
51
  export {};
@@ -1,18 +1,54 @@
1
- import { fontSizes, fontWeights } from '../../theme/tokens';
2
- import { TypographyProps } from '@mui/material';
1
+ import { useRender } from '@base-ui/react/use-render';
2
+ import { Ref } from 'react';
3
+ import { ColorPath } from '../../theme/token-types';
4
+ import { fontSizes } from '../../theme/tokens/font-sizes';
5
+ import { fontWeights } from '../../theme/tokens/font-weights';
6
+ import { TextStyleToken } from '../../theme/tokens/text-styles';
3
7
  type FontSizeToken = keyof typeof fontSizes;
4
8
  type FontWeightToken = keyof typeof fontWeights;
5
- type TextProps = TypographyProps & {
9
+ /** A palette token such as `text.subtle`, or `inherit` for the parent's colour. */
10
+ export type TextColor = ColorPath | "inherit";
11
+ export type TextAlign = "start" | "center" | "end";
12
+ export type TextProps = Omit<useRender.ComponentProps<"p">, "ref" | "color"> & {
13
+ /**
14
+ * A text style such as `heading-lg` or `body-sm`: size, weight, line height
15
+ * and letter spacing as one token. It says how the text looks, not which
16
+ * element it is, so pick the element with `render` from the page hierarchy.
17
+ * @default "body-md"
18
+ */
19
+ textStyle?: TextStyleToken;
20
+ /** A font weight token, when the text style's own weight does not fit. */
6
21
  fontWeight?: FontWeightToken;
7
- component?: string;
22
+ /**
23
+ * A colour token from the palette, written as its path. Use `inherit` to
24
+ * take the colour of the surrounding text.
25
+ * @default "text.base"
26
+ */
27
+ color?: TextColor;
28
+ /** Text alignment, following the reading direction. */
29
+ align?: TextAlign;
30
+ /**
31
+ * @deprecated Use `textStyle`. The `fontSize` scale (`xs` to `6xl`) is from
32
+ * before the text styles and is removed in a later major.
33
+ */
8
34
  fontSize?: FontSizeToken;
35
+ /** The rendered element: the `<p>`, or the element passed to `render`. */
36
+ ref?: Ref<HTMLElement>;
9
37
  };
10
38
  /**
11
- * A token-aware wrapper around MUI `Typography`. The `fontSize` and
12
- * `fontWeight` props accept Lea design tokens (resolved to their underlying
13
- * values). `color` is applied through `sx`, so design-token palette paths
14
- * (`text.base`, `purple.subtle`, …) and raw CSS colours work, and the MUI
15
- * shorthands (`primary`, `error`, …) still map to their `.main` shade.
39
+ * Text set in one of Lea's text styles. Renders a `p` with no margin; pass
40
+ * `render` for another element:
41
+ *
42
+ * ```tsx
43
+ * <Text>Vi svarer innen to virkedager.</Text>
44
+ * <Text render={<h1 />} textStyle="heading-lg">Mine lån</Text>
45
+ * <Text render={<span />} textStyle="body-sm" color="text.subtle">
46
+ * Sist oppdatert i dag
47
+ * </Text>
48
+ * ```
49
+ *
50
+ * Spacing around the text belongs to the layout: use `Stack` and its `gap`, or
51
+ * `className` and `style`.
16
52
  */
17
- export declare const Text: ({ children, fontSize, fontWeight, component, color, sx, ...props }: TextProps) => import("react").JSX.Element;
53
+ export declare function Text({ textStyle, fontWeight, color, align, fontSize, render, ref, className, style, ...rest }: TextProps): import('react').ReactElement<unknown, string | import('react').JSXElementConstructor<any>>;
18
54
  export {};
@@ -17,5 +17,13 @@ export type AppHeaderProps = {
17
17
  * is centred.
18
18
  */
19
19
  children?: ReactNode;
20
+ /**
21
+ * The logo to display in the header.
22
+ */
23
+ logo?: ReactNode;
24
+ /**
25
+ * Additional classes to apply to the header.
26
+ */
27
+ className?: string;
20
28
  };
21
- export declare const AppHeader: ({ brandDivider, fluid, children, }: AppHeaderProps) => import("react").JSX.Element;
29
+ export declare const AppHeader: ({ brandDivider, fluid, children, logo, className, }: AppHeaderProps) => import("react").JSX.Element;
@@ -2,7 +2,6 @@ import { ReactNode } from 'react';
2
2
  import { ConsentState, Market } from './constants';
3
3
  import { CookieConsentCopy } from './copy';
4
4
  import { ResolveHref } from './strapi-blocks';
5
- import "../theme/tokens.css";
6
5
  export interface CookieConsentContextValue {
7
6
  /** `null` until the visitor has made a valid choice, and on the server. */
8
7
  consent: ConsentState | null;
@@ -1,2 +1,2 @@
1
- "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});require('./cookie-consent.css');const e=require("./cookie-consent-core-C4MP7GlV.cjs");let t=require("react"),n=require("@base-ui/react/button"),r=require("@base-ui/react/use-render"),i=require("react/jsx-runtime"),a=require("@base-ui/react/dialog"),o=require("lucide-react"),s=require("@base-ui/react/switch"),c=require("@base-ui/react/accordion");var l=(e,t)=>n=>[e,typeof t==`function`?t(n):t].filter(Boolean).join(` `),u={Button:`_Button_sgjjo_9`,IconButton:`_IconButton_sgjjo_140`,Spinner:`_Spinner_sgjjo_167`,"button-spin":`_button-spin_sgjjo_1`,SrOnly:`_SrOnly_sgjjo_185`},d=e=>(0,t.isValidElement)(e)&&(e.props.href!=null||e.props.to!=null),f=e=>e.preventDefault(),p=(0,t.forwardRef)(function({variant:e=`solid`,color:t=`accent`,size:a=`md`,loading:o=!1,disabled:s,className:c,render:p,nativeButton:m,focusableWhenDisabled:h,...g},_){let v=!!(s||o),y=d(p),b={"aria-busy":o||void 0,"data-variant":e,"data-color":t,"data-size":a,"data-loading":o||void 0};return(0,r.useRender)({enabled:y,render:y?p:void 0,ref:_,defaultTagName:`a`,props:{...g,...b,"aria-disabled":v||void 0,"data-disabled":v?``:void 0,className:l(u.Button,c)({disabled:v}),onClick:v?f:g.onClick}})||(0,i.jsx)(n.Button,{...g,...b,ref:_,render:p,nativeButton:m,focusableWhenDisabled:h,disabled:v,className:l(u.Button,c)})}),m=(0,t.forwardRef)(function({startIcon:e,endIcon:t,loading:n=!1,loadingLabel:r,children:a,...o},s){return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)(p,{...o,ref:s,loading:n,"data-start-icon":n||e!=null||void 0,"data-end-icon":t!=null||void 0,children:[n?(0,i.jsx)(`span`,{className:u.Spinner,"aria-hidden":!0}):e,a,t]}),r!=null&&(0,i.jsx)(`span`,{role:`status`,className:u.SrOnly,children:n?r:null})]})}),h={Backdrop:`_Backdrop_1tudt_8`,Viewport:`_Viewport_1tudt_22`,Popup:`_Popup_1tudt_38`,Header:`_Header_1tudt_78`,Title:`_Title_1tudt_88`,Description:`_Description_1tudt_96`,Content:`_Content_1tudt_110`,Footer:`_Footer_1tudt_118`,CloseButton:`_CloseButton_1tudt_134`},g=(0,t.createContext)(`no`),_=()=>(0,t.useContext)(g);function v(e){return function(){let t=_(),n={};return Object.keys(e).forEach(r=>{let i=e[r];n[r]=i[t]??i.no}),n}}var y=v({close:{no:`Lukk`,se:`Stäng`,fi:`Sulje`,es:`Cerrar`}}),b=a.Dialog.Root;a.Dialog.Trigger;var x=a.Dialog.Portal;function S({className:e,...t}){return(0,i.jsx)(a.Dialog.Backdrop,{...t,className:l(h.Backdrop,e)})}function C({className:e,...t}){return(0,i.jsx)(a.Dialog.Viewport,{...t,className:l(h.Viewport,e)})}function w({size:e=`sm`,fullScreenOnMobile:t,className:n,...r}){return(0,i.jsx)(a.Dialog.Popup,{...r,"data-size":e,"data-full-screen-on-mobile":t||void 0,className:l(h.Popup,n)})}function T({className:e,...t}){return(0,i.jsx)(`div`,{...t,className:[h.Header,e].filter(Boolean).join(` `)})}function E({className:e,...t}){return(0,i.jsx)(a.Dialog.Title,{...t,className:l(h.Title,e)})}function D({className:e,...t}){return(0,i.jsx)(a.Dialog.Description,{...t,className:l(h.Description,e)})}function O({className:e,...t}){return(0,i.jsx)(`div`,{...t,className:[h.Content,e].filter(Boolean).join(` `)})}function k({className:e,...t}){return(0,i.jsx)(`div`,{...t,className:[h.Footer,e].filter(Boolean).join(` `)})}a.Dialog.Close;function A({label:e,className:t,...n}){let r=y();return(0,i.jsx)(a.Dialog.Close,{...n,"aria-label":e??r.close,className:l(h.CloseButton,t),children:(0,i.jsx)(o.X,{size:20,"aria-hidden":!0})})}var j={Switch:`_Switch_d3xdl_7`,Thumb:`_Thumb_d3xdl_36`};function M({className:e,...t}){return(0,i.jsx)(s.Switch.Root,{...t,className:l(j.Switch,e),children:(0,i.jsx)(s.Switch.Thumb,{className:j.Thumb})})}var N={Root:`_Root_i17mg_13`,Item:`_Item_i17mg_10`,Header:`_Header_i17mg_10`,Trigger:`_Trigger_i17mg_95`,Label:`_Label_i17mg_135`,Icon:`_Icon_i17mg_139`,Panel:`_Panel_i17mg_157`,Content:`_Content_i17mg_168`};function P({variant:e=`outline`,iconPosition:t=`end`,className:n,...r}){return(0,i.jsx)(c.Accordion.Root,{...r,"data-variant":e,"data-icon-position":t,className:l(N.Root,n)})}function F({className:e,...t}){return(0,i.jsx)(c.Accordion.Item,{...t,className:l(N.Item,e)})}function I({className:e,...t}){return(0,i.jsx)(c.Accordion.Header,{...t,className:l(N.Header,e)})}function L({children:e,className:t,...n}){return(0,i.jsxs)(c.Accordion.Trigger,{...n,className:l(N.Trigger,t),children:[(0,i.jsx)(`span`,{className:N.Label,children:e}),(0,i.jsx)(o.ChevronDown,{className:N.Icon,size:20,"aria-hidden":!0})]})}function R({children:e,className:t,...n}){return(0,i.jsx)(c.Accordion.Panel,{...n,className:l(N.Panel,t),children:(0,i.jsx)(`div`,{className:N.Content,children:e})})}var z={Intro:`_Intro_1lxh4_7`,Categories:`_Categories_1lxh4_39`,CategoryDescription:`_CategoryDescription_1lxh4_43`,Actions:`_Actions_1lxh4_49`,Save:`_Save_1lxh4_60`},B=`https://relative.invalid`;function V(e,t){try{return new URL(e,t)}catch{return null}}function H(e){let t=e.trim();if(!t)return!1;let n=V(t);return n?n.protocol===`https:`||n.protocol===`mailto:`:V(t,B)?.origin===B}function U({href:e,resolveHref:t=e=>e,children:n}){let r=t(e);return!r||!H(r)?(0,i.jsx)(i.Fragment,{children:n}):(0,i.jsx)(`a`,{href:r.trim(),target:`_blank`,rel:`noopener noreferrer`,children:n})}function W({node:e}){let t=e.text;return e.underline&&(t=(0,i.jsx)(`u`,{children:t})),e.italic&&(t=(0,i.jsx)(`em`,{children:t})),e.bold&&(t=(0,i.jsx)(`strong`,{children:t})),(0,i.jsx)(i.Fragment,{children:t})}function G({nodes:e,resolveHref:t}){return(0,i.jsx)(i.Fragment,{children:(e??[]).map((e,n)=>e.type===`text`?(0,i.jsx)(W,{node:e},n):e.type===`link`?(0,i.jsx)(U,{href:e.url??``,resolveHref:t,children:(e.children??[]).filter(e=>e.type===`text`).map((e,t)=>(0,i.jsx)(W,{node:e},t))},n):null)})}function K({node:e,resolveHref:t}){let n=e.format===`ordered`?`ol`:`ul`;return(0,i.jsx)(n,{children:(e.children??[]).map((e,n)=>e.type===`list`?(0,i.jsx)(`li`,{children:(0,i.jsx)(K,{node:e,resolveHref:t})},n):e.type===`list-item`?(0,i.jsx)(`li`,{children:(0,i.jsx)(G,{nodes:e.children,resolveHref:t})},n):null)})}function q({nodes:e,resolveHref:t}){return(0,i.jsx)(i.Fragment,{children:e.map((e,n)=>{switch(e.type){case`paragraph`:return(0,i.jsx)(`p`,{children:(0,i.jsx)(G,{nodes:e.children,resolveHref:t})},n);case`heading`:return(0,i.jsx)(`p`,{children:(0,i.jsx)(`strong`,{children:(0,i.jsx)(G,{nodes:e.children,resolveHref:t})})},n);case`list`:return(0,i.jsx)(K,{node:e,resolveHref:t},n);default:return null}})})}function J(e){return{necessary:!0,preferences:e?.preferences??!1,analytics:e?.analytics??!1,marketing:e?.marketing??!1}}function Y({category:e,copy:n,checked:r,onCheckedChange:a}){let o=(0,t.useId)(),s=(0,t.useId)(),{title:c,description:l}=n.categories[e];return(0,i.jsxs)(F,{value:e,children:[(0,i.jsxs)(I,{children:[(0,i.jsx)(L,{children:(0,i.jsx)(`span`,{id:o,children:c})}),(0,i.jsx)(M,{"aria-labelledby":o,"aria-describedby":l?s:void 0,checked:r,disabled:e===`necessary`,onCheckedChange:a})]}),(0,i.jsx)(R,{keepMounted:!0,children:l&&(0,i.jsx)(`p`,{id:s,className:z.CategoryDescription,children:l})})]})}function X({open:n,consent:r,copy:a,resolveHref:o,onAcceptAll:s,onAcceptNecessary:c,onSave:l,onClose:u}){let d=r!==null,[f,p]=(0,t.useState)(()=>J(r)),[h,g]=(0,t.useState)(n);return n!==h&&(g(n),n&&p(J(r))),(0,i.jsx)(b,{open:n,onOpenChange:e=>{!e&&d&&u()},disablePointerDismissal:!d,children:(0,i.jsxs)(x,{children:[(0,i.jsx)(S,{}),(0,i.jsx)(C,{children:(0,i.jsxs)(w,{fullScreenOnMobile:!0,size:`md`,children:[(0,i.jsxs)(T,{children:[(0,i.jsx)(E,{children:a.modalTitle}),d&&(0,i.jsx)(A,{label:a.closeIconLabel})]}),(0,i.jsxs)(O,{children:[(0,i.jsx)(D,{render:(0,i.jsx)(`div`,{}),className:z.Intro,children:(0,i.jsx)(q,{nodes:a.intro,resolveHref:o})}),(0,i.jsx)(P,{variant:`solid`,iconPosition:`start`,multiple:!0,className:z.Categories,children:e.l.map(e=>(0,i.jsx)(Y,{category:e,copy:a,checked:f[e],onCheckedChange:t=>p(n=>({...n,[e]:t}))},e))})]}),(0,i.jsxs)(k,{className:z.Actions,children:[(0,i.jsx)(m,{onClick:s,color:`neutral`,size:`sm`,children:a.btnAcceptAll}),(0,i.jsx)(m,{onClick:c,color:`neutral`,size:`sm`,children:a.btnAcceptNecessary}),(0,i.jsx)(m,{variant:`outline`,color:`neutral`,size:`sm`,className:z.Save,onClick:()=>l(e.l.filter(e=>f[e])),children:a.btnSaveSettings})]})]})})]})})}var Z=(0,t.createContext)(null);function Q({market:n,copy:r,resolveHref:a,children:o}){let{consent:s,settingsOpen:c,ready:l}=(0,t.useSyncExternalStore)(e.c,e.i,e.r);(0,t.useEffect)(()=>{e.a({market:n})},[]);let u=(0,t.useMemo)(()=>({consent:s,openSettings:e.o}),[s]);return(0,i.jsxs)(Z.Provider,{value:u,children:[o,l&&(0,i.jsx)(X,{open:c,consent:s,copy:r,resolveHref:a,onAcceptAll:()=>e.s(`all`),onAcceptNecessary:()=>e.s([]),onSave:e.s,onClose:e.t})]})}function $(){let e=(0,t.useContext)(Z);if(!e)throw Error(`useCookieConsent must be used inside a CookieConsentProvider.`);return e}exports.CookieConsentProvider=Q,exports.useCookieConsent=$;
1
+ "use client";Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});require('./cookie-consent.css');const e=require("./cookie-consent-core-C4MP7GlV.cjs");let t=require("react"),n=require("@base-ui/react/button"),r=require("@base-ui/react/use-render"),i=require("react/jsx-runtime"),a=require("@base-ui/react/dialog"),o=require("lucide-react"),s=require("@base-ui/react/switch"),c=require("@base-ui/react/accordion");var l=(e,t)=>n=>[e,typeof t==`function`?t(n):t].filter(Boolean).join(` `),u={Button:`_Button_1d72q_1`,IconButton:`_IconButton_1d72q_125`,Spinner:`_Spinner_1d72q_150`,"button-spin":`_button-spin_1d72q_1`,SrOnly:`_SrOnly_1d72q_168`},d=e=>(0,t.isValidElement)(e)&&(e.props.href!=null||e.props.to!=null),f=e=>e.preventDefault();function p({loading:e=!1,disabled:t,className:a,render:o,nativeButton:s,focusableWhenDisabled:c,ref:l,...u}){let p=!!(t||e),m=d(o),h={"aria-busy":e||void 0,"data-loading":e||void 0};return(0,r.useRender)({enabled:m,render:m?o:void 0,ref:l,defaultTagName:`a`,props:{...u,...h,"aria-disabled":p||void 0,"data-disabled":p?``:void 0,className:typeof a==`function`?a({disabled:p}):a,onClick:p?f:u.onClick}})||(0,i.jsx)(n.Button,{...u,...h,ref:l,render:o,nativeButton:s,focusableWhenDisabled:c,disabled:p,className:a})}function m({variant:e=`solid`,color:t=`primary`,size:n=`md`,className:r,...a}){return(0,i.jsx)(p,{...a,"data-variant":e,"data-color":t,"data-size":n,className:l(u.Button,r)})}function h({startIcon:e,endIcon:t,loading:n=!1,loadingLabel:r,children:a,...o}){return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsxs)(m,{...o,loading:n,"data-start-icon":n||e!=null||void 0,"data-end-icon":t!=null||void 0,children:[n?(0,i.jsx)(`span`,{className:u.Spinner,"aria-hidden":!0}):e,a,t]}),r!=null&&(0,i.jsx)(`span`,{role:`status`,className:u.SrOnly,children:n?r:null})]})}var g={Backdrop:`_Backdrop_1mady_8`,Viewport:`_Viewport_1mady_22`,Popup:`_Popup_1mady_38`,Header:`_Header_1mady_78`,Title:`_Title_1mady_88`,Description:`_Description_1mady_97`,Content:`_Content_1mady_111`,Footer:`_Footer_1mady_119`,CloseButton:`_CloseButton_1mady_135`},_=(0,t.createContext)(`no`),v=()=>(0,t.useContext)(_);function y(e){return function(){let t=v(),n={};return Object.keys(e).forEach(r=>{let i=e[r];n[r]=i[t]??i.no}),n}}var b=y({close:{no:`Lukk`,se:`Stäng`,fi:`Sulje`,es:`Cerrar`}}),x=a.Dialog.Root;a.Dialog.Trigger;var S=a.Dialog.Portal;function C({className:e,...t}){return(0,i.jsx)(a.Dialog.Backdrop,{...t,className:l(g.Backdrop,e)})}function w({className:e,...t}){return(0,i.jsx)(a.Dialog.Viewport,{...t,className:l(g.Viewport,e)})}function T({size:e=`sm`,fullScreenOnMobile:t,className:n,...r}){return(0,i.jsx)(a.Dialog.Popup,{...r,"data-size":e,"data-full-screen-on-mobile":t||void 0,className:l(g.Popup,n)})}function E({className:e,...t}){return(0,i.jsx)(`div`,{...t,className:[g.Header,e].filter(Boolean).join(` `)})}function D({className:e,...t}){return(0,i.jsx)(a.Dialog.Title,{...t,className:l(g.Title,e)})}function O({className:e,...t}){return(0,i.jsx)(a.Dialog.Description,{...t,className:l(g.Description,e)})}function k({className:e,...t}){return(0,i.jsx)(`div`,{...t,className:[g.Content,e].filter(Boolean).join(` `)})}function A({className:e,...t}){return(0,i.jsx)(`div`,{...t,className:[g.Footer,e].filter(Boolean).join(` `)})}a.Dialog.Close;function j({label:e,className:t,...n}){let r=b();return(0,i.jsx)(a.Dialog.Close,{...n,"aria-label":e??r.close,className:l(g.CloseButton,t),children:(0,i.jsx)(o.X,{size:20,"aria-hidden":!0})})}var M={Switch:`_Switch_9c9ji_7`,Thumb:`_Thumb_9c9ji_36`};function N({className:e,...t}){return(0,i.jsx)(s.Switch.Root,{...t,className:l(M.Switch,e),children:(0,i.jsx)(s.Switch.Thumb,{className:M.Thumb})})}var P={Root:`_Root_18gqk_13`,Item:`_Item_18gqk_10`,Header:`_Header_18gqk_10`,Trigger:`_Trigger_18gqk_95`,Label:`_Label_18gqk_136`,Icon:`_Icon_18gqk_140`,Panel:`_Panel_18gqk_158`,Content:`_Content_18gqk_169`};function F({variant:e=`outline`,iconPosition:t=`end`,className:n,...r}){return(0,i.jsx)(c.Accordion.Root,{...r,"data-variant":e,"data-icon-position":t,className:l(P.Root,n)})}function I({className:e,...t}){return(0,i.jsx)(c.Accordion.Item,{...t,className:l(P.Item,e)})}function L({className:e,...t}){return(0,i.jsx)(c.Accordion.Header,{...t,className:l(P.Header,e)})}function R({children:e,className:t,...n}){return(0,i.jsxs)(c.Accordion.Trigger,{...n,className:l(P.Trigger,t),children:[(0,i.jsx)(`span`,{className:P.Label,children:e}),(0,i.jsx)(o.ChevronDown,{className:P.Icon,size:20,"aria-hidden":!0})]})}function z({children:e,className:t,...n}){return(0,i.jsx)(c.Accordion.Panel,{...n,className:l(P.Panel,t),children:(0,i.jsx)(`div`,{className:P.Content,children:e})})}var B={Intro:`_Intro_nxwj4_7`,Categories:`_Categories_nxwj4_39`,CategoryDescription:`_CategoryDescription_nxwj4_43`,Actions:`_Actions_nxwj4_52`,Save:`_Save_nxwj4_63`},V=`https://relative.invalid`;function H(e,t){try{return new URL(e,t)}catch{return null}}function U(e){let t=e.trim();if(!t)return!1;let n=H(t);return n?n.protocol===`https:`||n.protocol===`mailto:`:H(t,V)?.origin===V}function W({href:e,resolveHref:t=e=>e,children:n}){let r=t(e);return!r||!U(r)?(0,i.jsx)(i.Fragment,{children:n}):(0,i.jsx)(`a`,{href:r.trim(),target:`_blank`,rel:`noopener noreferrer`,children:n})}function G({node:e}){let t=e.text;return e.underline&&(t=(0,i.jsx)(`u`,{children:t})),e.italic&&(t=(0,i.jsx)(`em`,{children:t})),e.bold&&(t=(0,i.jsx)(`strong`,{children:t})),(0,i.jsx)(i.Fragment,{children:t})}function K({nodes:e,resolveHref:t}){return(0,i.jsx)(i.Fragment,{children:(e??[]).map((e,n)=>e.type===`text`?(0,i.jsx)(G,{node:e},n):e.type===`link`?(0,i.jsx)(W,{href:e.url??``,resolveHref:t,children:(e.children??[]).filter(e=>e.type===`text`).map((e,t)=>(0,i.jsx)(G,{node:e},t))},n):null)})}function q({node:e,resolveHref:t}){let n=e.format===`ordered`?`ol`:`ul`;return(0,i.jsx)(n,{children:(e.children??[]).map((e,n)=>e.type===`list`?(0,i.jsx)(`li`,{children:(0,i.jsx)(q,{node:e,resolveHref:t})},n):e.type===`list-item`?(0,i.jsx)(`li`,{children:(0,i.jsx)(K,{nodes:e.children,resolveHref:t})},n):null)})}function J({nodes:e,resolveHref:t}){return(0,i.jsx)(i.Fragment,{children:e.map((e,n)=>{switch(e.type){case`paragraph`:return(0,i.jsx)(`p`,{children:(0,i.jsx)(K,{nodes:e.children,resolveHref:t})},n);case`heading`:return(0,i.jsx)(`p`,{children:(0,i.jsx)(`strong`,{children:(0,i.jsx)(K,{nodes:e.children,resolveHref:t})})},n);case`list`:return(0,i.jsx)(q,{node:e,resolveHref:t},n);default:return null}})})}function Y(e){return{necessary:!0,preferences:e?.preferences??!1,analytics:e?.analytics??!1,marketing:e?.marketing??!1}}function X({category:e,copy:n,checked:r,onCheckedChange:a}){let o=(0,t.useId)(),s=(0,t.useId)(),{title:c,description:l}=n.categories[e];return(0,i.jsxs)(I,{value:e,children:[(0,i.jsxs)(L,{children:[(0,i.jsx)(R,{children:(0,i.jsx)(`span`,{id:o,children:c})}),(0,i.jsx)(N,{"aria-labelledby":o,"aria-describedby":l?s:void 0,checked:r,disabled:e===`necessary`,onCheckedChange:a})]}),(0,i.jsx)(z,{keepMounted:!0,children:l&&(0,i.jsx)(`p`,{id:s,className:B.CategoryDescription,children:l})})]})}function Z({open:n,consent:r,copy:a,resolveHref:o,onAcceptAll:s,onAcceptNecessary:c,onSave:l,onClose:u}){let d=r!==null,[f,p]=(0,t.useState)(()=>Y(r)),[m,g]=(0,t.useState)(n);return n!==m&&(g(n),n&&p(Y(r))),(0,i.jsx)(x,{open:n,onOpenChange:e=>{!e&&d&&u()},disablePointerDismissal:!d,children:(0,i.jsxs)(S,{children:[(0,i.jsx)(C,{}),(0,i.jsx)(w,{children:(0,i.jsxs)(T,{fullScreenOnMobile:!0,size:`md`,children:[(0,i.jsxs)(E,{children:[(0,i.jsx)(D,{children:a.modalTitle}),d&&(0,i.jsx)(j,{label:a.closeIconLabel})]}),(0,i.jsxs)(k,{children:[(0,i.jsx)(O,{render:(0,i.jsx)(`div`,{}),className:B.Intro,children:(0,i.jsx)(J,{nodes:a.intro,resolveHref:o})}),(0,i.jsx)(F,{variant:`solid`,iconPosition:`start`,multiple:!0,className:B.Categories,children:e.l.map(e=>(0,i.jsx)(X,{category:e,copy:a,checked:f[e],onCheckedChange:t=>p(n=>({...n,[e]:t}))},e))})]}),(0,i.jsxs)(A,{className:B.Actions,children:[(0,i.jsx)(h,{onClick:s,color:`secondary`,size:`sm`,children:a.btnAcceptAll}),(0,i.jsx)(h,{onClick:c,color:`secondary`,size:`sm`,children:a.btnAcceptNecessary}),(0,i.jsx)(h,{variant:`outline`,color:`secondary`,size:`sm`,className:B.Save,onClick:()=>l(e.l.filter(e=>f[e])),children:a.btnSaveSettings})]})]})})]})})}var Q=(0,t.createContext)(null);function $({market:n,copy:r,resolveHref:a,children:o}){let{consent:s,settingsOpen:c,ready:l}=(0,t.useSyncExternalStore)(e.c,e.i,e.r);(0,t.useEffect)(()=>{e.a({market:n})},[]);let u=(0,t.useMemo)(()=>({consent:s,openSettings:e.o}),[s]);return(0,i.jsxs)(Q.Provider,{value:u,children:[o,l&&(0,i.jsx)(Z,{open:c,consent:s,copy:r,resolveHref:a,onAcceptAll:()=>e.s(`all`),onAcceptNecessary:()=>e.s([]),onSave:e.s,onClose:e.t})]})}function ee(){let e=(0,t.useContext)(Q);if(!e)throw Error(`useCookieConsent must be used inside a CookieConsentProvider.`);return e}exports.CookieConsentProvider=$,exports.useCookieConsent=ee;
2
2
  //# sourceMappingURL=cookie-consent-react.cjs.map
@@ -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.tsx","../src/components/feedback/dialog.module.css","../src/i18n/language-context.tsx","../src/i18n/create-translation.ts","../src/components/feedback/use-close-label.ts","../src/components/feedback/dialog.tsx","../src/components/form/switch.module.css","../src/components/form/switch.tsx","../src/components/surfaces/accordion.module.css","../src/components/surfaces/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","/*\n * Shared by Button, IconButton, CloseButton and DownloadTrigger. Design tokens\n * come from src/theme/tokens.css, so these render styled without a provider.\n *\n * Each colour sets local properties that the variants read, which keeps the\n * variant x colour matrix to one rule per cell.\n */\n\n.Button {\n --button-solid: var(--lea-palette-purple-base);\n --button-solid-hover: var(--lea-palette-purple-hover);\n --button-on-solid: var(--lea-palette-purple-contrast);\n --button-fg: var(--lea-palette-purple-base);\n --button-border: var(--lea-palette-purple-base);\n /* Translucent white, so outline and ghost hovers lighten whatever surface\n the button sits on; the hint of --button-fg keeps them visible on white. */\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 /* Height comes from min-height, so text and icon buttons line up; this only\n pads labels that wrap onto several lines. */\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-font-size-md);\n font-weight: var(--lea-font-weight-medium);\n line-height: 1.25;\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=\"neutral\"] {\n --button-solid: var(--lea-palette-bg-contrast);\n --button-solid-hover: var(--lea-color-neutral-800);\n --button-on-solid: var(--lea-palette-text-contrast);\n --button-fg: var(--lea-palette-text-base);\n --button-border: var(--lea-palette-border-strong);\n}\n\n.Button[data-color=\"danger\"] {\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@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\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/* An icon carries its own visual weight, so its side needs less padding. */\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/* Icon-only: a square the same height as a text button of the same size. */\n\n.Button.IconButton {\n width: var(--button-height);\n height: var(--button-height);\n padding: 0;\n}\n\n/* Disabled is greyscale; loading also disables, but keeps its colour. */\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 forwardRef,\n isValidElement,\n type MouseEvent,\n type ReactElement,\n} from \"react\";\n\nimport { mergeClassName } from \"../../utils/merge-class-name\";\nimport styles from \"./button.module.css\";\n\nexport type ButtonVariant = \"solid\" | \"outline\" | \"ghost\";\nexport type ButtonColor = \"accent\" | \"neutral\" | \"danger\";\nexport type ButtonSize = \"sm\" | \"md\" | \"lg\";\n\nexport type ButtonBaseProps = Omit<BaseButton.Props, \"color\"> & {\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. `\"danger\"` is for destructive actions.\n * @default \"accent\"\n */\n color?: ButtonColor;\n /**\n * @default \"md\"\n */\n size?: ButtonSize;\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};\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: ButtonBaseProps[\"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 * The root shared by `Button`, `IconButton`, `CloseButton` and\n * `DownloadTrigger`. Internal — not exported from the package.\n */\nexport const ButtonBase = forwardRef<HTMLElement, ButtonBaseProps>(\n function ButtonBase(\n {\n variant = \"solid\",\n color = \"accent\",\n size = \"md\",\n loading = false,\n disabled,\n className,\n render,\n nativeButton,\n focusableWhenDisabled,\n ...rest\n },\n ref,\n ) {\n const isDisabled = Boolean(disabled || loading);\n const isLink = isLinkElement(render);\n\n const shared = {\n \"aria-busy\": loading || undefined,\n \"data-variant\": variant,\n \"data-color\": color,\n \"data-size\": size,\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: mergeClassName(styles.Button, className)({\n disabled: isDisabled,\n }),\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={mergeClassName(styles.Button, className)}\n />\n );\n },\n);\n","\"use client\";\n\nimport { forwardRef, 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 * (`accent`, `neutral`, `danger`) 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 const Button = forwardRef<HTMLElement, ButtonProps>(function Button(\n { startIcon, endIcon, loading = false, loadingLabel, children, ...rest },\n ref,\n) {\n return (\n <>\n <ButtonBase\n {...rest}\n ref={ref}\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: rgba(15, 16, 16, 0.5);\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-font-size-lg);\n font-weight: var(--lea-font-weight-semibold);\n line-height: 1.4;\n}\n\n.Description {\n margin: 0 0 calc(var(--lea-spacing) * 2);\n color: var(--lea-palette-text-subtle);\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-purple-base);\n background-color: var(--lea-palette-purple-base);\n color: var(--lea-palette-purple-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-purple-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/* 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 font-size: var(--lea-font-size-md);\n font-weight: var(--lea-font-weight-medium);\n line-height: 1.5;\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-font-size-md);\n line-height: 1.5;\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\";\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 * @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-font-size-md);\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\";\nimport * as Dialog from \"../components/feedback/dialog\";\nimport { Switch } from \"../components/form/switch\";\nimport * as Accordion from \"../components/surfaces/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=\"neutral\" size=\"sm\">\n {copy.btnAcceptAll}\n </Button>\n <Button onClick={onAcceptNecessary} color=\"neutral\" size=\"sm\">\n {copy.btnAcceptNecessary}\n </Button>\n <Button\n variant=\"outline\"\n color=\"neutral\"\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.tsx","../src/components/feedback/dialog.module.css","../src/i18n/language-context.tsx","../src/i18n/create-translation.ts","../src/components/feedback/use-close-label.ts","../src/components/feedback/dialog.tsx","../src/components/form/switch.module.css","../src/components/form/switch.tsx","../src/components/surfaces/accordion.module.css","../src/components/surfaces/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@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\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.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.\n */\nexport function ButtonBase({\n variant = \"solid\",\n color = \"primary\",\n size = \"md\",\n className,\n ...rest\n}: ButtonBaseProps) {\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: var(--lea-palette-text-subtle);\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/* 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@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\";\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 * @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\";\nimport * as Dialog from \"../components/feedback/dialog\";\nimport { Switch } from \"../components/form/switch\";\nimport * as Accordion from \"../components/surfaces/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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"}