@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.
package/web/index.mjs ADDED
@@ -0,0 +1,911 @@
1
+ // src/GdprBanner.tsx
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+
4
+ // ../../foundation/primitives-web/src/Box.tsx
5
+ import React2 from "react";
6
+ import styled from "styled-components";
7
+
8
+ // ../../foundation/primitives-web/src/filterDOMProps.ts
9
+ import React from "react";
10
+
11
+ // ../../../node_modules/@emotion/memoize/dist/memoize.esm.js
12
+ function memoize(fn) {
13
+ var cache = {};
14
+ return function(arg) {
15
+ if (cache[arg] === void 0) cache[arg] = fn(arg);
16
+ return cache[arg];
17
+ };
18
+ }
19
+ var memoize_esm_default = memoize;
20
+
21
+ // ../../../node_modules/@emotion/is-prop-valid/dist/is-prop-valid.esm.js
22
+ var reactPropsRegex = /^((children|dangerouslySetInnerHTML|key|ref|autoFocus|defaultValue|defaultChecked|innerHTML|suppressContentEditableWarning|suppressHydrationWarning|valueLink|accept|acceptCharset|accessKey|action|allow|allowUserMedia|allowPaymentRequest|allowFullScreen|allowTransparency|alt|async|autoComplete|autoPlay|capture|cellPadding|cellSpacing|challenge|charSet|checked|cite|classID|className|cols|colSpan|content|contentEditable|contextMenu|controls|controlsList|coords|crossOrigin|data|dateTime|decoding|default|defer|dir|disabled|disablePictureInPicture|download|draggable|encType|form|formAction|formEncType|formMethod|formNoValidate|formTarget|frameBorder|headers|height|hidden|high|href|hrefLang|htmlFor|httpEquiv|id|inputMode|integrity|is|keyParams|keyType|kind|label|lang|list|loading|loop|low|marginHeight|marginWidth|max|maxLength|media|mediaGroup|method|min|minLength|multiple|muted|name|nonce|noValidate|open|optimum|pattern|placeholder|playsInline|poster|preload|profile|radioGroup|readOnly|referrerPolicy|rel|required|reversed|role|rows|rowSpan|sandbox|scope|scoped|scrolling|seamless|selected|shape|size|sizes|slot|span|spellCheck|src|srcDoc|srcLang|srcSet|start|step|style|summary|tabIndex|target|title|type|useMap|value|width|wmode|wrap|about|datatype|inlist|prefix|property|resource|typeof|vocab|autoCapitalize|autoCorrect|autoSave|color|inert|itemProp|itemScope|itemType|itemID|itemRef|on|results|security|unselectable|accentHeight|accumulate|additive|alignmentBaseline|allowReorder|alphabetic|amplitude|arabicForm|ascent|attributeName|attributeType|autoReverse|azimuth|baseFrequency|baselineShift|baseProfile|bbox|begin|bias|by|calcMode|capHeight|clip|clipPathUnits|clipPath|clipRule|colorInterpolation|colorInterpolationFilters|colorProfile|colorRendering|contentScriptType|contentStyleType|cursor|cx|cy|d|decelerate|descent|diffuseConstant|direction|display|divisor|dominantBaseline|dur|dx|dy|edgeMode|elevation|enableBackground|end|exponent|externalResourcesRequired|fill|fillOpacity|fillRule|filter|filterRes|filterUnits|floodColor|floodOpacity|focusable|fontFamily|fontSize|fontSizeAdjust|fontStretch|fontStyle|fontVariant|fontWeight|format|from|fr|fx|fy|g1|g2|glyphName|glyphOrientationHorizontal|glyphOrientationVertical|glyphRef|gradientTransform|gradientUnits|hanging|horizAdvX|horizOriginX|ideographic|imageRendering|in|in2|intercept|k|k1|k2|k3|k4|kernelMatrix|kernelUnitLength|kerning|keyPoints|keySplines|keyTimes|lengthAdjust|letterSpacing|lightingColor|limitingConeAngle|local|markerEnd|markerMid|markerStart|markerHeight|markerUnits|markerWidth|mask|maskContentUnits|maskUnits|mathematical|mode|numOctaves|offset|opacity|operator|order|orient|orientation|origin|overflow|overlinePosition|overlineThickness|panose1|paintOrder|pathLength|patternContentUnits|patternTransform|patternUnits|pointerEvents|points|pointsAtX|pointsAtY|pointsAtZ|preserveAlpha|preserveAspectRatio|primitiveUnits|r|radius|refX|refY|renderingIntent|repeatCount|repeatDur|requiredExtensions|requiredFeatures|restart|result|rotate|rx|ry|scale|seed|shapeRendering|slope|spacing|specularConstant|specularExponent|speed|spreadMethod|startOffset|stdDeviation|stemh|stemv|stitchTiles|stopColor|stopOpacity|strikethroughPosition|strikethroughThickness|string|stroke|strokeDasharray|strokeDashoffset|strokeLinecap|strokeLinejoin|strokeMiterlimit|strokeOpacity|strokeWidth|surfaceScale|systemLanguage|tableValues|targetX|targetY|textAnchor|textDecoration|textRendering|textLength|to|transform|u1|u2|underlinePosition|underlineThickness|unicode|unicodeBidi|unicodeRange|unitsPerEm|vAlphabetic|vHanging|vIdeographic|vMathematical|values|vectorEffect|version|vertAdvY|vertOriginX|vertOriginY|viewBox|viewTarget|visibility|widths|wordSpacing|writingMode|x|xHeight|x1|x2|xChannelSelector|xlinkActuate|xlinkArcrole|xlinkHref|xlinkRole|xlinkShow|xlinkTitle|xlinkType|xmlBase|xmlns|xmlnsXlink|xmlLang|xmlSpace|y|y1|y2|yChannelSelector|z|zoomAndPan|for|class|autofocus)|(([Dd][Aa][Tt][Aa]|[Aa][Rr][Ii][Aa]|x)-.*))$/;
23
+ var index = memoize_esm_default(
24
+ function(prop) {
25
+ return reactPropsRegex.test(prop) || prop.charCodeAt(0) === 111 && prop.charCodeAt(1) === 110 && prop.charCodeAt(2) < 91;
26
+ }
27
+ /* Z+1 */
28
+ );
29
+ var is_prop_valid_esm_default = index;
30
+
31
+ // ../../foundation/primitives-web/src/filterDOMProps.ts
32
+ var ADDITIONAL_BLOCKED_PROPS = /* @__PURE__ */ new Set([
33
+ // RN-only event handlers (pass isPropValid's on* pattern)
34
+ "onPress",
35
+ "onChangeText",
36
+ "onLayout",
37
+ "onMoveShouldSetResponder",
38
+ "onResponderGrant",
39
+ "onResponderMove",
40
+ "onResponderRelease",
41
+ "onResponderTerminate",
42
+ // SVG attributes that pass isPropValid
43
+ "strokeWidth",
44
+ // CSS properties that pass isPropValid but are used as component props
45
+ "overflow",
46
+ "opacity",
47
+ "cursor",
48
+ "fontSize",
49
+ "fontWeight",
50
+ "fontFamily",
51
+ "textDecoration",
52
+ // Cross-platform layout prop that Text consumes as CSS, never as an
53
+ // attribute. Pinned explicitly because it is now spread into the styled
54
+ // component rather than destructured away before it can reach the DOM.
55
+ "numberOfLines"
56
+ ]);
57
+ function shouldForwardProp(key) {
58
+ if (ADDITIONAL_BLOCKED_PROPS.has(key)) return false;
59
+ return is_prop_valid_esm_default(key);
60
+ }
61
+ function createFilteredElement(defaultTag) {
62
+ const Component = React.forwardRef(
63
+ ({ children, elementType, ...props }, ref) => {
64
+ const Tag = elementType || defaultTag;
65
+ const htmlProps = {};
66
+ for (const key of Object.keys(props)) {
67
+ if (shouldForwardProp(key)) {
68
+ htmlProps[key] = props[key];
69
+ }
70
+ }
71
+ return React.createElement(
72
+ Tag,
73
+ { ref, ...htmlProps },
74
+ children
75
+ );
76
+ }
77
+ );
78
+ Component.displayName = `Filtered(${defaultTag})`;
79
+ return Component;
80
+ }
81
+
82
+ // ../../foundation/primitives-web/src/Box.tsx
83
+ import { jsx } from "react/jsx-runtime";
84
+ var FilteredDiv = createFilteredElement("div");
85
+ var StyledBox = styled(FilteredDiv)`
86
+ display: flex;
87
+ box-sizing: border-box;
88
+ background-color: ${(props) => props.backgroundColor || "transparent"};
89
+ border-color: ${(props) => props.borderColor || "transparent"};
90
+ border-width: ${(props) => typeof props.borderWidth === "number" ? `${props.borderWidth}px` : props.borderWidth || 0};
91
+
92
+ ${(props) => props.borderBottomWidth !== void 0 && `
93
+ border-bottom-width: ${typeof props.borderBottomWidth === "number" ? `${props.borderBottomWidth}px` : props.borderBottomWidth};
94
+ border-bottom-color: ${props.borderBottomColor || props.borderColor || "transparent"};
95
+ border-bottom-style: solid;
96
+ `}
97
+ ${(props) => props.borderTopWidth !== void 0 && `
98
+ border-top-width: ${typeof props.borderTopWidth === "number" ? `${props.borderTopWidth}px` : props.borderTopWidth};
99
+ border-top-color: ${props.borderTopColor || props.borderColor || "transparent"};
100
+ border-top-style: solid;
101
+ `}
102
+ ${(props) => props.borderLeftWidth !== void 0 && `
103
+ border-left-width: ${typeof props.borderLeftWidth === "number" ? `${props.borderLeftWidth}px` : props.borderLeftWidth};
104
+ border-left-color: ${props.borderLeftColor || props.borderColor || "transparent"};
105
+ border-left-style: solid;
106
+ `}
107
+ ${(props) => props.borderRightWidth !== void 0 && `
108
+ border-right-width: ${typeof props.borderRightWidth === "number" ? `${props.borderRightWidth}px` : props.borderRightWidth};
109
+ border-right-color: ${props.borderRightColor || props.borderColor || "transparent"};
110
+ border-right-style: solid;
111
+ `}
112
+
113
+ border-style: ${(props) => props.borderStyle || (props.borderWidth || props.borderBottomWidth || props.borderTopWidth || props.borderLeftWidth || props.borderRightWidth ? "solid" : "none")};
114
+ border-radius: ${(props) => typeof props.borderRadius === "number" ? `${props.borderRadius}px` : props.borderRadius || 0};
115
+ height: ${(props) => typeof props.height === "number" ? `${props.height}px` : props.height || "auto"};
116
+ width: ${(props) => typeof props.width === "number" ? `${props.width}px` : props.width || "auto"};
117
+ min-width: ${(props) => typeof props.minWidth === "number" ? `${props.minWidth}px` : props.minWidth || "auto"};
118
+ min-height: ${(props) => typeof props.minHeight === "number" ? `${props.minHeight}px` : props.minHeight || "auto"};
119
+ max-width: ${(props) => typeof props.maxWidth === "number" ? `${props.maxWidth}px` : props.maxWidth || "none"};
120
+ max-height: ${(props) => typeof props.maxHeight === "number" ? `${props.maxHeight}px` : props.maxHeight || "none"};
121
+
122
+ padding: ${(props) => typeof props.padding === "number" ? `${props.padding}px` : props.padding || 0};
123
+ ${(props) => props.paddingHorizontal && `
124
+ padding-left: ${typeof props.paddingHorizontal === "number" ? `${props.paddingHorizontal}px` : props.paddingHorizontal};
125
+ padding-right: ${typeof props.paddingHorizontal === "number" ? `${props.paddingHorizontal}px` : props.paddingHorizontal};
126
+ `}
127
+ ${(props) => props.paddingVertical && `
128
+ padding-top: ${typeof props.paddingVertical === "number" ? `${props.paddingVertical}px` : props.paddingVertical};
129
+ padding-bottom: ${typeof props.paddingVertical === "number" ? `${props.paddingVertical}px` : props.paddingVertical};
130
+ `}
131
+ ${(props) => props.paddingTop !== void 0 && `padding-top: ${typeof props.paddingTop === "number" ? `${props.paddingTop}px` : props.paddingTop};`}
132
+ ${(props) => props.paddingBottom !== void 0 && `padding-bottom: ${typeof props.paddingBottom === "number" ? `${props.paddingBottom}px` : props.paddingBottom};`}
133
+ ${(props) => props.paddingLeft !== void 0 && `padding-left: ${typeof props.paddingLeft === "number" ? `${props.paddingLeft}px` : props.paddingLeft};`}
134
+ ${(props) => props.paddingRight !== void 0 && `padding-right: ${typeof props.paddingRight === "number" ? `${props.paddingRight}px` : props.paddingRight};`}
135
+
136
+ margin: ${(props) => typeof props.margin === "number" ? `${props.margin}px` : props.margin || 0};
137
+ ${(props) => props.marginTop !== void 0 && `margin-top: ${typeof props.marginTop === "number" ? `${props.marginTop}px` : props.marginTop};`}
138
+ ${(props) => props.marginBottom !== void 0 && `margin-bottom: ${typeof props.marginBottom === "number" ? `${props.marginBottom}px` : props.marginBottom};`}
139
+ ${(props) => props.marginLeft !== void 0 && `margin-left: ${typeof props.marginLeft === "number" ? `${props.marginLeft}px` : props.marginLeft};`}
140
+ ${(props) => props.marginRight !== void 0 && `margin-right: ${typeof props.marginRight === "number" ? `${props.marginRight}px` : props.marginRight};`}
141
+
142
+ flex-direction: ${(props) => props.flexDirection || "column"};
143
+ flex-wrap: ${(props) => props.flexWrap || "nowrap"};
144
+ align-items: ${(props) => props.alignItems || "stretch"};
145
+ justify-content: ${(props) => props.justifyContent || "flex-start"};
146
+ cursor: ${(props) => props.cursor ? props.cursor : props.onClick || props.onPress ? "pointer" : "inherit"};
147
+ position: ${(props) => props.position || "static"};
148
+ top: ${(props) => typeof props.top === "number" ? `${props.top}px` : props.top};
149
+ bottom: ${(props) => typeof props.bottom === "number" ? `${props.bottom}px` : props.bottom};
150
+ left: ${(props) => typeof props.left === "number" ? `${props.left}px` : props.left};
151
+ right: ${(props) => typeof props.right === "number" ? `${props.right}px` : props.right};
152
+ flex: ${(props) => props.flex};
153
+ flex-shrink: ${(props) => props.flexShrink ?? 1};
154
+ gap: ${(props) => typeof props.gap === "number" ? `${props.gap}px` : props.gap || 0};
155
+ align-self: ${(props) => props.alignSelf || "auto"};
156
+ /* Only emit overflow-x/y when explicitly set. Defaulting them to
157
+ visible after overflow would override the shorthand and break
158
+ clipping (e.g. OtherCard bottom-right promo image). */
159
+ overflow: ${(props) => props.overflow || "visible"};
160
+ ${(props) => props.overflowX != null ? `overflow-x: ${props.overflowX};` : ""}
161
+ ${(props) => props.overflowY != null ? `overflow-y: ${props.overflowY};` : ""}
162
+ z-index: ${(props) => props.zIndex};
163
+ opacity: ${(props) => props.opacity != null ? props.opacity : props.disabled ? 0.5 : 1};
164
+ pointer-events: ${(props) => props.disabled ? "none" : "auto"};
165
+
166
+ &:hover {
167
+ ${(props) => props.hoverStyle?.backgroundColor && `background-color: ${props.hoverStyle.backgroundColor};`}
168
+ ${(props) => props.hoverStyle?.borderColor && `border-color: ${props.hoverStyle.borderColor};`}
169
+ }
170
+
171
+ &:active {
172
+ ${(props) => props.pressStyle?.backgroundColor && `background-color: ${props.pressStyle.backgroundColor};`}
173
+ }
174
+ `;
175
+ var Box = React2.forwardRef(
176
+ ({
177
+ children,
178
+ onPress,
179
+ onKeyDown,
180
+ onKeyUp,
181
+ role,
182
+ "aria-label": ariaLabel,
183
+ "aria-labelledby": ariaLabelledBy,
184
+ "aria-current": ariaCurrent,
185
+ "aria-disabled": ariaDisabled,
186
+ "aria-live": ariaLive,
187
+ "aria-busy": ariaBusy,
188
+ "aria-describedby": ariaDescribedBy,
189
+ "aria-expanded": ariaExpanded,
190
+ "aria-haspopup": ariaHasPopup,
191
+ "aria-pressed": ariaPressed,
192
+ "aria-controls": ariaControls,
193
+ tabIndex,
194
+ as,
195
+ src,
196
+ alt,
197
+ onError,
198
+ onLoad,
199
+ type,
200
+ disabled,
201
+ id,
202
+ testID,
203
+ "data-testid": dataTestId,
204
+ ...props
205
+ }, ref) => {
206
+ if (as === "img" && src) {
207
+ return /* @__PURE__ */ jsx(
208
+ "img",
209
+ {
210
+ src,
211
+ alt: alt || "",
212
+ onError,
213
+ onLoad,
214
+ "data-testid": dataTestId || testID,
215
+ style: {
216
+ display: "block",
217
+ objectFit: "cover",
218
+ width: typeof props.width === "number" ? `${props.width}px` : props.width,
219
+ height: typeof props.height === "number" ? `${props.height}px` : props.height,
220
+ borderRadius: typeof props.borderRadius === "number" ? `${props.borderRadius}px` : props.borderRadius,
221
+ position: props.position,
222
+ top: typeof props.top === "number" ? `${props.top}px` : props.top,
223
+ left: typeof props.left === "number" ? `${props.left}px` : props.left,
224
+ right: typeof props.right === "number" ? `${props.right}px` : props.right,
225
+ bottom: typeof props.bottom === "number" ? `${props.bottom}px` : props.bottom,
226
+ ...props.style
227
+ }
228
+ }
229
+ );
230
+ }
231
+ return /* @__PURE__ */ jsx(
232
+ StyledBox,
233
+ {
234
+ ref,
235
+ elementType: as,
236
+ id,
237
+ type: as === "button" ? type || "button" : void 0,
238
+ disabled: as === "button" ? disabled : void 0,
239
+ onClick: onPress,
240
+ onKeyDown,
241
+ onKeyUp,
242
+ role,
243
+ "aria-label": ariaLabel,
244
+ "aria-labelledby": ariaLabelledBy,
245
+ "aria-current": ariaCurrent,
246
+ "aria-disabled": ariaDisabled,
247
+ "aria-busy": ariaBusy,
248
+ "aria-describedby": ariaDescribedBy,
249
+ "aria-expanded": ariaExpanded,
250
+ "aria-haspopup": ariaHasPopup,
251
+ "aria-pressed": ariaPressed,
252
+ "aria-controls": ariaControls,
253
+ "aria-live": ariaLive,
254
+ tabIndex: tabIndex !== void 0 ? tabIndex : void 0,
255
+ "data-testid": dataTestId || testID,
256
+ ...props,
257
+ children
258
+ }
259
+ );
260
+ }
261
+ );
262
+ Box.displayName = "Box";
263
+
264
+ // ../../foundation/primitives-web/src/Text.tsx
265
+ import styled2 from "styled-components";
266
+
267
+ // ../../foundation/primitives-web/src/textTruncation.ts
268
+ var truncationStyles = (numberOfLines) => {
269
+ if (!numberOfLines || numberOfLines < 1) return "";
270
+ if (numberOfLines === 1) {
271
+ return `
272
+ display: block;
273
+ max-width: 100%;
274
+ min-width: 0;
275
+ overflow: hidden;
276
+ text-overflow: ellipsis;
277
+ white-space: nowrap;
278
+ `;
279
+ }
280
+ return `
281
+ display: -webkit-box;
282
+ -webkit-box-orient: vertical;
283
+ -webkit-line-clamp: ${numberOfLines};
284
+ line-clamp: ${numberOfLines};
285
+ max-width: 100%;
286
+ min-width: 0;
287
+ overflow: hidden;
288
+ `;
289
+ };
290
+
291
+ // ../../foundation/primitives-web/src/Text.tsx
292
+ import { jsx as jsx2 } from "react/jsx-runtime";
293
+ var FilteredSpan = createFilteredElement("span");
294
+ var StyledText = styled2(FilteredSpan)`
295
+ color: ${(props) => props.color || "inherit"};
296
+ font-size: ${(props) => typeof props.fontSize === "number" ? `${props.fontSize}px` : props.fontSize || "inherit"};
297
+ font-weight: ${(props) => props.fontWeight || "normal"};
298
+ font-family: ${(props) => props.fontFamily || '"Aktiv Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'};
299
+ line-height: ${(props) => typeof props.lineHeight === "number" ? `${props.lineHeight}px` : props.lineHeight || "inherit"};
300
+ white-space: ${(props) => props.whiteSpace || "normal"};
301
+ text-align: ${(props) => props.textAlign || "inherit"};
302
+ text-decoration: ${(props) => props.textDecoration || "none"};
303
+
304
+ /* Keep last: single-line truncation has to win over white-space above. */
305
+ ${(props) => truncationStyles(props.numberOfLines)}
306
+ `;
307
+ var Text = ({
308
+ style,
309
+ className,
310
+ id,
311
+ role,
312
+ testID,
313
+ "data-testid": dataTestId,
314
+ ...props
315
+ }) => {
316
+ return /* @__PURE__ */ jsx2(
317
+ StyledText,
318
+ {
319
+ ...props,
320
+ style,
321
+ className,
322
+ id,
323
+ role,
324
+ "data-testid": dataTestId || testID
325
+ }
326
+ );
327
+ };
328
+
329
+ // src/GdprBanner.tsx
330
+ import { useResolvedTheme as useResolvedTheme2, useId } from "@xsolla/xui-core";
331
+ import { Button, FlexButton } from "@xsolla/xui-button";
332
+ import { Remove } from "@xsolla/xui-icons-base";
333
+
334
+ // src/PrivacySettings.tsx
335
+ import { useResolvedTheme } from "@xsolla/xui-core";
336
+ import { Checkbox } from "@xsolla/xui-checkbox";
337
+ import { Link } from "@xsolla/xui-link";
338
+ import { InfoCr } from "@xsolla/xui-icons-base";
339
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
340
+ var PANE_RADIUS = 8;
341
+ var LIST_WIDTH = 280;
342
+ var PrivacySettings = ({
343
+ categories,
344
+ selectedCategoryIds,
345
+ onToggleCategory,
346
+ activeCategoryId,
347
+ onActiveCategoryChange,
348
+ viewport,
349
+ labels,
350
+ cookiePolicyUrl,
351
+ privacyPolicyUrl,
352
+ testID = "gdpr-privacy-settings",
353
+ themeMode,
354
+ themeProductContext
355
+ }) => {
356
+ const { theme } = useResolvedTheme({ themeMode, themeProductContext });
357
+ const isMobile = viewport === "mobile";
358
+ const body = theme.typographyTokens.basic["body-sm"];
359
+ const activeDescription = activeCategoryId === null ? labels.generalInformationDescription : categories.find((category) => category.id === activeCategoryId)?.description ?? "";
360
+ const descriptionPane = /* @__PURE__ */ jsx3(
361
+ Box,
362
+ {
363
+ testID: `${testID}__description`,
364
+ backgroundColor: theme.colors.control.faint.bg,
365
+ borderRadius: PANE_RADIUS,
366
+ padding: 16,
367
+ flex: isMobile ? void 0 : 1,
368
+ width: isMobile ? "100%" : void 0,
369
+ "aria-live": "polite",
370
+ children: /* @__PURE__ */ jsx3(
371
+ Text,
372
+ {
373
+ color: theme.colors.content.secondary,
374
+ fontSize: body.fontSize,
375
+ lineHeight: body.lineHeight,
376
+ fontFamily: theme.fonts.body,
377
+ children: activeDescription
378
+ }
379
+ )
380
+ }
381
+ );
382
+ const focusStyle = {
383
+ outlineColor: theme.colors.border.brand,
384
+ outlineWidth: 2,
385
+ outlineOffset: 2,
386
+ outlineStyle: "solid"
387
+ };
388
+ const list = /* @__PURE__ */ jsxs(
389
+ Box,
390
+ {
391
+ gap: 12,
392
+ width: isMobile ? "100%" : LIST_WIDTH,
393
+ flexShrink: 0,
394
+ testID: `${testID}__categories`,
395
+ children: [
396
+ /* @__PURE__ */ jsxs(Box, { gap: 8, children: [
397
+ /* @__PURE__ */ jsxs(
398
+ Box,
399
+ {
400
+ flexDirection: "row",
401
+ alignItems: "center",
402
+ gap: 8,
403
+ role: "button",
404
+ tabIndex: 0,
405
+ cursor: "pointer",
406
+ "aria-pressed": activeCategoryId === null,
407
+ onPress: () => onActiveCategoryChange(null),
408
+ onKeyDown: (event) => {
409
+ if (event.key === "Enter" || event.key === " ") {
410
+ event.preventDefault();
411
+ onActiveCategoryChange(null);
412
+ }
413
+ },
414
+ focusStyle,
415
+ testID: `${testID}__general-information`,
416
+ children: [
417
+ /* @__PURE__ */ jsx3(
418
+ InfoCr,
419
+ {
420
+ variant: "solid",
421
+ size: 16,
422
+ color: theme.colors.control.link.primary
423
+ }
424
+ ),
425
+ /* @__PURE__ */ jsx3(
426
+ Text,
427
+ {
428
+ color: theme.colors.content.primary,
429
+ fontSize: body.fontSize,
430
+ lineHeight: body.lineHeight,
431
+ fontFamily: theme.fonts.body,
432
+ children: labels.generalInformation
433
+ }
434
+ )
435
+ ]
436
+ }
437
+ ),
438
+ isMobile && activeCategoryId === null && descriptionPane
439
+ ] }),
440
+ categories.map((category) => /* @__PURE__ */ jsxs(Box, { gap: 8, children: [
441
+ /* @__PURE__ */ jsx3(Box, { onPress: () => onActiveCategoryChange(category.id), children: /* @__PURE__ */ jsx3(
442
+ Checkbox,
443
+ {
444
+ size: "md",
445
+ checked: selectedCategoryIds.includes(category.id),
446
+ disabled: category.locked,
447
+ onChange: () => onToggleCategory(category.id),
448
+ testID: `${testID}__category-${category.id}`,
449
+ themeMode,
450
+ themeProductContext,
451
+ children: category.label
452
+ }
453
+ ) }),
454
+ isMobile && activeCategoryId === category.id && descriptionPane
455
+ ] }, category.id))
456
+ ]
457
+ }
458
+ );
459
+ return /* @__PURE__ */ jsxs(Box, { testID, gap: 16, width: "100%", children: [
460
+ /* @__PURE__ */ jsxs(
461
+ Box,
462
+ {
463
+ flexDirection: isMobile ? "column" : "row",
464
+ gap: isMobile ? 12 : 24,
465
+ alignItems: "flex-start",
466
+ width: "100%",
467
+ children: [
468
+ list,
469
+ !isMobile && descriptionPane
470
+ ]
471
+ }
472
+ ),
473
+ /* @__PURE__ */ jsxs(
474
+ Box,
475
+ {
476
+ flexDirection: "row",
477
+ alignItems: "center",
478
+ gap: 8,
479
+ testID: `${testID}__policies`,
480
+ children: [
481
+ /* @__PURE__ */ jsx3(
482
+ Link,
483
+ {
484
+ size: "sm",
485
+ href: cookiePolicyUrl,
486
+ target: "_blank",
487
+ themeMode,
488
+ themeProductContext,
489
+ testID: `${testID}__cookie-policy`,
490
+ children: labels.cookiePolicy
491
+ }
492
+ ),
493
+ /* @__PURE__ */ jsx3(
494
+ Text,
495
+ {
496
+ color: theme.colors.content.tertiary,
497
+ fontSize: body.fontSize,
498
+ lineHeight: body.lineHeight,
499
+ fontFamily: theme.fonts.body,
500
+ "aria-hidden": true,
501
+ children: "\u2022"
502
+ }
503
+ ),
504
+ /* @__PURE__ */ jsx3(
505
+ Link,
506
+ {
507
+ size: "sm",
508
+ href: privacyPolicyUrl,
509
+ target: "_blank",
510
+ themeMode,
511
+ themeProductContext,
512
+ testID: `${testID}__privacy-policy`,
513
+ children: labels.privacyPolicy
514
+ }
515
+ )
516
+ ]
517
+ }
518
+ )
519
+ ] });
520
+ };
521
+ PrivacySettings.displayName = "PrivacySettings";
522
+
523
+ // src/defaults.ts
524
+ var DEFAULT_CATEGORIES = [
525
+ {
526
+ id: "essential",
527
+ label: "Essential cookies",
528
+ locked: true,
529
+ 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."
530
+ },
531
+ {
532
+ id: "analytical",
533
+ label: "Analytical and performance cookies",
534
+ 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."
535
+ },
536
+ {
537
+ id: "functional",
538
+ label: "Functional cookies",
539
+ 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."
540
+ },
541
+ {
542
+ id: "targeting",
543
+ label: "Targeting cookies",
544
+ 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."
545
+ }
546
+ ];
547
+ var DEFAULT_LABELS = {
548
+ title: "How we use cookies",
549
+ description: "We use cookies to personalize your experience. If you reject the use of cookies, your experience will not be tailored to your preferences.",
550
+ simpleDescription: "We only use essential cookies that allow us to run this service. Read our cookie policy to learn more.",
551
+ privacySettingsTitle: "Privacy settings",
552
+ generalInformation: "General information",
553
+ 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.",
554
+ customize: "Customize",
555
+ rejectAll: "Reject All",
556
+ acceptAll: "Accept All",
557
+ confirmSelection: "Confirm Selection",
558
+ dismiss: "Dismiss",
559
+ close: "Close",
560
+ cookiePolicy: "Cookie Policy",
561
+ privacyPolicy: "Privacy Policy",
562
+ closeButtonAriaLabel: "Close cookie banner"
563
+ };
564
+ var CARD_WIDTH = 400;
565
+ var EDGE_OFFSET = 20;
566
+ var CONTENT_GAP = 8;
567
+
568
+ // src/GdprBanner.tsx
569
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
570
+ var FOCUSABLE_SELECTORS = 'button:not([disabled]), [href], input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
571
+ var isElementVisible = (element) => {
572
+ if (typeof window === "undefined") return true;
573
+ const style = window.getComputedStyle(element);
574
+ return style.visibility !== "hidden" && style.display !== "none";
575
+ };
576
+ var getFocusableElements = (container) => Array.from(
577
+ container.querySelectorAll(FOCUSABLE_SELECTORS)
578
+ ).filter(isElementVisible);
579
+ var GdprBanner = ({
580
+ type = "complex",
581
+ viewport = "desktop",
582
+ buttonsRow = "single",
583
+ anchor = "bottom-left",
584
+ expanded,
585
+ defaultExpanded = false,
586
+ onExpandedChange,
587
+ categories = DEFAULT_CATEGORIES,
588
+ selectedCategoryIds,
589
+ defaultSelectedCategoryIds,
590
+ onSelectionChange,
591
+ labels,
592
+ cookiePolicyUrl,
593
+ privacyPolicyUrl,
594
+ onAcceptAll,
595
+ onRejectAll,
596
+ onCustomize,
597
+ onConfirmSelection,
598
+ onDismiss,
599
+ onClose,
600
+ showCloseButton = true,
601
+ position = "fixed",
602
+ offset = EDGE_OFFSET,
603
+ testID = "gdpr-banner",
604
+ themeMode,
605
+ themeProductContext
606
+ }) => {
607
+ const { theme } = useResolvedTheme2({ themeMode, themeProductContext });
608
+ const containerRef = useRef(null);
609
+ const rawId = useId();
610
+ const titleId = `gdpr-banner-title-${rawId.replace(/:/g, "")}`;
611
+ const t = useMemo(() => ({ ...DEFAULT_LABELS, ...labels }), [labels]);
612
+ const isMobile = viewport === "mobile";
613
+ const isSimple = type === "simple";
614
+ const allIds = useMemo(
615
+ () => categories.map((category) => category.id),
616
+ [categories]
617
+ );
618
+ const lockedIds = useMemo(
619
+ () => categories.filter((category) => category.locked).map((c) => c.id),
620
+ [categories]
621
+ );
622
+ const isExpandedControlled = expanded !== void 0;
623
+ const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
624
+ const isExpanded = !isSimple && (isExpandedControlled ? expanded : internalExpanded);
625
+ const isSelectionControlled = selectedCategoryIds !== void 0;
626
+ const [internalSelection, setInternalSelection] = useState(
627
+ defaultSelectedCategoryIds ?? allIds
628
+ );
629
+ const selection = isSelectionControlled ? selectedCategoryIds : internalSelection;
630
+ const [activeCategoryId, setActiveCategoryId] = useState(null);
631
+ const setExpanded = useCallback(
632
+ (next) => {
633
+ if (!isExpandedControlled) setInternalExpanded(next);
634
+ onExpandedChange?.(next);
635
+ },
636
+ [isExpandedControlled, onExpandedChange]
637
+ );
638
+ const setSelection = useCallback(
639
+ (next) => {
640
+ if (!isSelectionControlled) setInternalSelection(next);
641
+ onSelectionChange?.(next);
642
+ },
643
+ [isSelectionControlled, onSelectionChange]
644
+ );
645
+ useEffect(() => {
646
+ if (!onClose || typeof document === "undefined") return;
647
+ const handleKeyDown = (event) => {
648
+ if (event.key === "Escape") onClose();
649
+ };
650
+ document.addEventListener("keydown", handleKeyDown);
651
+ return () => document.removeEventListener("keydown", handleKeyDown);
652
+ }, [onClose]);
653
+ useEffect(() => {
654
+ const container = containerRef.current;
655
+ if (!container) return;
656
+ const [first] = getFocusableElements(container);
657
+ first?.focus();
658
+ }, [isExpanded]);
659
+ const handleTabKey = useCallback((event) => {
660
+ if (event.key !== "Tab" || !containerRef.current) return;
661
+ const focusable = getFocusableElements(containerRef.current);
662
+ const first = focusable[0];
663
+ const last = focusable[focusable.length - 1];
664
+ if (!first) {
665
+ event.preventDefault();
666
+ return;
667
+ }
668
+ if (event.shiftKey && document.activeElement === first) {
669
+ event.preventDefault();
670
+ last.focus();
671
+ } else if (!event.shiftKey && document.activeElement === last) {
672
+ event.preventDefault();
673
+ first.focus();
674
+ }
675
+ }, []);
676
+ const handleToggleCategory = useCallback(
677
+ (id) => {
678
+ const category = categories.find((item) => item.id === id);
679
+ if (!category || category.locked) return;
680
+ const next = selection.includes(id) ? selection.filter((item) => item !== id) : [...selection, id];
681
+ setSelection(next);
682
+ },
683
+ [categories, selection, setSelection]
684
+ );
685
+ const handleAcceptAll = useCallback(() => {
686
+ setSelection(allIds);
687
+ onAcceptAll?.();
688
+ }, [allIds, onAcceptAll, setSelection]);
689
+ const handleRejectAll = useCallback(() => {
690
+ setSelection(lockedIds);
691
+ onRejectAll?.();
692
+ }, [lockedIds, onRejectAll, setSelection]);
693
+ const handleCustomize = useCallback(() => {
694
+ setExpanded(true);
695
+ onCustomize?.();
696
+ }, [onCustomize, setExpanded]);
697
+ const handleCollapse = useCallback(() => {
698
+ setExpanded(false);
699
+ }, [setExpanded]);
700
+ const handleConfirmSelection = useCallback(() => {
701
+ onConfirmSelection?.(selection);
702
+ }, [onConfirmSelection, selection]);
703
+ const sizing = theme.sizing.modal();
704
+ const titleToken = theme.typographyTokens.basic["body-lg"];
705
+ const bodyToken = theme.typographyTokens.basic["body-sm"];
706
+ const solidButton = (key, label, onPress, fullWidth) => /* @__PURE__ */ jsx4(
707
+ Button,
708
+ {
709
+ variant: "secondary",
710
+ size: "md",
711
+ fullWidth,
712
+ onPress,
713
+ testID: `${testID}__${key}`,
714
+ themeMode,
715
+ themeProductContext,
716
+ children: label
717
+ },
718
+ key
719
+ );
720
+ const textButton = (key, label, onPress) => /* @__PURE__ */ jsx4(
721
+ FlexButton,
722
+ {
723
+ variant: "brand",
724
+ size: "lg",
725
+ onPress,
726
+ testID: `${testID}__${key}`,
727
+ themeMode,
728
+ themeProductContext,
729
+ children: label
730
+ },
731
+ key
732
+ );
733
+ const renderActions = () => {
734
+ if (isSimple) {
735
+ return /* @__PURE__ */ jsx4(
736
+ Box,
737
+ {
738
+ width: "100%",
739
+ flexDirection: "row",
740
+ justifyContent: isMobile ? "center" : "flex-end",
741
+ children: solidButton("dismiss", t.dismiss, () => onDismiss?.(), isMobile)
742
+ }
743
+ );
744
+ }
745
+ if (isExpanded) {
746
+ const close = textButton("close", t.close, handleCollapse);
747
+ const reject = solidButton(
748
+ "reject-all",
749
+ t.rejectAll,
750
+ handleRejectAll,
751
+ isMobile
752
+ );
753
+ const confirm = solidButton(
754
+ "confirm-selection",
755
+ t.confirmSelection,
756
+ handleConfirmSelection,
757
+ isMobile
758
+ );
759
+ return isMobile ? /* @__PURE__ */ jsx4(Box, { width: "100%", gap: CONTENT_GAP, alignItems: "center", children: [confirm, reject, close] }) : /* @__PURE__ */ jsx4(
760
+ Box,
761
+ {
762
+ width: "100%",
763
+ flexDirection: "row",
764
+ alignItems: "center",
765
+ justifyContent: "flex-end",
766
+ gap: CONTENT_GAP,
767
+ children: [close, reject, confirm]
768
+ }
769
+ );
770
+ }
771
+ const customize = textButton("customize", t.customize, handleCustomize);
772
+ if (isMobile) {
773
+ return /* @__PURE__ */ jsxs2(Box, { width: "100%", gap: CONTENT_GAP, alignItems: "center", children: [
774
+ solidButton("accept-all", t.acceptAll, handleAcceptAll, true),
775
+ solidButton("reject-all", t.rejectAll, handleRejectAll, true),
776
+ customize
777
+ ] });
778
+ }
779
+ if (buttonsRow === "double") {
780
+ return /* @__PURE__ */ jsxs2(Box, { width: "100%", gap: CONTENT_GAP, children: [
781
+ /* @__PURE__ */ jsx4(Box, { flexDirection: "row", justifyContent: "center", width: "100%", children: customize }),
782
+ /* @__PURE__ */ jsxs2(Box, { flexDirection: "row", gap: CONTENT_GAP, width: "100%", children: [
783
+ /* @__PURE__ */ jsx4(Box, { flex: 1, children: solidButton("reject-all", t.rejectAll, handleRejectAll, true) }),
784
+ /* @__PURE__ */ jsx4(Box, { flex: 1, children: solidButton("accept-all", t.acceptAll, handleAcceptAll, true) })
785
+ ] })
786
+ ] });
787
+ }
788
+ return /* @__PURE__ */ jsxs2(
789
+ Box,
790
+ {
791
+ width: "100%",
792
+ flexDirection: "row",
793
+ alignItems: "center",
794
+ justifyContent: "flex-end",
795
+ gap: CONTENT_GAP,
796
+ children: [
797
+ customize,
798
+ solidButton("reject-all", t.rejectAll, handleRejectAll, false),
799
+ solidButton("accept-all", t.acceptAll, handleAcceptAll, false)
800
+ ]
801
+ }
802
+ );
803
+ };
804
+ const edge = isExpanded ? 0 : offset;
805
+ const isFixed = position === "fixed";
806
+ const spansFullWidth = isExpanded || isMobile;
807
+ return /* @__PURE__ */ jsxs2(
808
+ Box,
809
+ {
810
+ ref: containerRef,
811
+ testID,
812
+ role: "dialog",
813
+ "aria-labelledby": titleId,
814
+ onKeyDown: handleTabKey,
815
+ "data-gdpr-banner-type": type,
816
+ "data-gdpr-banner-viewport": viewport,
817
+ "data-gdpr-banner-expanded": isExpanded ? "true" : "false",
818
+ position: isFixed ? "fixed" : "relative",
819
+ bottom: isFixed ? edge : void 0,
820
+ left: isFixed && (spansFullWidth || anchor === "bottom-left") ? edge : void 0,
821
+ right: isFixed && (spansFullWidth || anchor === "bottom-right") ? edge : void 0,
822
+ zIndex: isFixed ? 1e3 : void 0,
823
+ width: spansFullWidth ? "100%" : CARD_WIDTH,
824
+ maxWidth: "100%",
825
+ backgroundColor: theme.colors.background.primary,
826
+ borderRadius: isExpanded ? 0 : sizing.borderRadius,
827
+ padding: isMobile ? 16 : 20,
828
+ gap: CONTENT_GAP,
829
+ children: [
830
+ /* @__PURE__ */ jsxs2(
831
+ Box,
832
+ {
833
+ flexDirection: "row",
834
+ alignItems: "flex-start",
835
+ justifyContent: "space-between",
836
+ gap: 12,
837
+ width: "100%",
838
+ children: [
839
+ /* @__PURE__ */ jsx4(
840
+ Text,
841
+ {
842
+ id: titleId,
843
+ color: theme.colors.content.primary,
844
+ fontSize: titleToken.fontSize,
845
+ lineHeight: titleToken.lineHeight,
846
+ fontWeight: titleToken.accent?.fontWeight ?? 500,
847
+ fontFamily: theme.fonts.body,
848
+ children: isExpanded ? t.privacySettingsTitle : t.title
849
+ }
850
+ ),
851
+ showCloseButton && onClose && /* @__PURE__ */ jsx4(
852
+ FlexButton,
853
+ {
854
+ variant: "tertiary",
855
+ size: "xs",
856
+ onPress: onClose,
857
+ "aria-label": t.closeButtonAriaLabel,
858
+ testID: `${testID}__close-icon`,
859
+ themeMode,
860
+ themeProductContext,
861
+ children: /* @__PURE__ */ jsx4(Remove, { variant: "line" })
862
+ }
863
+ )
864
+ ]
865
+ }
866
+ ),
867
+ isExpanded ? /* @__PURE__ */ jsx4(
868
+ PrivacySettings,
869
+ {
870
+ categories,
871
+ selectedCategoryIds: selection,
872
+ onToggleCategory: handleToggleCategory,
873
+ activeCategoryId,
874
+ onActiveCategoryChange: setActiveCategoryId,
875
+ viewport,
876
+ labels: {
877
+ generalInformation: t.generalInformation,
878
+ generalInformationDescription: t.generalInformationDescription,
879
+ cookiePolicy: t.cookiePolicy,
880
+ privacyPolicy: t.privacyPolicy
881
+ },
882
+ cookiePolicyUrl,
883
+ privacyPolicyUrl,
884
+ themeMode,
885
+ themeProductContext,
886
+ testID: `${testID}__privacy-settings`
887
+ }
888
+ ) : /* @__PURE__ */ jsx4(
889
+ Text,
890
+ {
891
+ color: theme.colors.content.secondary,
892
+ fontSize: bodyToken.fontSize,
893
+ lineHeight: bodyToken.lineHeight,
894
+ fontFamily: theme.fonts.body,
895
+ testID: `${testID}__description`,
896
+ children: isSimple ? t.simpleDescription : t.description
897
+ }
898
+ ),
899
+ renderActions()
900
+ ]
901
+ }
902
+ );
903
+ };
904
+ GdprBanner.displayName = "GdprBanner";
905
+ export {
906
+ DEFAULT_CATEGORIES,
907
+ DEFAULT_LABELS,
908
+ GdprBanner,
909
+ PrivacySettings
910
+ };
911
+ //# sourceMappingURL=index.mjs.map