@xsolla/xui-gdpr-banner 0.206.2 → 0.207.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/GdprBanner.tsx","../../../../foundation/primitives-native/src/Box.tsx","../../../../foundation/primitives-native/src/Text.tsx","../../src/PrivacySettings.tsx","../../src/defaults.ts"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box, Text } from \"@xsolla/xui-primitives\";\nimport { useResolvedTheme, useId } from \"@xsolla/xui-core\";\nimport { Button, FlexButton } from \"@xsolla/xui-button\";\nimport { Remove } from \"@xsolla/xui-icons-base\";\nimport { PrivacySettings } from \"./PrivacySettings\";\nimport {\n CARD_WIDTH,\n CONTENT_GAP,\n DEFAULT_CATEGORIES,\n DEFAULT_LABELS,\n EDGE_OFFSET,\n} from \"./defaults\";\nimport type { GdprBannerProps } from \"./types\";\n\nconst FOCUSABLE_SELECTORS =\n 'button:not([disabled]), [href], input:not([disabled]):not([type=\"hidden\"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\nconst isElementVisible = (element: HTMLElement): boolean => {\n if (typeof window === \"undefined\") return true;\n const style = window.getComputedStyle(element);\n return style.visibility !== \"hidden\" && style.display !== \"none\";\n};\n\nconst getFocusableElements = (container: HTMLElement): HTMLElement[] =>\n Array.from(\n container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS)\n ).filter(isElementVisible);\n\n/**\n * GdprBanner - the Xsolla cookie-consent banner.\n *\n * Implements the Figma `GDPR-banner` component set. The four Figma variant\n * axes map to props: `Type` -> {@link GdprBannerProps.type}, `Fold` ->\n * {@link GdprBannerProps.expanded} (inverted - Figma's `Fold=On` is the\n * collapsed card), `Buttons row` -> {@link GdprBannerProps.buttonsRow},\n * `Viewport` -> {@link GdprBannerProps.viewport}.\n *\n * ## Layout\n *\n * - Desktop, collapsed - a card pinned to the bottom of the viewport with a\n * 20px inset, at either bottom corner (`anchor`).\n * - Desktop, expanded - a wider panel hosting the two-column Privacy settings.\n * - Mobile - always bottom-anchored and full width; actions stack.\n *\n * `Customize` and `Close` are borderless brand-tone text actions, matching the\n * Figma `Flex Button` with `Background=False`.\n *\n * ## Accessibility\n *\n * - `role=\"dialog\"` labelled by the visible heading.\n * - Focus moves into the banner when it mounts and when the panel expands.\n * - Tab and Shift+Tab cycle within the banner; Escape fires `onClose`.\n * - The close control carries an explicit `aria-label`.\n */\nexport const GdprBanner: React.FC<GdprBannerProps> = ({\n type = \"complex\",\n viewport = \"desktop\",\n buttonsRow = \"single\",\n anchor = \"bottom-left\",\n expanded,\n defaultExpanded = false,\n onExpandedChange,\n categories = DEFAULT_CATEGORIES,\n selectedCategoryIds,\n defaultSelectedCategoryIds,\n onSelectionChange,\n labels,\n cookiePolicyUrl,\n privacyPolicyUrl,\n onAcceptAll,\n onRejectAll,\n onCustomize,\n onConfirmSelection,\n onDismiss,\n onClose,\n showCloseButton = true,\n position = \"fixed\",\n offset = EDGE_OFFSET,\n testID = \"gdpr-banner\",\n themeMode,\n themeProductContext,\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const containerRef = useRef<HTMLDivElement>(null);\n\n const rawId = useId();\n const titleId = `gdpr-banner-title-${rawId.replace(/:/g, \"\")}`;\n\n const t = useMemo(() => ({ ...DEFAULT_LABELS, ...labels }), [labels]);\n\n const isMobile = viewport === \"mobile\";\n const isSimple = type === \"simple\";\n\n const allIds = useMemo(\n () => categories.map((category) => category.id),\n [categories]\n );\n const lockedIds = useMemo(\n () => categories.filter((category) => category.locked).map((c) => c.id),\n [categories]\n );\n\n const isExpandedControlled = expanded !== undefined;\n const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);\n // `simple` has no Privacy settings panel, so it can never be expanded.\n const isExpanded =\n !isSimple && (isExpandedControlled ? expanded : internalExpanded);\n\n const isSelectionControlled = selectedCategoryIds !== undefined;\n const [internalSelection, setInternalSelection] = useState<string[]>(\n defaultSelectedCategoryIds ?? allIds\n );\n const selection = isSelectionControlled\n ? selectedCategoryIds\n : internalSelection;\n\n const [activeCategoryId, setActiveCategoryId] = useState<string | null>(null);\n\n const setExpanded = useCallback(\n (next: boolean) => {\n if (!isExpandedControlled) setInternalExpanded(next);\n onExpandedChange?.(next);\n },\n [isExpandedControlled, onExpandedChange]\n );\n\n const setSelection = useCallback(\n (next: string[]) => {\n if (!isSelectionControlled) setInternalSelection(next);\n onSelectionChange?.(next);\n },\n [isSelectionControlled, onSelectionChange]\n );\n\n // Escape dismisses the banner outright, matching the close (X) control.\n useEffect(() => {\n if (!onClose || typeof document === \"undefined\") return;\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") onClose();\n };\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [onClose]);\n\n // Pull focus in on mount and whenever the panel expands or collapses, so the\n // newly revealed controls are reachable without hunting for them.\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n const [first] = getFocusableElements(container);\n first?.focus();\n }, [isExpanded]);\n\n const handleTabKey = useCallback((event: React.KeyboardEvent) => {\n if (event.key !== \"Tab\" || !containerRef.current) return;\n const focusable = getFocusableElements(containerRef.current);\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (!first) {\n event.preventDefault();\n return;\n }\n if (event.shiftKey && document.activeElement === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && document.activeElement === last) {\n event.preventDefault();\n first.focus();\n }\n }, []);\n\n const handleToggleCategory = useCallback(\n (id: string) => {\n const category = categories.find((item) => item.id === id);\n if (!category || category.locked) return;\n const next = selection.includes(id)\n ? selection.filter((item) => item !== id)\n : [...selection, id];\n setSelection(next);\n },\n [categories, selection, setSelection]\n );\n\n const handleAcceptAll = useCallback(() => {\n setSelection(allIds);\n onAcceptAll?.();\n }, [allIds, onAcceptAll, setSelection]);\n\n const handleRejectAll = useCallback(() => {\n // Locked categories stay consented - they cannot be switched off.\n setSelection(lockedIds);\n onRejectAll?.();\n }, [lockedIds, onRejectAll, setSelection]);\n\n const handleCustomize = useCallback(() => {\n setExpanded(true);\n onCustomize?.();\n }, [onCustomize, setExpanded]);\n\n const handleCollapse = useCallback(() => {\n setExpanded(false);\n }, [setExpanded]);\n\n const handleConfirmSelection = useCallback(() => {\n onConfirmSelection?.(selection);\n }, [onConfirmSelection, selection]);\n\n const sizing = theme.sizing.modal();\n const titleToken = theme.typographyTokens.basic[\"body-lg\"];\n const bodyToken = theme.typographyTokens.basic[\"body-sm\"];\n\n /** Filled action (Reject All / Accept All / Confirm Selection / Dismiss). */\n const solidButton = (\n key: string,\n label: string,\n onPress: () => void,\n fullWidth: boolean\n ) => (\n <Button\n key={key}\n variant=\"secondary\"\n size=\"md\"\n fullWidth={fullWidth}\n onPress={onPress}\n testID={`${testID}__${key}`}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n >\n {label}\n </Button>\n );\n\n /**\n * Borderless text action (Customize / Close).\n *\n * `Button variant=\"ghost\"` cannot be used here: its border token is not\n * transparent in the light theme, so it renders a visible outline that the\n * Figma design does not have. `FlexButton` with the default\n * `background={false}` is the design-system's text-action primitive and is\n * what the Figma file instantiates (`Flex Button`, `Background=False`).\n */\n const textButton = (key: string, label: string, onPress: () => void) => (\n <FlexButton\n key={key}\n variant=\"brand\"\n size=\"lg\"\n onPress={onPress}\n testID={`${testID}__${key}`}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n >\n {label}\n </FlexButton>\n );\n\n const renderActions = () => {\n if (isSimple) {\n return (\n <Box\n width=\"100%\"\n flexDirection=\"row\"\n justifyContent={isMobile ? \"center\" : \"flex-end\"}\n >\n {solidButton(\"dismiss\", t.dismiss, () => onDismiss?.(), isMobile)}\n </Box>\n );\n }\n\n if (isExpanded) {\n const close = textButton(\"close\", t.close, handleCollapse);\n const reject = solidButton(\n \"reject-all\",\n t.rejectAll,\n handleRejectAll,\n isMobile\n );\n const confirm = solidButton(\n \"confirm-selection\",\n t.confirmSelection,\n handleConfirmSelection,\n isMobile\n );\n\n return isMobile ? (\n <Box width=\"100%\" gap={CONTENT_GAP} alignItems=\"center\">\n {[confirm, reject, close]}\n </Box>\n ) : (\n <Box\n width=\"100%\"\n flexDirection=\"row\"\n alignItems=\"center\"\n justifyContent=\"flex-end\"\n gap={CONTENT_GAP}\n >\n {[close, reject, confirm]}\n </Box>\n );\n }\n\n const customize = textButton(\"customize\", t.customize, handleCustomize);\n\n if (isMobile) {\n return (\n <Box width=\"100%\" gap={CONTENT_GAP} alignItems=\"center\">\n {solidButton(\"accept-all\", t.acceptAll, handleAcceptAll, true)}\n {solidButton(\"reject-all\", t.rejectAll, handleRejectAll, true)}\n {customize}\n </Box>\n );\n }\n\n if (buttonsRow === \"double\") {\n // Figma node 21855:20776 - Customize centred on its own row, then two\n // equal-width buttons filling the 250px content column with an 8px gap.\n return (\n <Box width=\"100%\" gap={CONTENT_GAP}>\n <Box flexDirection=\"row\" justifyContent=\"center\" width=\"100%\">\n {customize}\n </Box>\n <Box flexDirection=\"row\" gap={CONTENT_GAP} width=\"100%\">\n <Box flex={1}>\n {solidButton(\"reject-all\", t.rejectAll, handleRejectAll, true)}\n </Box>\n <Box flex={1}>\n {solidButton(\"accept-all\", t.acceptAll, handleAcceptAll, true)}\n </Box>\n </Box>\n </Box>\n );\n }\n\n return (\n <Box\n width=\"100%\"\n flexDirection=\"row\"\n alignItems=\"center\"\n justifyContent=\"flex-end\"\n gap={CONTENT_GAP}\n >\n {customize}\n {solidButton(\"reject-all\", t.rejectAll, handleRejectAll, false)}\n {solidButton(\"accept-all\", t.acceptAll, handleAcceptAll, false)}\n </Box>\n );\n };\n\n const edge = isExpanded ? 0 : offset;\n const isFixed = position === \"fixed\";\n const spansFullWidth = isExpanded || isMobile;\n\n return (\n <Box\n ref={containerRef}\n testID={testID}\n role=\"dialog\"\n aria-labelledby={titleId}\n onKeyDown={handleTabKey}\n data-gdpr-banner-type={type}\n data-gdpr-banner-viewport={viewport}\n data-gdpr-banner-expanded={isExpanded ? \"true\" : \"false\"}\n position={isFixed ? \"fixed\" : \"relative\"}\n bottom={isFixed ? edge : undefined}\n left={isFixed && (spansFullWidth || anchor === \"bottom-left\") ? edge : undefined}\n right={\n isFixed && (spansFullWidth || anchor === \"bottom-right\") ? edge : undefined\n }\n zIndex={isFixed ? 1000 : undefined}\n width={spansFullWidth ? \"100%\" : CARD_WIDTH}\n maxWidth=\"100%\"\n backgroundColor={theme.colors.background.primary}\n borderRadius={isExpanded ? 0 : sizing.borderRadius}\n padding={isMobile ? 16 : 20}\n gap={CONTENT_GAP}\n >\n <Box\n flexDirection=\"row\"\n alignItems=\"flex-start\"\n justifyContent=\"space-between\"\n gap={12}\n width=\"100%\"\n >\n <Text\n id={titleId}\n color={theme.colors.content.primary}\n fontSize={titleToken.fontSize}\n lineHeight={titleToken.lineHeight}\n fontWeight={titleToken.accent?.fontWeight ?? 500}\n fontFamily={theme.fonts.body}\n >\n {isExpanded ? t.privacySettingsTitle : t.title}\n </Text>\n\n {showCloseButton && onClose && (\n <FlexButton\n variant=\"tertiary\"\n size=\"xs\"\n onPress={onClose}\n aria-label={t.closeButtonAriaLabel}\n testID={`${testID}__close-icon`}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n >\n <Remove variant=\"line\" />\n </FlexButton>\n )}\n </Box>\n\n {isExpanded ? (\n <PrivacySettings\n categories={categories}\n selectedCategoryIds={selection}\n onToggleCategory={handleToggleCategory}\n activeCategoryId={activeCategoryId}\n onActiveCategoryChange={setActiveCategoryId}\n viewport={viewport}\n labels={{\n generalInformation: t.generalInformation,\n generalInformationDescription: t.generalInformationDescription,\n cookiePolicy: t.cookiePolicy,\n privacyPolicy: t.privacyPolicy,\n }}\n cookiePolicyUrl={cookiePolicyUrl}\n privacyPolicyUrl={privacyPolicyUrl}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n testID={`${testID}__privacy-settings`}\n />\n ) : (\n <Text\n color={theme.colors.content.secondary}\n fontSize={bodyToken.fontSize}\n lineHeight={bodyToken.lineHeight}\n fontFamily={theme.fonts.body}\n testID={`${testID}__description`}\n >\n {isSimple ? t.simpleDescription : t.description}\n </Text>\n )}\n\n {renderActions()}\n </Box>\n );\n};\n\nGdprBanner.displayName = \"GdprBanner\";\n","import React from \"react\";\nimport {\n View,\n Pressable,\n Image,\n ViewStyle,\n ImageStyle,\n DimensionValue,\n AnimatableNumericValue,\n AccessibilityRole,\n} from \"react-native\";\nimport { BoxProps } from \"@xsolla/xui-primitives-core\";\n\nconst roleToAccessibilityRole: Record<string, AccessibilityRole> = {\n button: \"button\",\n link: \"link\",\n img: \"image\",\n image: \"image\",\n summary: \"summary\",\n switch: \"switch\",\n checkbox: \"checkbox\",\n radio: \"radio\",\n heading: \"header\",\n text: \"text\",\n search: \"search\",\n alert: \"alert\",\n none: \"none\",\n presentation: \"none\",\n progressbar: \"progressbar\",\n scrollbar: \"scrollbar\",\n adjustable: \"adjustable\",\n tab: \"tab\",\n menu: \"menu\",\n menuitem: \"menuitem\",\n list: \"list\",\n timer: \"timer\",\n};\n\n/** RN ViewStyle.gap is typed as number (Yoga gap), not DimensionValue. */\nconst toGapValue = (gap: number | string | undefined): number | undefined => {\n if (gap === undefined) return undefined;\n if (typeof gap === \"number\") return gap;\n const pxMatch = /^(\\d+(?:\\.\\d+)?)px$/.exec(gap);\n if (pxMatch) return Number(pxMatch[1]);\n const asNumber = Number(gap);\n return Number.isFinite(asNumber) ? asNumber : undefined;\n};\n\nexport const Box: React.FC<BoxProps> = ({\n children,\n onPress,\n onLayout,\n onMoveShouldSetResponder,\n onResponderGrant,\n onResponderMove,\n onResponderRelease,\n onResponderTerminate,\n backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius,\n borderStyle,\n height,\n padding,\n paddingHorizontal,\n paddingVertical,\n margin,\n marginTop,\n marginBottom,\n marginLeft,\n marginRight,\n flexDirection,\n flexWrap,\n alignItems,\n justifyContent,\n alignSelf,\n position,\n top,\n bottom,\n left,\n right,\n width,\n minWidth,\n minHeight,\n maxWidth,\n maxHeight,\n flex,\n flexShrink,\n gap,\n overflow,\n opacity,\n zIndex,\n hoverStyle,\n pressStyle,\n style,\n disabled,\n \"data-testid\": dataTestId,\n testID,\n as,\n src,\n alt,\n ...rest\n}) => {\n const getContainerStyle = (pressed?: boolean): ViewStyle => ({\n backgroundColor:\n pressed && pressStyle?.backgroundColor\n ? pressStyle.backgroundColor\n : backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius: borderRadius as AnimatableNumericValue,\n borderStyle: borderStyle as ViewStyle[\"borderStyle\"],\n overflow,\n opacity: opacity ?? (disabled ? 0.5 : undefined),\n zIndex,\n height: height as DimensionValue,\n width: width as DimensionValue,\n minWidth: minWidth as DimensionValue,\n minHeight: minHeight as DimensionValue,\n maxWidth: maxWidth as DimensionValue,\n maxHeight: maxHeight as DimensionValue,\n padding: padding as DimensionValue,\n paddingHorizontal: paddingHorizontal as DimensionValue,\n paddingVertical: paddingVertical as DimensionValue,\n margin: margin as DimensionValue,\n marginTop: marginTop as DimensionValue,\n marginBottom: marginBottom as DimensionValue,\n marginLeft: marginLeft as DimensionValue,\n marginRight: marginRight as DimensionValue,\n flexDirection,\n flexWrap,\n alignItems,\n justifyContent,\n alignSelf,\n position: position as ViewStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n flex,\n flexShrink,\n gap: toGapValue(gap),\n ...(style as ViewStyle),\n });\n\n const finalTestID = dataTestId || testID;\n\n // Map web a11y props; drop the rest of the web-only attributes before\n // spreading onto RN hosts (unknown props warn at runtime).\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {\n role,\n tabIndex,\n onKeyDown,\n onKeyUp,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": _ariaLabelledBy,\n \"aria-current\": _ariaCurrent,\n \"aria-disabled\": ariaDisabled,\n \"aria-live\": ariaLive,\n \"aria-busy\": ariaBusy,\n className,\n \"data-testid\": _dataTestId,\n cursor: _cursor,\n type: _type,\n ...nativeRest\n } = rest as Record<string, unknown>;\n\n const accessibilityRole =\n (typeof role === \"string\" && roleToAccessibilityRole[role]) ||\n (as === \"button\" ? \"button\" : undefined);\n const isDisabled = Boolean(disabled || ariaDisabled);\n const accessibilityState = {\n disabled: isDisabled || undefined,\n busy: Boolean(ariaBusy) || undefined,\n };\n const accessibilityLiveRegion =\n ariaLive === \"polite\" || ariaLive === \"assertive\" ? ariaLive : undefined;\n\n // Handle as=\"img\" for React Native\n if (as === \"img\" && src) {\n const imageStyle: ImageStyle = {\n width: width as DimensionValue,\n height: height as DimensionValue,\n borderRadius: borderRadius as number,\n position: position as ImageStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n ...(style as ImageStyle),\n };\n\n return (\n <Image\n source={{ uri: src }}\n style={imageStyle}\n testID={finalTestID}\n accessibilityLabel={typeof ariaLabel === \"string\" ? ariaLabel : alt}\n resizeMode=\"cover\"\n {...nativeRest}\n />\n );\n }\n\n // Prefer Pressable whenever the node is interactive or explicitly a button,\n // so disabled / aria-disabled controls stay in the a11y tree as buttons.\n if (onPress || as === \"button\") {\n return (\n <Pressable\n onPress={!isDisabled && onPress ? onPress : undefined}\n disabled={isDisabled}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n style={({ pressed }) => getContainerStyle(pressed)}\n testID={finalTestID}\n accessibilityRole={accessibilityRole}\n accessibilityLabel={\n typeof ariaLabel === \"string\" ? ariaLabel : undefined\n }\n accessibilityState={accessibilityState}\n accessibilityLiveRegion={accessibilityLiveRegion}\n {...nativeRest}\n >\n {children}\n </Pressable>\n );\n }\n\n return (\n <View\n style={getContainerStyle()}\n testID={finalTestID}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n accessibilityRole={accessibilityRole}\n accessibilityLabel={typeof ariaLabel === \"string\" ? ariaLabel : undefined}\n accessibilityState={accessibilityState}\n accessibilityLiveRegion={accessibilityLiveRegion}\n {...nativeRest}\n >\n {children}\n </View>\n );\n};\n","import React from \"react\";\nimport {\n Text as RNText,\n TextStyle,\n AccessibilityRole,\n StyleSheet,\n} from \"react-native\";\nimport { TextProps } from \"@xsolla/xui-primitives-core\";\n\nconst roleMap: Record<string, AccessibilityRole> = {\n alert: \"alert\",\n heading: \"header\",\n button: \"button\",\n link: \"link\",\n text: \"text\",\n};\n\nconst parseNumericValue = (\n value: string | number | undefined\n): number | undefined => {\n if (value === undefined) return undefined;\n if (typeof value === \"number\") return value;\n const parsed = parseFloat(value);\n return isNaN(parsed) ? undefined : parsed;\n};\n\nexport const Text: React.FC<TextProps> = ({\n children,\n color,\n fontSize,\n fontWeight,\n fontFamily,\n textAlign,\n lineHeight,\n numberOfLines,\n id,\n role,\n testID,\n \"data-testid\": dataTestId,\n style: styleProp,\n ...props\n}) => {\n let resolvedFontFamily = fontFamily\n ? fontFamily.split(\",\")[0].replace(/['\"]/g, \"\").trim()\n : undefined;\n\n if (\n resolvedFontFamily === \"Pilat Wide\" ||\n resolvedFontFamily === \"Pilat Wide Bold\" ||\n resolvedFontFamily === \"Aktiv Grotesk\"\n ) {\n resolvedFontFamily = undefined;\n }\n\n const incomingStyle = StyleSheet.flatten(styleProp) as TextStyle | undefined;\n\n const baseStyle: TextStyle = {\n color: color ?? incomingStyle?.color,\n fontSize: typeof fontSize === \"number\" ? fontSize : undefined,\n fontWeight: fontWeight as TextStyle[\"fontWeight\"],\n fontFamily: resolvedFontFamily,\n textDecorationLine: props.textDecoration as TextStyle[\"textDecorationLine\"],\n textAlign: textAlign ?? incomingStyle?.textAlign,\n lineHeight: parseNumericValue(lineHeight ?? incomingStyle?.lineHeight),\n marginTop: parseNumericValue(\n incomingStyle?.marginTop as number | string | undefined\n ),\n marginBottom: parseNumericValue(\n incomingStyle?.marginBottom as number | string | undefined\n ),\n };\n\n const accessibilityRole = role ? roleMap[role] : undefined;\n\n return (\n <RNText\n style={baseStyle}\n numberOfLines={numberOfLines}\n testID={dataTestId || testID || id}\n accessibilityRole={accessibilityRole}\n >\n {children}\n </RNText>\n );\n};\n","import React from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box, Text } from \"@xsolla/xui-primitives\";\nimport { useResolvedTheme } from \"@xsolla/xui-core\";\nimport { Checkbox } from \"@xsolla/xui-checkbox\";\nimport { Link } from \"@xsolla/xui-link\";\nimport { InfoCr } from \"@xsolla/xui-icons-base\";\nimport type { PrivacySettingsProps } from \"./types\";\n\nconst PANE_RADIUS = 8;\nconst LIST_WIDTH = 280;\n\n/**\n * The expanded half of the GDPR banner: a category list on the left and the\n * selected category's explanation on the right (stacked on mobile, where the\n * explanation renders directly beneath the row it belongs to).\n *\n * Rendered by `GdprBanner` when the Privacy settings panel is open; exported\n * so consumers can embed the same panel inside their own settings screen.\n */\nexport const PrivacySettings: React.FC<PrivacySettingsProps> = ({\n categories,\n selectedCategoryIds,\n onToggleCategory,\n activeCategoryId,\n onActiveCategoryChange,\n viewport,\n labels,\n cookiePolicyUrl,\n privacyPolicyUrl,\n testID = \"gdpr-privacy-settings\",\n themeMode,\n themeProductContext,\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const isMobile = viewport === \"mobile\";\n const body = theme.typographyTokens.basic[\"body-sm\"];\n\n const activeDescription =\n activeCategoryId === null\n ? labels.generalInformationDescription\n : (categories.find((category) => category.id === activeCategoryId)\n ?.description ?? \"\");\n\n const descriptionPane = (\n <Box\n testID={`${testID}__description`}\n backgroundColor={theme.colors.control.faint.bg}\n borderRadius={PANE_RADIUS}\n padding={16}\n flex={isMobile ? undefined : 1}\n width={isMobile ? \"100%\" : undefined}\n aria-live=\"polite\"\n >\n <Text\n color={theme.colors.content.secondary}\n fontSize={body.fontSize}\n lineHeight={body.lineHeight}\n fontFamily={theme.fonts.body}\n >\n {activeDescription}\n </Text>\n </Box>\n );\n\n const focusStyle = {\n outlineColor: theme.colors.border.brand,\n outlineWidth: 2,\n outlineOffset: 2,\n outlineStyle: \"solid\",\n };\n\n const list = (\n <Box\n gap={12}\n width={isMobile ? \"100%\" : LIST_WIDTH}\n flexShrink={0}\n testID={`${testID}__categories`}\n >\n <Box gap={8}>\n <Box\n flexDirection=\"row\"\n alignItems=\"center\"\n gap={8}\n role=\"button\"\n tabIndex={0}\n cursor=\"pointer\"\n aria-pressed={activeCategoryId === null}\n onPress={() => onActiveCategoryChange(null)}\n onKeyDown={(event: React.KeyboardEvent) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n onActiveCategoryChange(null);\n }\n }}\n focusStyle={focusStyle}\n testID={`${testID}__general-information`}\n >\n <InfoCr\n variant=\"solid\"\n size={16}\n color={theme.colors.control.link.primary}\n />\n <Text\n color={theme.colors.content.primary}\n fontSize={body.fontSize}\n lineHeight={body.lineHeight}\n fontFamily={theme.fonts.body}\n >\n {labels.generalInformation}\n </Text>\n </Box>\n {isMobile && activeCategoryId === null && descriptionPane}\n </Box>\n\n {categories.map((category) => (\n <Box key={category.id} gap={8}>\n <Box onPress={() => onActiveCategoryChange(category.id)}>\n <Checkbox\n size=\"md\"\n checked={selectedCategoryIds.includes(category.id)}\n disabled={category.locked}\n onChange={() => onToggleCategory(category.id)}\n testID={`${testID}__category-${category.id}`}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n >\n {category.label}\n </Checkbox>\n </Box>\n {isMobile && activeCategoryId === category.id && descriptionPane}\n </Box>\n ))}\n </Box>\n );\n\n return (\n <Box testID={testID} gap={16} width=\"100%\">\n <Box\n flexDirection={isMobile ? \"column\" : \"row\"}\n gap={isMobile ? 12 : 24}\n alignItems=\"flex-start\"\n width=\"100%\"\n >\n {list}\n {!isMobile && descriptionPane}\n </Box>\n\n <Box\n flexDirection=\"row\"\n alignItems=\"center\"\n gap={8}\n testID={`${testID}__policies`}\n >\n <Link\n size=\"sm\"\n href={cookiePolicyUrl}\n target=\"_blank\"\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n testID={`${testID}__cookie-policy`}\n >\n {labels.cookiePolicy}\n </Link>\n <Text\n color={theme.colors.content.tertiary}\n fontSize={body.fontSize}\n lineHeight={body.lineHeight}\n fontFamily={theme.fonts.body}\n aria-hidden={true}\n >\n {\"•\"}\n </Text>\n <Link\n size=\"sm\"\n href={privacyPolicyUrl}\n target=\"_blank\"\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n testID={`${testID}__privacy-policy`}\n >\n {labels.privacyPolicy}\n </Link>\n </Box>\n </Box>\n );\n};\n\nPrivacySettings.displayName = \"PrivacySettings\";\n","import type { GdprBannerLabels, GdprCookieCategory } from \"./types\";\n\n/**\n * The five categories shown in the Figma Privacy settings panel.\n *\n * `essential` is `locked`: it renders checked + disabled and is always part of\n * the reported selection, including after `Reject All`.\n */\nexport const DEFAULT_CATEGORIES: GdprCookieCategory[] = [\n {\n id: \"essential\",\n label: \"Essential cookies\",\n locked: true,\n description:\n \"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.\",\n },\n {\n id: \"analytical\",\n label: \"Analytical and performance cookies\",\n description:\n \"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.\",\n },\n {\n id: \"functional\",\n label: \"Functional cookies\",\n description:\n \"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.\",\n },\n {\n id: \"targeting\",\n label: \"Targeting cookies\",\n description:\n \"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.\",\n },\n];\n\nexport const DEFAULT_LABELS: Required<GdprBannerLabels> = {\n title: \"How we use cookies\",\n description:\n \"We use cookies to personalize your experience. If you reject the use of cookies, your experience will not be tailored to your preferences.\",\n simpleDescription:\n \"We only use essential cookies that allow us to run this service. Read our cookie policy to learn more.\",\n privacySettingsTitle: \"Privacy settings\",\n generalInformation: \"General information\",\n generalInformationDescription:\n \"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.\",\n customize: \"Customize\",\n rejectAll: \"Reject All\",\n acceptAll: \"Accept All\",\n confirmSelection: \"Confirm Selection\",\n dismiss: \"Dismiss\",\n close: \"Close\",\n cookiePolicy: \"Cookie Policy\",\n privacyPolicy: \"Privacy Policy\",\n closeButtonAriaLabel: \"Close cookie banner\",\n};\n\n/** Collapsed desktop card width, from the Figma frame. */\nexport const CARD_WIDTH = 400;\n\n/** Default bottom / left / right inset of the collapsed banner, from Figma. */\nexport const EDGE_OFFSET = 20;\n\n/**\n * Vertical and horizontal gap between stacked content blocks and between\n * adjacent action buttons.\n *\n * 8px, measured on the Figma content column (`itemSpacing: 8` on the auto-\n * layout frame inside node 21855:20776).\n */\nexport const CONTENT_GAP = 8;\n"],"mappings":"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","names":["jsx","useResolvedTheme","jsx","jsx","jsxs","useResolvedTheme"]}
1
+ {"version":3,"sources":["../../src/GdprBanner.tsx","../../../../foundation/primitives-native/src/Box.tsx","../../../../foundation/primitives-native/src/Text.tsx","../../src/PrivacySettings.tsx","../../src/defaults.ts"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box, Text } from \"@xsolla/xui-primitives\";\nimport { useResolvedTheme, useId } from \"@xsolla/xui-core\";\nimport { Button, FlexButton } from \"@xsolla/xui-button\";\nimport { Remove } from \"@xsolla/xui-icons-base\";\nimport { PrivacySettings } from \"./PrivacySettings\";\nimport {\n ACTION_BUTTON_SIZE,\n CARD_WIDTH,\n CONTENT_GAP,\n DEFAULT_CATEGORIES,\n DEFAULT_LABELS,\n EDGE_OFFSET,\n} from \"./defaults\";\nimport type { GdprBannerProps } from \"./types\";\n\nconst FOCUSABLE_SELECTORS =\n 'button:not([disabled]), [href], input:not([disabled]):not([type=\"hidden\"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\nconst isElementVisible = (element: HTMLElement): boolean => {\n if (typeof window === \"undefined\") return true;\n const style = window.getComputedStyle(element);\n return style.visibility !== \"hidden\" && style.display !== \"none\";\n};\n\nconst getFocusableElements = (container: HTMLElement): HTMLElement[] =>\n Array.from(\n container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS)\n ).filter(isElementVisible);\n\n/**\n * GdprBanner - the Xsolla cookie-consent banner.\n *\n * Implements the Figma `GDPR-banner` component set. The four Figma variant\n * axes map to props: `Type` -> {@link GdprBannerProps.type}, `Fold` ->\n * {@link GdprBannerProps.expanded} (inverted - Figma's `Fold=On` is the\n * collapsed card), `Buttons row` -> {@link GdprBannerProps.buttonsRow},\n * `Viewport` -> {@link GdprBannerProps.viewport}.\n *\n * ## Layout\n *\n * - Desktop, collapsed - a card pinned to the bottom of the viewport with a\n * 20px inset, at either bottom corner (`anchor`). Its top corners use the\n * modal radius token and its bottom corners the toast radius token.\n * - Desktop, expanded - a wider panel hosting the two-column Privacy settings.\n * It sits flush with the bottom edge, so its bottom corners are square.\n * - Mobile - always bottom-anchored, full width and flush against the bottom\n * edge: `offset` is not applied and the bottom corners are square.\n *\n * `Customize` and `Close` are borderless brand-tone text actions, matching the\n * Figma `Button` with `Type=Tertiary` / `Tone=Brand`.\n *\n * ## Accessibility\n *\n * - `role=\"dialog\"` labelled by the visible heading.\n * - Focus moves into the banner when it mounts and when the panel expands.\n * - Tab and Shift+Tab cycle within the banner; Escape fires `onClose`.\n * - The close control carries an explicit `aria-label`.\n */\nexport const GdprBanner: React.FC<GdprBannerProps> = ({\n type = \"complex\",\n viewport = \"desktop\",\n buttonsRow = \"single\",\n anchor = \"bottom-left\",\n expanded,\n defaultExpanded = false,\n onExpandedChange,\n categories = DEFAULT_CATEGORIES,\n selectedCategoryIds,\n defaultSelectedCategoryIds,\n onSelectionChange,\n labels,\n cookiePolicyUrl,\n privacyPolicyUrl,\n onAcceptAll,\n onRejectAll,\n onCustomize,\n onConfirmSelection,\n onDismiss,\n onClose,\n showCloseButton = true,\n position = \"fixed\",\n offset = EDGE_OFFSET,\n testID = \"gdpr-banner\",\n themeMode,\n themeProductContext,\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const containerRef = useRef<HTMLDivElement>(null);\n\n const rawId = useId();\n const titleId = `gdpr-banner-title-${rawId.replace(/:/g, \"\")}`;\n\n const t = useMemo(() => ({ ...DEFAULT_LABELS, ...labels }), [labels]);\n\n const isMobile = viewport === \"mobile\";\n const isSimple = type === \"simple\";\n\n const allIds = useMemo(\n () => categories.map((category) => category.id),\n [categories]\n );\n const lockedIds = useMemo(\n () => categories.filter((category) => category.locked).map((c) => c.id),\n [categories]\n );\n\n const isExpandedControlled = expanded !== undefined;\n const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);\n // `simple` has no Privacy settings panel, so it can never be expanded.\n const isExpanded =\n !isSimple && (isExpandedControlled ? expanded : internalExpanded);\n\n const isSelectionControlled = selectedCategoryIds !== undefined;\n const [internalSelection, setInternalSelection] = useState<string[]>(\n defaultSelectedCategoryIds ?? allIds\n );\n const selection = isSelectionControlled\n ? selectedCategoryIds\n : internalSelection;\n\n const [activeCategoryId, setActiveCategoryId] = useState<string | null>(null);\n\n const setExpanded = useCallback(\n (next: boolean) => {\n if (!isExpandedControlled) setInternalExpanded(next);\n onExpandedChange?.(next);\n },\n [isExpandedControlled, onExpandedChange]\n );\n\n const setSelection = useCallback(\n (next: string[]) => {\n if (!isSelectionControlled) setInternalSelection(next);\n onSelectionChange?.(next);\n },\n [isSelectionControlled, onSelectionChange]\n );\n\n // Escape dismisses the banner outright, matching the close (X) control.\n useEffect(() => {\n if (!onClose || typeof document === \"undefined\") return;\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") onClose();\n };\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [onClose]);\n\n // Pull focus in on mount and whenever the panel expands or collapses, so the\n // newly revealed controls are reachable without hunting for them.\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n const [first] = getFocusableElements(container);\n first?.focus();\n }, [isExpanded]);\n\n const handleTabKey = useCallback((event: React.KeyboardEvent) => {\n if (event.key !== \"Tab\" || !containerRef.current) return;\n const focusable = getFocusableElements(containerRef.current);\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (!first) {\n event.preventDefault();\n return;\n }\n if (event.shiftKey && document.activeElement === first) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && document.activeElement === last) {\n event.preventDefault();\n first.focus();\n }\n }, []);\n\n const handleToggleCategory = useCallback(\n (id: string) => {\n const category = categories.find((item) => item.id === id);\n if (!category || category.locked) return;\n const next = selection.includes(id)\n ? selection.filter((item) => item !== id)\n : [...selection, id];\n setSelection(next);\n },\n [categories, selection, setSelection]\n );\n\n const handleAcceptAll = useCallback(() => {\n setSelection(allIds);\n onAcceptAll?.();\n }, [allIds, onAcceptAll, setSelection]);\n\n const handleRejectAll = useCallback(() => {\n // Locked categories stay consented - they cannot be switched off.\n setSelection(lockedIds);\n onRejectAll?.();\n }, [lockedIds, onRejectAll, setSelection]);\n\n const handleCustomize = useCallback(() => {\n setExpanded(true);\n onCustomize?.();\n }, [onCustomize, setExpanded]);\n\n const handleCollapse = useCallback(() => {\n setExpanded(false);\n }, [setExpanded]);\n\n const handleConfirmSelection = useCallback(() => {\n onConfirmSelection?.(selection);\n }, [onConfirmSelection, selection]);\n\n const titleToken = theme.typographyTokens.basic[\"body-lg\"];\n const bodyToken = theme.typographyTokens.basic[\"body-sm\"];\n\n /** Filled action (Reject All / Accept All / Confirm Selection / Dismiss). */\n const solidButton = (\n key: string,\n label: string,\n onPress: () => void,\n fullWidth: boolean\n ) => (\n <Button\n key={key}\n variant=\"secondary\"\n size={ACTION_BUTTON_SIZE}\n fullWidth={fullWidth}\n onPress={onPress}\n testID={`${testID}__${key}`}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n >\n {label}\n </Button>\n );\n\n /**\n * Borderless text action (Customize / Close).\n *\n * Figma builds these from the same `Button` component as the filled actions\n * beside them, switching only the `Type` axis to `Tertiary` (`Tone=Brand`) -\n * see nodes 21858:22234 (`Customize`) and 21858:22512 (`Close`). Rendering\n * them as a `FlexButton` instead made them a different control with its own\n * height, padding and type scale, so the action row read as mismatched.\n *\n * `Button variant=\"ghost\"` is still not an option: its border token is not\n * transparent, so it draws a visible outline the design does not have.\n * `tertiary` is - `control.brand.tertiary` resolves `bg` and `border` to a\n * fully transparent colour in both `light.json` and `dark.json`, and the\n * label picks up `control.brand.text.tertiary`, the brand tone the design\n * uses.\n */\n const textButton = (key: string, label: string, onPress: () => void) => (\n <Button\n key={key}\n variant=\"tertiary\"\n tone=\"brand\"\n size={ACTION_BUTTON_SIZE}\n onPress={onPress}\n testID={`${testID}__${key}`}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n >\n {label}\n </Button>\n );\n\n const renderActions = () => {\n if (isSimple) {\n return (\n <Box\n width=\"100%\"\n flexDirection=\"row\"\n justifyContent={isMobile ? \"center\" : \"flex-end\"}\n >\n {solidButton(\"dismiss\", t.dismiss, () => onDismiss?.(), isMobile)}\n </Box>\n );\n }\n\n if (isExpanded) {\n const close = textButton(\"close\", t.close, handleCollapse);\n const reject = solidButton(\n \"reject-all\",\n t.rejectAll,\n handleRejectAll,\n isMobile\n );\n const confirm = solidButton(\n \"confirm-selection\",\n t.confirmSelection,\n handleConfirmSelection,\n isMobile\n );\n\n return isMobile ? (\n <Box width=\"100%\" gap={CONTENT_GAP} alignItems=\"center\">\n {[confirm, reject, close]}\n </Box>\n ) : (\n <Box\n width=\"100%\"\n flexDirection=\"row\"\n alignItems=\"center\"\n justifyContent=\"flex-end\"\n gap={CONTENT_GAP}\n >\n {[close, reject, confirm]}\n </Box>\n );\n }\n\n const customize = textButton(\"customize\", t.customize, handleCustomize);\n\n if (isMobile) {\n return (\n <Box width=\"100%\" gap={CONTENT_GAP} alignItems=\"center\">\n {solidButton(\"accept-all\", t.acceptAll, handleAcceptAll, true)}\n {solidButton(\"reject-all\", t.rejectAll, handleRejectAll, true)}\n {customize}\n </Box>\n );\n }\n\n if (buttonsRow === \"double\") {\n // Figma node 21855:20776 - Customize centred on its own row, then two\n // equal-width buttons filling the 250px content column with an 8px gap.\n return (\n <Box width=\"100%\" gap={CONTENT_GAP}>\n <Box flexDirection=\"row\" justifyContent=\"center\" width=\"100%\">\n {customize}\n </Box>\n <Box flexDirection=\"row\" gap={CONTENT_GAP} width=\"100%\">\n <Box flex={1}>\n {solidButton(\"reject-all\", t.rejectAll, handleRejectAll, true)}\n </Box>\n <Box flex={1}>\n {solidButton(\"accept-all\", t.acceptAll, handleAcceptAll, true)}\n </Box>\n </Box>\n </Box>\n );\n }\n\n return (\n <Box\n width=\"100%\"\n flexDirection=\"row\"\n alignItems=\"center\"\n justifyContent=\"flex-end\"\n gap={CONTENT_GAP}\n >\n {customize}\n {solidButton(\"reject-all\", t.rejectAll, handleRejectAll, false)}\n {solidButton(\"accept-all\", t.acceptAll, handleAcceptAll, false)}\n </Box>\n );\n };\n\n // Mobile is a bottom sheet and the expanded desktop panel is a full-bleed\n // bar, so both sit flush against the viewport edges; only the collapsed\n // desktop card is inset.\n const isFlushWithEdges = isExpanded || isMobile;\n const edge = isFlushWithEdges ? 0 : offset;\n const isFixed = position === \"fixed\";\n const spansFullWidth = isExpanded || isMobile;\n\n /**\n * Figma binds the banner's four corners to two different radius tokens: the\n * modal frame radius on top, and - on the collapsed desktop card only - the\n * toast frame radius on the bottom (node 21855:20787). Anything flush with\n * the bottom edge keeps square bottom corners (nodes 21855:20743 mobile /\n * expanded).\n *\n * Written as the four longhand properties rather than a `border-radius`\n * shorthand string: the native `Box` forwards `borderRadius` straight into a\n * React Native `ViewStyle`, which only accepts a number, so a shorthand\n * would silently drop on native.\n */\n const topCornerRadius = theme.shape.modal.borderRadius;\n const bottomCornerRadius = isFlushWithEdges\n ? 0\n : theme.shape.toast.borderRadius;\n const cornerRadiusStyle = {\n borderTopLeftRadius: topCornerRadius,\n borderTopRightRadius: topCornerRadius,\n borderBottomLeftRadius: bottomCornerRadius,\n borderBottomRightRadius: bottomCornerRadius,\n };\n\n return (\n <Box\n ref={containerRef}\n testID={testID}\n role=\"dialog\"\n aria-labelledby={titleId}\n onKeyDown={handleTabKey}\n data-gdpr-banner-type={type}\n data-gdpr-banner-viewport={viewport}\n data-gdpr-banner-expanded={isExpanded ? \"true\" : \"false\"}\n position={isFixed ? \"fixed\" : \"relative\"}\n bottom={isFixed ? edge : undefined}\n left={isFixed && (spansFullWidth || anchor === \"bottom-left\") ? edge : undefined}\n right={\n isFixed && (spansFullWidth || anchor === \"bottom-right\") ? edge : undefined\n }\n zIndex={isFixed ? 1000 : undefined}\n width={spansFullWidth ? \"100%\" : CARD_WIDTH}\n maxWidth=\"100%\"\n backgroundColor={theme.colors.layer.float}\n style={cornerRadiusStyle}\n padding={isMobile ? 16 : 20}\n gap={CONTENT_GAP}\n >\n <Box\n flexDirection=\"row\"\n alignItems=\"flex-start\"\n justifyContent=\"space-between\"\n gap={12}\n width=\"100%\"\n >\n <Text\n id={titleId}\n color={theme.colors.content.primary}\n fontSize={titleToken.fontSize}\n lineHeight={titleToken.lineHeight}\n fontWeight={titleToken.accent?.fontWeight ?? 500}\n fontFamily={theme.fonts.body}\n >\n {isExpanded ? t.privacySettingsTitle : t.title}\n </Text>\n\n {showCloseButton && onClose && (\n <FlexButton\n variant=\"tertiary\"\n size=\"xs\"\n onPress={onClose}\n aria-label={t.closeButtonAriaLabel}\n testID={`${testID}__close-icon`}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n >\n <Remove variant=\"line\" />\n </FlexButton>\n )}\n </Box>\n\n {isExpanded ? (\n <PrivacySettings\n categories={categories}\n selectedCategoryIds={selection}\n onToggleCategory={handleToggleCategory}\n activeCategoryId={activeCategoryId}\n onActiveCategoryChange={setActiveCategoryId}\n viewport={viewport}\n labels={{\n generalInformation: t.generalInformation,\n generalInformationDescription: t.generalInformationDescription,\n cookiePolicy: t.cookiePolicy,\n privacyPolicy: t.privacyPolicy,\n }}\n cookiePolicyUrl={cookiePolicyUrl}\n privacyPolicyUrl={privacyPolicyUrl}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n testID={`${testID}__privacy-settings`}\n />\n ) : (\n <Text\n color={theme.colors.content.secondary}\n fontSize={bodyToken.fontSize}\n lineHeight={bodyToken.lineHeight}\n fontFamily={theme.fonts.body}\n testID={`${testID}__description`}\n >\n {isSimple ? t.simpleDescription : t.description}\n </Text>\n )}\n\n {renderActions()}\n </Box>\n );\n};\n\nGdprBanner.displayName = \"GdprBanner\";\n","import React from \"react\";\nimport {\n View,\n Pressable,\n Image,\n ViewStyle,\n ImageStyle,\n DimensionValue,\n AnimatableNumericValue,\n AccessibilityRole,\n} from \"react-native\";\nimport { BoxProps } from \"@xsolla/xui-primitives-core\";\n\nconst roleToAccessibilityRole: Record<string, AccessibilityRole> = {\n button: \"button\",\n link: \"link\",\n img: \"image\",\n image: \"image\",\n summary: \"summary\",\n switch: \"switch\",\n checkbox: \"checkbox\",\n radio: \"radio\",\n heading: \"header\",\n text: \"text\",\n search: \"search\",\n alert: \"alert\",\n none: \"none\",\n presentation: \"none\",\n progressbar: \"progressbar\",\n scrollbar: \"scrollbar\",\n adjustable: \"adjustable\",\n tab: \"tab\",\n menu: \"menu\",\n menuitem: \"menuitem\",\n list: \"list\",\n timer: \"timer\",\n};\n\n/** RN ViewStyle.gap is typed as number (Yoga gap), not DimensionValue. */\nconst toGapValue = (gap: number | string | undefined): number | undefined => {\n if (gap === undefined) return undefined;\n if (typeof gap === \"number\") return gap;\n const pxMatch = /^(\\d+(?:\\.\\d+)?)px$/.exec(gap);\n if (pxMatch) return Number(pxMatch[1]);\n const asNumber = Number(gap);\n return Number.isFinite(asNumber) ? asNumber : undefined;\n};\n\nexport const Box: React.FC<BoxProps> = ({\n children,\n onPress,\n onLayout,\n onMoveShouldSetResponder,\n onResponderGrant,\n onResponderMove,\n onResponderRelease,\n onResponderTerminate,\n backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius,\n borderStyle,\n height,\n padding,\n paddingHorizontal,\n paddingVertical,\n margin,\n marginTop,\n marginBottom,\n marginLeft,\n marginRight,\n flexDirection,\n flexWrap,\n alignItems,\n justifyContent,\n alignSelf,\n position,\n top,\n bottom,\n left,\n right,\n width,\n minWidth,\n minHeight,\n maxWidth,\n maxHeight,\n flex,\n flexShrink,\n gap,\n overflow,\n opacity,\n zIndex,\n hoverStyle,\n pressStyle,\n style,\n disabled,\n \"data-testid\": dataTestId,\n testID,\n as,\n src,\n alt,\n ...rest\n}) => {\n const getContainerStyle = (pressed?: boolean): ViewStyle => ({\n backgroundColor:\n pressed && pressStyle?.backgroundColor\n ? pressStyle.backgroundColor\n : backgroundColor,\n borderColor,\n borderWidth,\n borderBottomWidth,\n borderBottomColor,\n borderTopWidth,\n borderTopColor,\n borderLeftWidth,\n borderLeftColor,\n borderRightWidth,\n borderRightColor,\n borderRadius: borderRadius as AnimatableNumericValue,\n borderStyle: borderStyle as ViewStyle[\"borderStyle\"],\n overflow,\n opacity: opacity ?? (disabled ? 0.5 : undefined),\n zIndex,\n height: height as DimensionValue,\n width: width as DimensionValue,\n minWidth: minWidth as DimensionValue,\n minHeight: minHeight as DimensionValue,\n maxWidth: maxWidth as DimensionValue,\n maxHeight: maxHeight as DimensionValue,\n padding: padding as DimensionValue,\n paddingHorizontal: paddingHorizontal as DimensionValue,\n paddingVertical: paddingVertical as DimensionValue,\n margin: margin as DimensionValue,\n marginTop: marginTop as DimensionValue,\n marginBottom: marginBottom as DimensionValue,\n marginLeft: marginLeft as DimensionValue,\n marginRight: marginRight as DimensionValue,\n flexDirection,\n flexWrap,\n alignItems,\n justifyContent,\n alignSelf,\n position: position as ViewStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n flex,\n flexShrink,\n gap: toGapValue(gap),\n ...(style as ViewStyle),\n });\n\n const finalTestID = dataTestId || testID;\n\n // Map web a11y props; drop the rest of the web-only attributes before\n // spreading onto RN hosts (unknown props warn at runtime).\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {\n role,\n tabIndex,\n onKeyDown,\n onKeyUp,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": _ariaLabelledBy,\n \"aria-current\": _ariaCurrent,\n \"aria-disabled\": ariaDisabled,\n \"aria-live\": ariaLive,\n \"aria-busy\": ariaBusy,\n className,\n \"data-testid\": _dataTestId,\n cursor: _cursor,\n type: _type,\n ...nativeRest\n } = rest as Record<string, unknown>;\n\n const accessibilityRole =\n (typeof role === \"string\" && roleToAccessibilityRole[role]) ||\n (as === \"button\" ? \"button\" : undefined);\n const isDisabled = Boolean(disabled || ariaDisabled);\n const accessibilityState = {\n disabled: isDisabled || undefined,\n busy: Boolean(ariaBusy) || undefined,\n };\n const accessibilityLiveRegion =\n ariaLive === \"polite\" || ariaLive === \"assertive\" ? ariaLive : undefined;\n\n // Handle as=\"img\" for React Native\n if (as === \"img\" && src) {\n const imageStyle: ImageStyle = {\n width: width as DimensionValue,\n height: height as DimensionValue,\n borderRadius: borderRadius as number,\n position: position as ImageStyle[\"position\"],\n top: top as DimensionValue,\n bottom: bottom as DimensionValue,\n left: left as DimensionValue,\n right: right as DimensionValue,\n ...(style as ImageStyle),\n };\n\n return (\n <Image\n source={{ uri: src }}\n style={imageStyle}\n testID={finalTestID}\n accessibilityLabel={typeof ariaLabel === \"string\" ? ariaLabel : alt}\n resizeMode=\"cover\"\n {...nativeRest}\n />\n );\n }\n\n // Prefer Pressable whenever the node is interactive or explicitly a button,\n // so disabled / aria-disabled controls stay in the a11y tree as buttons.\n if (onPress || as === \"button\") {\n return (\n <Pressable\n onPress={!isDisabled && onPress ? onPress : undefined}\n disabled={isDisabled}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n style={({ pressed }) => getContainerStyle(pressed)}\n testID={finalTestID}\n accessibilityRole={accessibilityRole}\n accessibilityLabel={\n typeof ariaLabel === \"string\" ? ariaLabel : undefined\n }\n accessibilityState={accessibilityState}\n accessibilityLiveRegion={accessibilityLiveRegion}\n {...nativeRest}\n >\n {children}\n </Pressable>\n );\n }\n\n return (\n <View\n style={getContainerStyle()}\n testID={finalTestID}\n onLayout={onLayout}\n onMoveShouldSetResponder={onMoveShouldSetResponder}\n onResponderGrant={onResponderGrant}\n onResponderMove={onResponderMove}\n onResponderRelease={onResponderRelease}\n onResponderTerminate={onResponderTerminate}\n accessibilityRole={accessibilityRole}\n accessibilityLabel={typeof ariaLabel === \"string\" ? ariaLabel : undefined}\n accessibilityState={accessibilityState}\n accessibilityLiveRegion={accessibilityLiveRegion}\n {...nativeRest}\n >\n {children}\n </View>\n );\n};\n","import React from \"react\";\nimport {\n Text as RNText,\n TextStyle,\n AccessibilityRole,\n StyleSheet,\n} from \"react-native\";\nimport { TextProps } from \"@xsolla/xui-primitives-core\";\n\nconst roleMap: Record<string, AccessibilityRole> = {\n alert: \"alert\",\n heading: \"header\",\n button: \"button\",\n link: \"link\",\n text: \"text\",\n};\n\nconst parseNumericValue = (\n value: string | number | undefined\n): number | undefined => {\n if (value === undefined) return undefined;\n if (typeof value === \"number\") return value;\n const parsed = parseFloat(value);\n return isNaN(parsed) ? undefined : parsed;\n};\n\nexport const Text: React.FC<TextProps> = ({\n children,\n color,\n fontSize,\n fontWeight,\n fontFamily,\n textAlign,\n lineHeight,\n numberOfLines,\n id,\n role,\n testID,\n \"data-testid\": dataTestId,\n style: styleProp,\n ...props\n}) => {\n let resolvedFontFamily = fontFamily\n ? fontFamily.split(\",\")[0].replace(/['\"]/g, \"\").trim()\n : undefined;\n\n if (\n resolvedFontFamily === \"Pilat Wide\" ||\n resolvedFontFamily === \"Pilat Wide Bold\" ||\n resolvedFontFamily === \"Aktiv Grotesk\"\n ) {\n resolvedFontFamily = undefined;\n }\n\n const incomingStyle = StyleSheet.flatten(styleProp) as TextStyle | undefined;\n\n const baseStyle: TextStyle = {\n color: color ?? incomingStyle?.color,\n fontSize: typeof fontSize === \"number\" ? fontSize : undefined,\n fontWeight: fontWeight as TextStyle[\"fontWeight\"],\n fontFamily: resolvedFontFamily,\n textDecorationLine: props.textDecoration as TextStyle[\"textDecorationLine\"],\n textAlign: textAlign ?? incomingStyle?.textAlign,\n lineHeight: parseNumericValue(lineHeight ?? incomingStyle?.lineHeight),\n marginTop: parseNumericValue(\n incomingStyle?.marginTop as number | string | undefined\n ),\n marginBottom: parseNumericValue(\n incomingStyle?.marginBottom as number | string | undefined\n ),\n };\n\n const accessibilityRole = role ? roleMap[role] : undefined;\n\n return (\n <RNText\n style={baseStyle}\n numberOfLines={numberOfLines}\n testID={dataTestId || testID || id}\n accessibilityRole={accessibilityRole}\n >\n {children}\n </RNText>\n );\n};\n","import React from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box, Text } from \"@xsolla/xui-primitives\";\nimport { useResolvedTheme } from \"@xsolla/xui-core\";\nimport { Checkbox } from \"@xsolla/xui-checkbox\";\nimport { Link } from \"@xsolla/xui-link\";\nimport { InfoCr } from \"@xsolla/xui-icons-base\";\nimport type { PrivacySettingsProps } from \"./types\";\n\nconst PANE_RADIUS = 8;\nconst LIST_WIDTH = 280;\n\n/**\n * The expanded half of the GDPR banner: a category list on the left and the\n * selected category's explanation on the right (stacked on mobile, where the\n * explanation renders directly beneath the row it belongs to).\n *\n * Rendered by `GdprBanner` when the Privacy settings panel is open; exported\n * so consumers can embed the same panel inside their own settings screen.\n */\nexport const PrivacySettings: React.FC<PrivacySettingsProps> = ({\n categories,\n selectedCategoryIds,\n onToggleCategory,\n activeCategoryId,\n onActiveCategoryChange,\n viewport,\n labels,\n cookiePolicyUrl,\n privacyPolicyUrl,\n testID = \"gdpr-privacy-settings\",\n themeMode,\n themeProductContext,\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const isMobile = viewport === \"mobile\";\n const body = theme.typographyTokens.basic[\"body-sm\"];\n\n const activeDescription =\n activeCategoryId === null\n ? labels.generalInformationDescription\n : (categories.find((category) => category.id === activeCategoryId)\n ?.description ?? \"\");\n\n const descriptionPane = (\n <Box\n testID={`${testID}__description`}\n backgroundColor={theme.colors.control.faint.bg}\n borderRadius={PANE_RADIUS}\n padding={16}\n flex={isMobile ? undefined : 1}\n width={isMobile ? \"100%\" : undefined}\n aria-live=\"polite\"\n >\n <Text\n color={theme.colors.content.secondary}\n fontSize={body.fontSize}\n lineHeight={body.lineHeight}\n fontFamily={theme.fonts.body}\n >\n {activeDescription}\n </Text>\n </Box>\n );\n\n const focusStyle = {\n outlineColor: theme.colors.border.brand,\n outlineWidth: 2,\n outlineOffset: 2,\n outlineStyle: \"solid\",\n };\n\n const list = (\n <Box\n gap={12}\n width={isMobile ? \"100%\" : LIST_WIDTH}\n flexShrink={0}\n testID={`${testID}__categories`}\n >\n <Box gap={8}>\n <Box\n flexDirection=\"row\"\n alignItems=\"center\"\n gap={8}\n role=\"button\"\n tabIndex={0}\n cursor=\"pointer\"\n aria-pressed={activeCategoryId === null}\n onPress={() => onActiveCategoryChange(null)}\n onKeyDown={(event: React.KeyboardEvent) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n onActiveCategoryChange(null);\n }\n }}\n focusStyle={focusStyle}\n testID={`${testID}__general-information`}\n >\n <InfoCr\n variant=\"solid\"\n size={16}\n color={theme.colors.control.link.primary}\n />\n <Text\n color={theme.colors.content.primary}\n fontSize={body.fontSize}\n lineHeight={body.lineHeight}\n fontFamily={theme.fonts.body}\n >\n {labels.generalInformation}\n </Text>\n </Box>\n {isMobile && activeCategoryId === null && descriptionPane}\n </Box>\n\n {categories.map((category) => (\n <Box key={category.id} gap={8}>\n <Box onPress={() => onActiveCategoryChange(category.id)}>\n <Checkbox\n size=\"md\"\n checked={selectedCategoryIds.includes(category.id)}\n disabled={category.locked}\n onChange={() => onToggleCategory(category.id)}\n testID={`${testID}__category-${category.id}`}\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n >\n {category.label}\n </Checkbox>\n </Box>\n {isMobile && activeCategoryId === category.id && descriptionPane}\n </Box>\n ))}\n </Box>\n );\n\n return (\n <Box testID={testID} gap={16} width=\"100%\">\n <Box\n flexDirection={isMobile ? \"column\" : \"row\"}\n gap={isMobile ? 12 : 24}\n alignItems=\"flex-start\"\n width=\"100%\"\n >\n {list}\n {!isMobile && descriptionPane}\n </Box>\n\n <Box\n flexDirection=\"row\"\n alignItems=\"center\"\n gap={8}\n testID={`${testID}__policies`}\n >\n <Link\n size=\"sm\"\n href={cookiePolicyUrl}\n target=\"_blank\"\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n testID={`${testID}__cookie-policy`}\n >\n {labels.cookiePolicy}\n </Link>\n <Text\n color={theme.colors.content.tertiary}\n fontSize={body.fontSize}\n lineHeight={body.lineHeight}\n fontFamily={theme.fonts.body}\n aria-hidden={true}\n >\n {\"•\"}\n </Text>\n <Link\n size=\"sm\"\n href={privacyPolicyUrl}\n target=\"_blank\"\n themeMode={themeMode}\n themeProductContext={themeProductContext}\n testID={`${testID}__privacy-policy`}\n >\n {labels.privacyPolicy}\n </Link>\n </Box>\n </Box>\n );\n};\n\nPrivacySettings.displayName = \"PrivacySettings\";\n","import type { GdprBannerLabels, GdprCookieCategory } from \"./types\";\n\n/**\n * The five categories shown in the Figma Privacy settings panel.\n *\n * `essential` is `locked`: it renders checked + disabled and is always part of\n * the reported selection, including after `Reject All`.\n */\nexport const DEFAULT_CATEGORIES: GdprCookieCategory[] = [\n {\n id: \"essential\",\n label: \"Essential cookies\",\n locked: true,\n description:\n \"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.\",\n },\n {\n id: \"analytical\",\n label: \"Analytical and performance cookies\",\n description:\n \"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.\",\n },\n {\n id: \"functional\",\n label: \"Functional cookies\",\n description:\n \"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.\",\n },\n {\n id: \"targeting\",\n label: \"Targeting cookies\",\n description:\n \"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.\",\n },\n];\n\nexport const DEFAULT_LABELS: Required<GdprBannerLabels> = {\n title: \"How we use cookies\",\n description:\n \"We use cookies to personalize your experience. If you reject the use of cookies, your experience will not be tailored to your preferences.\",\n simpleDescription:\n \"We only use essential cookies that allow us to run this service. Read our cookie policy to learn more.\",\n privacySettingsTitle: \"Privacy settings\",\n generalInformation: \"General information\",\n generalInformationDescription:\n \"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.\",\n customize: \"Customize\",\n rejectAll: \"Reject All\",\n acceptAll: \"Accept All\",\n confirmSelection: \"Confirm Selection\",\n dismiss: \"Dismiss\",\n close: \"Close\",\n cookiePolicy: \"Cookie Policy\",\n privacyPolicy: \"Privacy Policy\",\n closeButtonAriaLabel: \"Close cookie banner\",\n};\n\n/** Collapsed desktop card width, from the Figma frame. */\nexport const CARD_WIDTH = 400;\n\n/** Default bottom / left / right inset of the collapsed banner, from Figma. */\nexport const EDGE_OFFSET = 20;\n\n/**\n * Size shared by every action button in the banner.\n *\n * Figma builds `Customize`, `Close`, `Reject All`, `Accept All`,\n * `Confirm Selection` and `Dismiss` from one `Button` component at a single\n * size, varying only the `Type` axis. Driving them all from one constant keeps\n * the borderless text actions from drifting out of step with the filled ones.\n */\nexport const ACTION_BUTTON_SIZE = \"md\" as const;\n\n/**\n * Vertical and horizontal gap between stacked content blocks and between\n * adjacent action buttons.\n *\n * 8px, measured on the Figma content column (`itemSpacing: 8` on the auto-\n * layout frame inside node 21855:20776).\n */\nexport const CONTENT_GAP = 8;\n"],"mappings":"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","names":["jsx","useResolvedTheme","jsx","jsx","jsxs","useResolvedTheme"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xsolla/xui-gdpr-banner",
3
- "version": "0.206.2",
3
+ "version": "0.207.0",
4
4
  "main": "./web/index.js",
