@xsolla/xui-gdpr-banner 0.206.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.
@@ -0,0 +1,895 @@
1
+ // src/GdprBanner.tsx
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+
4
+ // ../../foundation/primitives-native/src/Box.tsx
5
+ import {
6
+ View,
7
+ Pressable,
8
+ Image
9
+ } from "react-native";
10
+ import { jsx } from "react/jsx-runtime";
11
+ var roleToAccessibilityRole = {
12
+ button: "button",
13
+ link: "link",
14
+ img: "image",
15
+ image: "image",
16
+ summary: "summary",
17
+ switch: "switch",
18
+ checkbox: "checkbox",
19
+ radio: "radio",
20
+ heading: "header",
21
+ text: "text",
22
+ search: "search",
23
+ alert: "alert",
24
+ none: "none",
25
+ presentation: "none",
26
+ progressbar: "progressbar",
27
+ scrollbar: "scrollbar",
28
+ adjustable: "adjustable",
29
+ tab: "tab",
30
+ menu: "menu",
31
+ menuitem: "menuitem",
32
+ list: "list",
33
+ timer: "timer"
34
+ };
35
+ var toGapValue = (gap) => {
36
+ if (gap === void 0) return void 0;
37
+ if (typeof gap === "number") return gap;
38
+ const pxMatch = /^(\d+(?:\.\d+)?)px$/.exec(gap);
39
+ if (pxMatch) return Number(pxMatch[1]);
40
+ const asNumber = Number(gap);
41
+ return Number.isFinite(asNumber) ? asNumber : void 0;
42
+ };
43
+ var Box = ({
44
+ children,
45
+ onPress,
46
+ onLayout,
47
+ onMoveShouldSetResponder,
48
+ onResponderGrant,
49
+ onResponderMove,
50
+ onResponderRelease,
51
+ onResponderTerminate,
52
+ backgroundColor,
53
+ borderColor,
54
+ borderWidth,
55
+ borderBottomWidth,
56
+ borderBottomColor,
57
+ borderTopWidth,
58
+ borderTopColor,
59
+ borderLeftWidth,
60
+ borderLeftColor,
61
+ borderRightWidth,
62
+ borderRightColor,
63
+ borderRadius,
64
+ borderStyle,
65
+ height,
66
+ padding,
67
+ paddingHorizontal,
68
+ paddingVertical,
69
+ margin,
70
+ marginTop,
71
+ marginBottom,
72
+ marginLeft,
73
+ marginRight,
74
+ flexDirection,
75
+ flexWrap,
76
+ alignItems,
77
+ justifyContent,
78
+ alignSelf,
79
+ position,
80
+ top,
81
+ bottom,
82
+ left,
83
+ right,
84
+ width,
85
+ minWidth,
86
+ minHeight,
87
+ maxWidth,
88
+ maxHeight,
89
+ flex,
90
+ flexShrink,
91
+ gap,
92
+ overflow,
93
+ opacity,
94
+ zIndex,
95
+ hoverStyle,
96
+ pressStyle,
97
+ style,
98
+ disabled,
99
+ "data-testid": dataTestId,
100
+ testID,
101
+ as,
102
+ src,
103
+ alt,
104
+ ...rest
105
+ }) => {
106
+ const getContainerStyle = (pressed) => ({
107
+ backgroundColor: pressed && pressStyle?.backgroundColor ? pressStyle.backgroundColor : backgroundColor,
108
+ borderColor,
109
+ borderWidth,
110
+ borderBottomWidth,
111
+ borderBottomColor,
112
+ borderTopWidth,
113
+ borderTopColor,
114
+ borderLeftWidth,
115
+ borderLeftColor,
116
+ borderRightWidth,
117
+ borderRightColor,
118
+ borderRadius,
119
+ borderStyle,
120
+ overflow,
121
+ opacity: opacity ?? (disabled ? 0.5 : void 0),
122
+ zIndex,
123
+ height,
124
+ width,
125
+ minWidth,
126
+ minHeight,
127
+ maxWidth,
128
+ maxHeight,
129
+ padding,
130
+ paddingHorizontal,
131
+ paddingVertical,
132
+ margin,
133
+ marginTop,
134
+ marginBottom,
135
+ marginLeft,
136
+ marginRight,
137
+ flexDirection,
138
+ flexWrap,
139
+ alignItems,
140
+ justifyContent,
141
+ alignSelf,
142
+ position,
143
+ top,
144
+ bottom,
145
+ left,
146
+ right,
147
+ flex,
148
+ flexShrink,
149
+ gap: toGapValue(gap),
150
+ ...style
151
+ });
152
+ const finalTestID = dataTestId || testID;
153
+ const {
154
+ role,
155
+ tabIndex,
156
+ onKeyDown,
157
+ onKeyUp,
158
+ "aria-label": ariaLabel,
159
+ "aria-labelledby": _ariaLabelledBy,
160
+ "aria-current": _ariaCurrent,
161
+ "aria-disabled": ariaDisabled,
162
+ "aria-live": ariaLive,
163
+ "aria-busy": ariaBusy,
164
+ className,
165
+ "data-testid": _dataTestId,
166
+ cursor: _cursor,
167
+ type: _type,
168
+ ...nativeRest
169
+ } = rest;
170
+ const accessibilityRole = typeof role === "string" && roleToAccessibilityRole[role] || (as === "button" ? "button" : void 0);
171
+ const isDisabled = Boolean(disabled || ariaDisabled);
172
+ const accessibilityState = {
173
+ disabled: isDisabled || void 0,
174
+ busy: Boolean(ariaBusy) || void 0
175
+ };
176
+ const accessibilityLiveRegion = ariaLive === "polite" || ariaLive === "assertive" ? ariaLive : void 0;
177
+ if (as === "img" && src) {
178
+ const imageStyle = {
179
+ width,
180
+ height,
181
+ borderRadius,
182
+ position,
183
+ top,
184
+ bottom,
185
+ left,
186
+ right,
187
+ ...style
188
+ };
189
+ return /* @__PURE__ */ jsx(
190
+ Image,
191
+ {
192
+ source: { uri: src },
193
+ style: imageStyle,
194
+ testID: finalTestID,
195
+ accessibilityLabel: typeof ariaLabel === "string" ? ariaLabel : alt,
196
+ resizeMode: "cover",
197
+ ...nativeRest
198
+ }
199
+ );
200
+ }
201
+ if (onPress || as === "button") {
202
+ return /* @__PURE__ */ jsx(
203
+ Pressable,
204
+ {
205
+ onPress: !isDisabled && onPress ? onPress : void 0,
206
+ disabled: isDisabled,
207
+ onLayout,
208
+ onMoveShouldSetResponder,
209
+ onResponderGrant,
210
+ onResponderMove,
211
+ onResponderRelease,
212
+ onResponderTerminate,
213
+ style: ({ pressed }) => getContainerStyle(pressed),
214
+ testID: finalTestID,
215
+ accessibilityRole,
216
+ accessibilityLabel: typeof ariaLabel === "string" ? ariaLabel : void 0,
217
+ accessibilityState,
218
+ accessibilityLiveRegion,
219
+ ...nativeRest,
220
+ children
221
+ }
222
+ );
223
+ }
224
+ return /* @__PURE__ */ jsx(
225
+ View,
226
+ {
227
+ style: getContainerStyle(),
228
+ testID: finalTestID,
229
+ onLayout,
230
+ onMoveShouldSetResponder,
231
+ onResponderGrant,
232
+ onResponderMove,
233
+ onResponderRelease,
234
+ onResponderTerminate,
235
+ accessibilityRole,
236
+ accessibilityLabel: typeof ariaLabel === "string" ? ariaLabel : void 0,
237
+ accessibilityState,
238
+ accessibilityLiveRegion,
239
+ ...nativeRest,
240
+ children
241
+ }
242
+ );
243
+ };
244
+
245
+ // ../../foundation/primitives-native/src/Text.tsx
246
+ import {
247
+ Text as RNText,
248
+ StyleSheet
249
+ } from "react-native";
250
+ import { jsx as jsx2 } from "react/jsx-runtime";
251
+ var roleMap = {
252
+ alert: "alert",
253
+ heading: "header",
254
+ button: "button",
255
+ link: "link",
256
+ text: "text"
257
+ };
258
+ var parseNumericValue = (value) => {
259
+ if (value === void 0) return void 0;
260
+ if (typeof value === "number") return value;
261
+ const parsed = parseFloat(value);
262
+ return isNaN(parsed) ? void 0 : parsed;
263
+ };
264
+ var Text = ({
265
+ children,
266
+ color,
267
+ fontSize,
268
+ fontWeight,
269
+ fontFamily,
270
+ textAlign,
271
+ lineHeight,
272
+ numberOfLines,
273
+ id,
274
+ role,
275
+ testID,
276
+ "data-testid": dataTestId,
277
+ style: styleProp,
278
+ ...props
279
+ }) => {
280
+ let resolvedFontFamily = fontFamily ? fontFamily.split(",")[0].replace(/['"]/g, "").trim() : void 0;
281
+ if (resolvedFontFamily === "Pilat Wide" || resolvedFontFamily === "Pilat Wide Bold" || resolvedFontFamily === "Aktiv Grotesk") {
282
+ resolvedFontFamily = void 0;
283
+ }
284
+ const incomingStyle = StyleSheet.flatten(styleProp);
285
+ const baseStyle = {
286
+ color: color ?? incomingStyle?.color,
287
+ fontSize: typeof fontSize === "number" ? fontSize : void 0,
288
+ fontWeight,
289
+ fontFamily: resolvedFontFamily,
290
+ textDecorationLine: props.textDecoration,
291
+ textAlign: textAlign ?? incomingStyle?.textAlign,
292
+ lineHeight: parseNumericValue(lineHeight ?? incomingStyle?.lineHeight),
293
+ marginTop: parseNumericValue(
294
+ incomingStyle?.marginTop
295
+ ),
296
+ marginBottom: parseNumericValue(
297
+ incomingStyle?.marginBottom
298
+ )
299
+ };
300
+ const accessibilityRole = role ? roleMap[role] : void 0;
301
+ return /* @__PURE__ */ jsx2(
302
+ RNText,
303
+ {
304
+ style: baseStyle,
305
+ numberOfLines,
306
+ testID: dataTestId || testID || id,
307
+ accessibilityRole,
308
+ children
309
+ }
310
+ );
311
+ };
312
+
313
+ // src/GdprBanner.tsx
314
+ import { useResolvedTheme as useResolvedTheme2, useId } from "@xsolla/xui-core";
315
+ import { Button, FlexButton } from "@xsolla/xui-button";
316
+ import { Remove } from "@xsolla/xui-icons-base";
317
+
318
+ // src/PrivacySettings.tsx
319
+ import { useResolvedTheme } from "@xsolla/xui-core";
320
+ import { Checkbox } from "@xsolla/xui-checkbox";
321
+ import { Link } from "@xsolla/xui-link";
322
+ import { InfoCr } from "@xsolla/xui-icons-base";
323
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
324
+ var PANE_RADIUS = 8;
325
+ var LIST_WIDTH = 280;
326
+ var PrivacySettings = ({
327
+ categories,
328
+ selectedCategoryIds,
329
+ onToggleCategory,
330
+ activeCategoryId,
331
+ onActiveCategoryChange,
332
+ viewport,
333
+ labels,
334
+ cookiePolicyUrl,
335
+ privacyPolicyUrl,
336
+ testID = "gdpr-privacy-settings",
337
+ themeMode,
338
+ themeProductContext
339
+ }) => {
340
+ const { theme } = useResolvedTheme({ themeMode, themeProductContext });
341
+ const isMobile = viewport === "mobile";
342
+ const body = theme.typographyTokens.basic["body-sm"];
343
+ const activeDescription = activeCategoryId === null ? labels.generalInformationDescription : categories.find((category) => category.id === activeCategoryId)?.description ?? "";
344
+ const descriptionPane = /* @__PURE__ */ jsx3(
345
+ Box,
346
+ {
347
+ testID: `${testID}__description`,
348
+ backgroundColor: theme.colors.control.faint.bg,
349
+ borderRadius: PANE_RADIUS,
350
+ padding: 16,
351
+ flex: isMobile ? void 0 : 1,
352
+ width: isMobile ? "100%" : void 0,
353
+ "aria-live": "polite",
354
+ children: /* @__PURE__ */ jsx3(
355
+ Text,
356
+ {
357
+ color: theme.colors.content.secondary,
358
+ fontSize: body.fontSize,
359
+ lineHeight: body.lineHeight,
360
+ fontFamily: theme.fonts.body,
361
+ children: activeDescription
362
+ }
363
+ )
364
+ }
365
+ );
366
+ const focusStyle = {
367
+ outlineColor: theme.colors.border.brand,
368
+ outlineWidth: 2,
369
+ outlineOffset: 2,
370
+ outlineStyle: "solid"
371
+ };
372
+ const list = /* @__PURE__ */ jsxs(
373
+ Box,
374
+ {
375
+ gap: 12,
376
+ width: isMobile ? "100%" : LIST_WIDTH,
377
+ flexShrink: 0,
378
+ testID: `${testID}__categories`,
379
+ children: [
380
+ /* @__PURE__ */ jsxs(Box, { gap: 8, children: [
381
+ /* @__PURE__ */ jsxs(
382
+ Box,
383
+ {
384
+ flexDirection: "row",
385
+ alignItems: "center",
386
+ gap: 8,
387
+ role: "button",
388
+ tabIndex: 0,
389
+ cursor: "pointer",
390
+ "aria-pressed": activeCategoryId === null,
391
+ onPress: () => onActiveCategoryChange(null),
392
+ onKeyDown: (event) => {
393
+ if (event.key === "Enter" || event.key === " ") {
394
+ event.preventDefault();
395
+ onActiveCategoryChange(null);
396
+ }
397
+ },
398
+ focusStyle,
399
+ testID: `${testID}__general-information`,
400
+ children: [
401
+ /* @__PURE__ */ jsx3(
402
+ InfoCr,
403
+ {
404
+ variant: "solid",
405
+ size: 16,
406
+ color: theme.colors.control.link.primary
407
+ }
408
+ ),
409
+ /* @__PURE__ */ jsx3(
410
+ Text,
411
+ {
412
+ color: theme.colors.content.primary,
413
+ fontSize: body.fontSize,
414
+ lineHeight: body.lineHeight,
415
+ fontFamily: theme.fonts.body,
416
+ children: labels.generalInformation
417
+ }
418
+ )
419
+ ]
420
+ }
421
+ ),
422
+ isMobile && activeCategoryId === null && descriptionPane
423
+ ] }),
424
+ categories.map((category) => /* @__PURE__ */ jsxs(Box, { gap: 8, children: [
425
+ /* @__PURE__ */ jsx3(Box, { onPress: () => onActiveCategoryChange(category.id), children: /* @__PURE__ */ jsx3(
426
+ Checkbox,
427
+ {
428
+ size: "md",
429
+ checked: selectedCategoryIds.includes(category.id),
430
+ disabled: category.locked,
431
+ onChange: () => onToggleCategory(category.id),
432
+ testID: `${testID}__category-${category.id}`,
433
+ themeMode,
434
+ themeProductContext,
435
+ children: category.label
436
+ }
437
+ ) }),
438
+ isMobile && activeCategoryId === category.id && descriptionPane
439
+ ] }, category.id))
440
+ ]
441
+ }
442
+ );
443
+ return /* @__PURE__ */ jsxs(Box, { testID, gap: 16, width: "100%", children: [
444
+ /* @__PURE__ */ jsxs(
445
+ Box,
446
+ {
447
+ flexDirection: isMobile ? "column" : "row",
448
+ gap: isMobile ? 12 : 24,
449
+ alignItems: "flex-start",
450
+ width: "100%",
451
+ children: [
452
+ list,
453
+ !isMobile && descriptionPane
454
+ ]
455
+ }
456
+ ),
457
+ /* @__PURE__ */ jsxs(
458
+ Box,
459
+ {
460
+ flexDirection: "row",
461
+ alignItems: "center",
462
+ gap: 8,
463
+ testID: `${testID}__policies`,
464
+ children: [
465
+ /* @__PURE__ */ jsx3(
466
+ Link,
467
+ {
468
+ size: "sm",
469
+ href: cookiePolicyUrl,
470
+ target: "_blank",
471
+ themeMode,
472
+ themeProductContext,
473
+ testID: `${testID}__cookie-policy`,
474
+ children: labels.cookiePolicy
475
+ }
476
+ ),
477
+ /* @__PURE__ */ jsx3(
478
+ Text,
479
+ {
480
+ color: theme.colors.content.tertiary,
481
+ fontSize: body.fontSize,
482
+ lineHeight: body.lineHeight,
483
+ fontFamily: theme.fonts.body,
484
+ "aria-hidden": true,
485
+ children: "\u2022"
486
+ }
487
+ ),
488
+ /* @__PURE__ */ jsx3(
489
+ Link,
490
+ {
491
+ size: "sm",
492
+ href: privacyPolicyUrl,
493
+ target: "_blank",
494
+ themeMode,
495
+ themeProductContext,
496
+ testID: `${testID}__privacy-policy`,
497
+ children: labels.privacyPolicy
498
+ }
499
+ )
500
+ ]
501
+ }
502
+ )
503
+ ] });
504
+ };
505
+ PrivacySettings.displayName = "PrivacySettings";
506
+
507
+ // src/defaults.ts
508
+ var DEFAULT_CATEGORIES = [
509
+ {
510
+ id: "essential",
511
+ label: "Essential cookies",
512
+ locked: true,
513
+ description: "These cookies are required for the Xsolla Ecosystem to function and cannot be disabled in our systems. They are often only set in response to activities taken by you that constitute a request for services, such as changing your privacy settings, logging in, contacting customer support, or filling out forms. You can set your browser to block or inform you about these cookies, but some aspects of the Xsolla Ecosystem will not function properly. These cookies do not contain personally identifying information."
514
+ },
515
+ {
516
+ id: "analytical",
517
+ label: "Analytical and performance cookies",
518
+ description: "These cookies let us count visits and traffic sources so we can measure and improve the performance of the Xsolla Ecosystem. They help us understand which pages are the most and least popular and see how visitors move around the service. All information these cookies collect is aggregated."
519
+ },
520
+ {
521
+ id: "functional",
522
+ label: "Functional cookies",
523
+ description: "These cookies enable the Xsolla Ecosystem to provide enhanced functionality and personalization, such as remembering your preferences and the region you are in. If you do not allow these cookies, some or all of these services may not function properly."
524
+ },
525
+ {
526
+ id: "targeting",
527
+ label: "Targeting cookies",
528
+ description: "These cookies may be set through the Xsolla Ecosystem by our advertising partners to build a profile of your interests and show you relevant offers on other sites. They do not store directly personal information but are based on uniquely identifying your browser and device."
529
+ }
530
+ ];
531
+ var DEFAULT_LABELS = {
532
+ title: "How we use cookies",
533
+ description: "We use cookies to personalize your experience. If you reject the use of cookies, your experience will not be tailored to your preferences.",
534
+ simpleDescription: "We only use essential cookies that allow us to run this service. Read our cookie policy to learn more.",
535
+ privacySettingsTitle: "Privacy settings",
536
+ generalInformation: "General information",
537
+ generalInformationDescription: "We use cookies to personalize your experience, to run and secure the Xsolla Ecosystem, and to measure how it is used. Choose which categories you want to allow below. You can change your choice at any time.",
538
+ customize: "Customize",
539
+ rejectAll: "Reject All",
540
+ acceptAll: "Accept All",
541
+ confirmSelection: "Confirm Selection",
542
+ dismiss: "Dismiss",
543
+ close: "Close",
544
+ cookiePolicy: "Cookie Policy",
545
+ privacyPolicy: "Privacy Policy",
546
+ closeButtonAriaLabel: "Close cookie banner"
547
+ };
548
+ var CARD_WIDTH = 400;
549
+ var EDGE_OFFSET = 20;
550
+ var CONTENT_GAP = 8;
551
+
552
+ // src/GdprBanner.tsx
553
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
554
+ var FOCUSABLE_SELECTORS = 'button:not([disabled]), [href], input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
555
+ var isElementVisible = (element) => {
556
+ if (typeof window === "undefined") return true;
557
+ const style = window.getComputedStyle(element);
558
+ return style.visibility !== "hidden" && style.display !== "none";
559
+ };
560
+ var getFocusableElements = (container) => Array.from(
561
+ container.querySelectorAll(FOCUSABLE_SELECTORS)
562
+ ).filter(isElementVisible);
563
+ var GdprBanner = ({
564
+ type = "complex",
565
+ viewport = "desktop",
566
+ buttonsRow = "single",
567
+ anchor = "bottom-left",
568
+ expanded,
569
+ defaultExpanded = false,
570
+ onExpandedChange,
571
+ categories = DEFAULT_CATEGORIES,
572
+ selectedCategoryIds,
573
+ defaultSelectedCategoryIds,
574
+ onSelectionChange,
575
+ labels,
576
+ cookiePolicyUrl,
577
+ privacyPolicyUrl,
578
+ onAcceptAll,
579
+ onRejectAll,
580
+ onCustomize,
581
+ onConfirmSelection,
582
+ onDismiss,
583
+ onClose,
584
+ showCloseButton = true,
585
+ position = "fixed",
586
+ offset = EDGE_OFFSET,
587
+ testID = "gdpr-banner",
588
+ themeMode,
589
+ themeProductContext
590
+ }) => {
591
+ const { theme } = useResolvedTheme2({ themeMode, themeProductContext });
592
+ const containerRef = useRef(null);
593
+ const rawId = useId();
594
+ const titleId = `gdpr-banner-title-${rawId.replace(/:/g, "")}`;
595
+ const t = useMemo(() => ({ ...DEFAULT_LABELS, ...labels }), [labels]);
596
+ const isMobile = viewport === "mobile";
597
+ const isSimple = type === "simple";
598
+ const allIds = useMemo(
599
+ () => categories.map((category) => category.id),
600
+ [categories]
601
+ );
602
+ const lockedIds = useMemo(
603
+ () => categories.filter((category) => category.locked).map((c) => c.id),
604
+ [categories]
605
+ );
606
+ const isExpandedControlled = expanded !== void 0;
607
+ const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
608
+ const isExpanded = !isSimple && (isExpandedControlled ? expanded : internalExpanded);
609
+ const isSelectionControlled = selectedCategoryIds !== void 0;
610
+ const [internalSelection, setInternalSelection] = useState(
611
+ defaultSelectedCategoryIds ?? allIds
612
+ );
613
+ const selection = isSelectionControlled ? selectedCategoryIds : internalSelection;
614
+ const [activeCategoryId, setActiveCategoryId] = useState(null);
615
+ const setExpanded = useCallback(
616
+ (next) => {
617
+ if (!isExpandedControlled) setInternalExpanded(next);
618
+ onExpandedChange?.(next);
619
+ },
620
+ [isExpandedControlled, onExpandedChange]
621
+ );
622
+ const setSelection = useCallback(
623
+ (next) => {
624
+ if (!isSelectionControlled) setInternalSelection(next);
625
+ onSelectionChange?.(next);
626
+ },
627
+ [isSelectionControlled, onSelectionChange]
628
+ );
629
+ useEffect(() => {
630
+ if (!onClose || typeof document === "undefined") return;
631
+ const handleKeyDown = (event) => {
632
+ if (event.key === "Escape") onClose();
633
+ };
634
+ document.addEventListener("keydown", handleKeyDown);
635
+ return () => document.removeEventListener("keydown", handleKeyDown);
636
+ }, [onClose]);
637
+ useEffect(() => {
638
+ const container = containerRef.current;
639
+ if (!container) return;
640
+ const [first] = getFocusableElements(container);
641
+ first?.focus();
642
+ }, [isExpanded]);
643
+ const handleTabKey = useCallback((event) => {
644
+ if (event.key !== "Tab" || !containerRef.current) return;
645
+ const focusable = getFocusableElements(containerRef.current);
646
+ const first = focusable[0];
647
+ const last = focusable[focusable.length - 1];
648
+ if (!first) {
649
+ event.preventDefault();
650
+ return;
651
+ }
652
+ if (event.shiftKey && document.activeElement === first) {
653
+ event.preventDefault();
654
+ last.focus();
655
+ } else if (!event.shiftKey && document.activeElement === last) {
656
+ event.preventDefault();
657
+ first.focus();
658
+ }
659
+ }, []);
660
+ const handleToggleCategory = useCallback(
661
+ (id) => {
662
+ const category = categories.find((item) => item.id === id);
663
+ if (!category || category.locked) return;
664
+ const next = selection.includes(id) ? selection.filter((item) => item !== id) : [...selection, id];
665
+ setSelection(next);
666
+ },
667
+ [categories, selection, setSelection]
668
+ );
669
+ const handleAcceptAll = useCallback(() => {
670
+ setSelection(allIds);
671
+ onAcceptAll?.();
672
+ }, [allIds, onAcceptAll, setSelection]);
673
+ const handleRejectAll = useCallback(() => {
674
+ setSelection(lockedIds);
675
+ onRejectAll?.();
676
+ }, [lockedIds, onRejectAll, setSelection]);
677
+ const handleCustomize = useCallback(() => {
678
+ setExpanded(true);
679
+ onCustomize?.();
680
+ }, [onCustomize, setExpanded]);
681
+ const handleCollapse = useCallback(() => {
682
+ setExpanded(false);
683
+ }, [setExpanded]);
684
+ const handleConfirmSelection = useCallback(() => {
685
+ onConfirmSelection?.(selection);
686
+ }, [onConfirmSelection, selection]);
687
+ const sizing = theme.sizing.modal();
688
+ const titleToken = theme.typographyTokens.basic["body-lg"];
689
+ const bodyToken = theme.typographyTokens.basic["body-sm"];
690
+ const solidButton = (key, label, onPress, fullWidth) => /* @__PURE__ */ jsx4(
691
+ Button,
692
+ {
693
+ variant: "secondary",
694
+ size: "md",
695
+ fullWidth,
696
+ onPress,
697
+ testID: `${testID}__${key}`,
698
+ themeMode,
699
+ themeProductContext,
700
+ children: label
701
+ },
702
+ key
703
+ );
704
+ const textButton = (key, label, onPress) => /* @__PURE__ */ jsx4(
705
+ FlexButton,
706
+ {
707
+ variant: "brand",
708
+ size: "lg",
709
+ onPress,
710
+ testID: `${testID}__${key}`,
711
+ themeMode,
712
+ themeProductContext,
713
+ children: label
714
+ },
715
+ key
716
+ );
717
+ const renderActions = () => {
718
+ if (isSimple) {
719
+ return /* @__PURE__ */ jsx4(
720
+ Box,
721
+ {
722
+ width: "100%",
723
+ flexDirection: "row",
724
+ justifyContent: isMobile ? "center" : "flex-end",
725
+ children: solidButton("dismiss", t.dismiss, () => onDismiss?.(), isMobile)
726
+ }
727
+ );
728
+ }
729
+ if (isExpanded) {
730
+ const close = textButton("close", t.close, handleCollapse);
731
+ const reject = solidButton(
732
+ "reject-all",
733
+ t.rejectAll,
734
+ handleRejectAll,
735
+ isMobile
736
+ );
737
+ const confirm = solidButton(
738
+ "confirm-selection",
739
+ t.confirmSelection,
740
+ handleConfirmSelection,
741
+ isMobile
742
+ );
743
+ return isMobile ? /* @__PURE__ */ jsx4(Box, { width: "100%", gap: CONTENT_GAP, alignItems: "center", children: [confirm, reject, close] }) : /* @__PURE__ */ jsx4(
744
+ Box,
745
+ {
746
+ width: "100%",
747
+ flexDirection: "row",
748
+ alignItems: "center",
749
+ justifyContent: "flex-end",
750
+ gap: CONTENT_GAP,
751
+ children: [close, reject, confirm]
752
+ }
753
+ );
754
+ }
755
+ const customize = textButton("customize", t.customize, handleCustomize);
756
+ if (isMobile) {
757
+ return /* @__PURE__ */ jsxs2(Box, { width: "100%", gap: CONTENT_GAP, alignItems: "center", children: [
758
+ solidButton("accept-all", t.acceptAll, handleAcceptAll, true),
759
+ solidButton("reject-all", t.rejectAll, handleRejectAll, true),
760
+ customize
761
+ ] });
762
+ }
763
+ if (buttonsRow === "double") {
764
+ return /* @__PURE__ */ jsxs2(Box, { width: "100%", gap: CONTENT_GAP, children: [
765
+ /* @__PURE__ */ jsx4(Box, { flexDirection: "row", justifyContent: "center", width: "100%", children: customize }),
766
+ /* @__PURE__ */ jsxs2(Box, { flexDirection: "row", gap: CONTENT_GAP, width: "100%", children: [
767
+ /* @__PURE__ */ jsx4(Box, { flex: 1, children: solidButton("reject-all", t.rejectAll, handleRejectAll, true) }),
768
+ /* @__PURE__ */ jsx4(Box, { flex: 1, children: solidButton("accept-all", t.acceptAll, handleAcceptAll, true) })
769
+ ] })
770
+ ] });
771
+ }
772
+ return /* @__PURE__ */ jsxs2(
773
+ Box,
774
+ {
775
+ width: "100%",
776
+ flexDirection: "row",
777
+ alignItems: "center",
778
+ justifyContent: "flex-end",
779
+ gap: CONTENT_GAP,
780
+ children: [
781
+ customize,
782
+ solidButton("reject-all", t.rejectAll, handleRejectAll, false),
783
+ solidButton("accept-all", t.acceptAll, handleAcceptAll, false)
784
+ ]
785
+ }
786
+ );
787
+ };
788
+ const edge = isExpanded ? 0 : offset;
789
+ const isFixed = position === "fixed";
790
+ const spansFullWidth = isExpanded || isMobile;
791
+ return /* @__PURE__ */ jsxs2(
792
+ Box,
793
+ {
794
+ ref: containerRef,
795
+ testID,
796
+ role: "dialog",
797
+ "aria-labelledby": titleId,
798
+ onKeyDown: handleTabKey,
799
+ "data-gdpr-banner-type": type,
800
+ "data-gdpr-banner-viewport": viewport,
801
+ "data-gdpr-banner-expanded": isExpanded ? "true" : "false",
802
+ position: isFixed ? "fixed" : "relative",
803
+ bottom: isFixed ? edge : void 0,
804
+ left: isFixed && (spansFullWidth || anchor === "bottom-left") ? edge : void 0,
805
+ right: isFixed && (spansFullWidth || anchor === "bottom-right") ? edge : void 0,
806
+ zIndex: isFixed ? 1e3 : void 0,
807
+ width: spansFullWidth ? "100%" : CARD_WIDTH,
808
+ maxWidth: "100%",
809
+ backgroundColor: theme.colors.background.primary,
810
+ borderRadius: isExpanded ? 0 : sizing.borderRadius,
811
+ padding: isMobile ? 16 : 20,
812
+ gap: CONTENT_GAP,
813
+ children: [
814
+ /* @__PURE__ */ jsxs2(
815
+ Box,
816
+ {
817
+ flexDirection: "row",
818
+ alignItems: "flex-start",
819
+ justifyContent: "space-between",
820
+ gap: 12,
821
+ width: "100%",
822
+ children: [
823
+ /* @__PURE__ */ jsx4(
824
+ Text,
825
+ {
826
+ id: titleId,
827
+ color: theme.colors.content.primary,
828
+ fontSize: titleToken.fontSize,
829
+ lineHeight: titleToken.lineHeight,
830
+ fontWeight: titleToken.accent?.fontWeight ?? 500,
831
+ fontFamily: theme.fonts.body,
832
+ children: isExpanded ? t.privacySettingsTitle : t.title
833
+ }
834
+ ),
835
+ showCloseButton && onClose && /* @__PURE__ */ jsx4(
836
+ FlexButton,
837
+ {
838
+ variant: "tertiary",
839
+ size: "xs",
840
+ onPress: onClose,
841
+ "aria-label": t.closeButtonAriaLabel,
842
+ testID: `${testID}__close-icon`,
843
+ themeMode,
844
+ themeProductContext,
845
+ children: /* @__PURE__ */ jsx4(Remove, { variant: "line" })
846
+ }
847
+ )
848
+ ]
849
+ }
850
+ ),
851
+ isExpanded ? /* @__PURE__ */ jsx4(
852
+ PrivacySettings,
853
+ {
854
+ categories,
855
+ selectedCategoryIds: selection,
856
+ onToggleCategory: handleToggleCategory,
857
+ activeCategoryId,
858
+ onActiveCategoryChange: setActiveCategoryId,
859
+ viewport,
860
+ labels: {
861
+ generalInformation: t.generalInformation,
862
+ generalInformationDescription: t.generalInformationDescription,
863
+ cookiePolicy: t.cookiePolicy,
864
+ privacyPolicy: t.privacyPolicy
865
+ },
866
+ cookiePolicyUrl,
867
+ privacyPolicyUrl,
868
+ themeMode,
869
+ themeProductContext,
870
+ testID: `${testID}__privacy-settings`
871
+ }
872
+ ) : /* @__PURE__ */ jsx4(
873
+ Text,
874
+ {
875
+ color: theme.colors.content.secondary,
876
+ fontSize: bodyToken.fontSize,
877
+ lineHeight: bodyToken.lineHeight,
878
+ fontFamily: theme.fonts.body,
879
+ testID: `${testID}__description`,
880
+ children: isSimple ? t.simpleDescription : t.description
881
+ }
882
+ ),
883
+ renderActions()
884
+ ]
885
+ }
886
+ );
887
+ };
888
+ GdprBanner.displayName = "GdprBanner";
889
+ export {
890
+ DEFAULT_CATEGORIES,
891
+ DEFAULT_LABELS,
892
+ GdprBanner,
893
+ PrivacySettings
894
+ };
895
+ //# sourceMappingURL=index.mjs.map