5
5
  "module": "./web/index.mjs",
6
6
  "types": "./web/index.d.ts",
@@ -13,12 +13,12 @@
13
13
  "test:coverage": "vitest run --coverage"
14
14
  },
15
15
  "dependencies": {
16
- "@xsolla/xui-button": "0.206.2",
17
- "@xsolla/xui-checkbox": "0.206.2",
18
- "@xsolla/xui-core": "0.206.2",
19
- "@xsolla/xui-icons-base": "0.206.2",
20
- "@xsolla/xui-link": "0.206.2",
21
- "@xsolla/xui-primitives-core": "0.206.2"
16
+ "@xsolla/xui-button": "0.207.0",
17
+ "@xsolla/xui-checkbox": "0.207.0",
18
+ "@xsolla/xui-core": "0.207.0",
19
+ "@xsolla/xui-icons-base": "0.207.0",
20
+ "@xsolla/xui-link": "0.207.0",
21
+ "@xsolla/xui-primitives-core": "0.207.0"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "react": ">=16.8.0",
package/web/index.d.mts CHANGED
@@ -161,12 +161,15 @@ interface PrivacySettingsProps extends ThemeOverrideProps {
161
161
  * ## Layout
162
162
  *
163
163
  * - Desktop, collapsed - a card pinned to the bottom of the viewport with a
164
- * 20px inset, at either bottom corner (`anchor`).
164
+ * 20px inset, at either bottom corner (`anchor`). Its top corners use the
165
+ * modal radius token and its bottom corners the toast radius token.
165
166
  * - Desktop, expanded - a wider panel hosting the two-column Privacy settings.
166
- * - Mobile - always bottom-anchored and full width; actions stack.
167
+ * It sits flush with the bottom edge, so its bottom corners are square.
168
+ * - Mobile - always bottom-anchored, full width and flush against the bottom
169
+ * edge: `offset` is not applied and the bottom corners are square.
167
170
  *
168
171
  * `Customize` and `Close` are borderless brand-tone text actions, matching the
169
- * Figma `Flex Button` with `Background=False`.
172
+ * Figma `Button` with `Type=Tertiary` / `Tone=Brand`.
170
173
  *
171
174
  * ## Accessibility
172
175
  *
package/web/index.d.ts CHANGED
@@ -161,12 +161,15 @@ interface PrivacySettingsProps extends ThemeOverrideProps {
161
161
  * ## Layout
162
162
  *
163
163
  * - Desktop, collapsed - a card pinned to the bottom of the viewport with a
164
- * 20px inset, at either bottom corner (`anchor`).
164
+ * 20px inset, at either bottom corner (`anchor`). Its top corners use the
165
+ * modal radius token and its bottom corners the toast radius token.
165
166
  * - Desktop, expanded - a wider panel hosting the two-column Privacy settings.
166
- * - Mobile - always bottom-anchored and full width; actions stack.
167
+ * It sits flush with the bottom edge, so its bottom corners are square.
168
+ * - Mobile - always bottom-anchored, full width and flush against the bottom
169
+ * edge: `offset` is not applied and the bottom corners are square.
167
170
  *
168
171
  * `Customize` and `Close` are borderless brand-tone text actions, matching the
169
- * Figma `Flex Button` with `Background=False`.
172
+ * Figma `Button` with `Type=Tertiary` / `Tone=Brand`.
170
173
  *
171
174
  * ## Accessibility
172
175
  *
package/web/index.js CHANGED
@@ -602,6 +602,7 @@ var DEFAULT_LABELS = {
602
602
  };
603
603
  var CARD_WIDTH = 400;
604
604
  var EDGE_OFFSET = 20;
605
+ var ACTION_BUTTON_SIZE = "md";
605
606
  var CONTENT_GAP = 8;
606
607
 
607
608
  // src/GdprBanner.tsx
@@ -739,14 +740,13 @@ var GdprBanner = ({
739
740
  const handleConfirmSelection = (0, import_react3.useCallback)(() => {
740
741
  onConfirmSelection?.(selection);
741
742
  }, [onConfirmSelection, selection]);
742
- const sizing = theme.sizing.modal();
743
743
  const titleToken = theme.typographyTokens.basic["body-lg"];
744
744
  const bodyToken = theme.typographyTokens.basic["body-sm"];
745
745
  const solidButton = (key, label, onPress, fullWidth) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
746
746
  import_xui_button.Button,
747
747
  {
748
748
  variant: "secondary",
749
- size: "md",
749
+ size: ACTION_BUTTON_SIZE,
750
750
  fullWidth,
751
751
  onPress,
752
752
  testID: `${testID}__${key}`,
@@ -757,10 +757,11 @@ var GdprBanner = ({
757
757
  key
758
758
  );
759
759
  const textButton = (key, label, onPress) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
760
- import_xui_button.FlexButton,
760
+ import_xui_button.Button,
761
761
  {
762
- variant: "brand",
763
- size: "lg",
762
+ variant: "tertiary",
763
+ tone: "brand",
764
+ size: ACTION_BUTTON_SIZE,
764
765
  onPress,
765
766
  testID: `${testID}__${key}`,
766
767
  themeMode,
@@ -840,9 +841,18 @@ var GdprBanner = ({
840
841
  }
841
842
  );
842
843
  };
843
- const edge = isExpanded ? 0 : offset;
844
+ const isFlushWithEdges = isExpanded || isMobile;
845
+ const edge = isFlushWithEdges ? 0 : offset;
844
846
  const isFixed = position === "fixed";
845
847
  const spansFullWidth = isExpanded || isMobile;
848
+ const topCornerRadius = theme.shape.modal.borderRadius;
849
+ const bottomCornerRadius = isFlushWithEdges ? 0 : theme.shape.toast.borderRadius;
850
+ const cornerRadiusStyle = {
851
+ borderTopLeftRadius: topCornerRadius,
852
+ borderTopRightRadius: topCornerRadius,
853
+ borderBottomLeftRadius: bottomCornerRadius,
854
+ borderBottomRightRadius: bottomCornerRadius
855
+ };
846
856
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
847
857
  Box,
848
858
  {
@@ -861,8 +871,8 @@ var GdprBanner = ({
861
871
  zIndex: isFixed ? 1e3 : void 0,
862
872
  width: spansFullWidth ? "100%" : CARD_WIDTH,
863
873
  maxWidth: "100%",
864
- backgroundColor: theme.colors.background.primary,
865
- borderRadius: isExpanded ? 0 : sizing.borderRadius,
874
+ backgroundColor: theme.colors.layer.float,
875
+ style: cornerRadiusStyle,
866
876
  padding: isMobile ? 16 : 20,
867
877
  gap: CONTENT_GAP,
868
878
  children: [