@xsolla/xui-b2c-card-selector 0.217.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/README.md +4 -0
- package/native/index.d.mts +112 -0
- package/native/index.d.ts +112 -0
- package/native/index.js +773 -0
- package/native/index.js.map +1 -0
- package/native/index.mjs +752 -0
- package/native/index.mjs.map +1 -0
- package/package.json +56 -0
- package/web/index.d.mts +112 -0
- package/web/index.d.ts +112 -0
- package/web/index.js +4345 -0
- package/web/index.js.map +1 -0
- package/web/index.mjs +4330 -0
- package/web/index.mjs.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/CardSelector.tsx","../../../../foundation/primitives-native/src/Box.tsx","../../../../foundation/primitives-native/src/Text.tsx","../../src/CardSelectorGroupContext.tsx","../../src/CardSelectorGroup.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useRef, useState } from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box, Text } from \"@xsolla/xui-primitives\";\nimport { useResolvedTheme, radiusScale } from \"@xsolla/xui-core\";\nimport {\n useCardSelectorGroup,\n type CardSelectorGroupItem,\n} from \"./CardSelectorGroupContext\";\nimport type {\n CardSelectorControlPlacement,\n CardSelectorPlatform,\n CardSelectorProps,\n CardSelectorType,\n} from \"./types\";\n\ntype ResolvedTheme = ReturnType<typeof useResolvedTheme>[\"theme\"];\n\n/**\n * Figma `CardSelector` (3446:1761). The component set is fixed-width per\n * `Platform`; the exact frame widths could not be read back from the Figma\n * REST API (the node payload exceeds the tool's response budget), so the card\n * is fluid by default and the consumer sizes it via `width` or the grid cell.\n * `Platform` therefore drives the internal proportions only.\n */\ninterface PlatformMetrics {\n /** Maps onto the shared checkbox/radio size scale so the shape tokens line up. */\n controlSize: \"md\" | \"lg\";\n controlBox: number;\n controlIcon: number;\n titleFontSize: number;\n titleLineHeight: number;\n descriptionFontSize: number;\n descriptionLineHeight: number;\n padding: number;\n gap: number;\n radius: number;\n}\n\nconst platformMetrics: Record<CardSelectorPlatform, PlatformMetrics> = {\n mobile: {\n controlSize: \"md\",\n controlBox: 18,\n controlIcon: 12,\n titleFontSize: 14,\n titleLineHeight: 16,\n descriptionFontSize: 12,\n descriptionLineHeight: 16,\n padding: 8,\n gap: 8,\n radius: 8,\n },\n desktop: {\n controlSize: \"lg\",\n controlBox: 20,\n controlIcon: 14,\n titleFontSize: 16,\n titleLineHeight: 20,\n descriptionFontSize: 12,\n descriptionLineHeight: 16,\n padding: 10,\n gap: 8,\n radius: 8,\n },\n};\n\n/** Inline 12×12 glyph so the stroke scales with the control, mirroring Checkbox. */\nconst CheckGlyph: React.FC<{ size: number; color: string }> = ({\n size,\n color,\n}) => (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 12 12\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path\n d=\"M10.6699 4.2959L6.00293 8.96387C5.72637 9.24043 5.27761 9.24031 5.00098 8.96387L2 5.96289L3.2959 4.66699L5.50098 6.87207L9.37402 3L10.6699 4.2959Z\"\n fill={color}\n />\n </svg>\n);\n\n/**\n * The selection control is decorative: `aria-checked` lives on the card\n * itself, so exposing a second checkbox/radio here would double up the\n * semantics. It exists purely so selection never relies on colour alone.\n */\nconst SelectionControl: React.FC<{\n type: CardSelectorType;\n checked: boolean;\n disabled: boolean;\n metrics: PlatformMetrics;\n theme: ResolvedTheme;\n}> = ({ type, checked, disabled, metrics, theme }) => {\n const check = theme.colors.control.check;\n const faint = theme.colors.control.faint;\n\n const backgroundColor = disabled\n ? check.bgDisable\n : checked\n ? check.bg\n : faint.bg;\n const borderColor = disabled\n ? check.borderDisable\n : checked\n ? check.border\n : faint.border;\n const iconColor = disabled ? theme.colors.control.text.disable : check.icon;\n\n return (\n <Box\n width={metrics.controlBox}\n height={metrics.controlBox}\n flexShrink={0}\n alignItems=\"center\"\n justifyContent=\"center\"\n borderWidth={1}\n borderStyle=\"solid\"\n borderColor={borderColor}\n backgroundColor={backgroundColor}\n borderRadius={\n type === \"radio\"\n ? radiusScale[\"999\"]\n : theme.shape.checkbox[metrics.controlSize].borderRadius\n }\n aria-hidden=\"true\"\n testID=\"card-selector-control\"\n >\n {checked &&\n (type === \"radio\" ? (\n <Box\n width={metrics.controlIcon}\n height={metrics.controlIcon}\n borderRadius={radiusScale[\"999\"]}\n backgroundColor={iconColor}\n />\n ) : (\n <CheckGlyph size={metrics.controlIcon} color={iconColor} />\n ))}\n </Box>\n );\n};\n\nconst asPlainText = (value: React.ReactNode): string | undefined =>\n typeof value === \"string\" ? value : undefined;\n\nexport const CardSelector: React.FC<CardSelectorProps> = ({\n title,\n description,\n imageSrc,\n imageAlt = \"\",\n value,\n type: typeProp,\n control: controlProp,\n platform: platformProp,\n checked: checkedProp,\n defaultChecked = false,\n onChange,\n state = \"default\",\n disabled,\n width = \"100%\",\n \"aria-label\": ariaLabel,\n testID,\n themeMode,\n themeProductContext = \"b2c\",\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const group = useCardSelectorGroup();\n const nodeRef = useRef<HTMLDivElement | null>(null);\n\n const type: CardSelectorType = typeProp ?? group?.type ?? \"radio\";\n const control: CardSelectorControlPlacement =\n controlProp ?? group?.control ?? \"text\";\n const platform: CardSelectorPlatform =\n platformProp ?? group?.platform ?? \"mobile\";\n const metrics = platformMetrics[platform];\n\n const isDisabled =\n Boolean(disabled) || state === \"disabled\" || Boolean(group?.disabled);\n\n const [internalChecked, setInternalChecked] = useState(defaultChecked);\n const isGroupControlled =\n group !== null && type === \"radio\" && value !== undefined;\n const isControlled = checkedProp !== undefined;\n const checked = isControlled\n ? Boolean(checkedProp)\n : isGroupControlled\n ? group?.selectedValue === value\n : internalChecked;\n\n // Radio cards in a group are mutually exclusive, so re-pressing the selected\n // card must not clear the group's selection.\n const toggle = useCallback(() => {\n if (isDisabled) return;\n if (type === \"radio\" && checked && isGroupControlled) return;\n\n const next = type === \"radio\" ? true : !checked;\n\n if (!isControlled && !isGroupControlled) {\n setInternalChecked(next);\n }\n if (group && isGroupControlled && value !== undefined) {\n group.select(value);\n }\n onChange?.(next, value);\n }, [\n checked,\n group,\n isControlled,\n isDisabled,\n isGroupControlled,\n onChange,\n type,\n value,\n ]);\n\n const focus = useCallback(() => {\n const node = nodeRef.current;\n if (node && typeof node.focus === \"function\") node.focus();\n }, []);\n\n // Registered once per mount so the group's Arrow-key order matches DOM\n // order; the entry's fields are refreshed in place on every render.\n const itemRef = useRef<CardSelectorGroupItem>({\n value: value ?? \"\",\n disabled: Boolean(isDisabled),\n focus,\n });\n\n useEffect(() => {\n itemRef.current.value = value ?? \"\";\n itemRef.current.disabled = isDisabled;\n itemRef.current.focus = focus;\n });\n\n const registerItem = group?.register;\n useEffect(() => {\n if (!registerItem) return undefined;\n return registerItem(itemRef.current);\n }, [registerItem]);\n\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (isDisabled) return;\n\n const key = event.key;\n if (\n key === \" \" ||\n key === \"Spacebar\" ||\n key === \"Space\" ||\n key === \"Enter\"\n ) {\n event.preventDefault();\n toggle();\n return;\n }\n\n // Within a radio group, Arrow keys move focus and selection between cards.\n if (!group || type !== \"radio\") return;\n const delta =\n key === \"ArrowRight\" || key === \"ArrowDown\"\n ? 1\n : key === \"ArrowLeft\" || key === \"ArrowUp\"\n ? -1\n : 0;\n if (delta === 0) return;\n event.preventDefault();\n group.moveFocus(value, delta);\n };\n\n const showControl = control === \"text\" || checked;\n const isOnImage = control === \"onImage\";\n\n // Selected takes visual priority over hover.\n const surfaceBackground = checked\n ? theme.colors.overlay.brand\n : state === \"press\"\n ? theme.colors.overlay.brand\n : state === \"hover\"\n ? theme.colors.overlay.monoHover\n : theme.colors.overlay.mono;\n\n const titleColor =\n isOnImage && checked\n ? theme.colors.control.check.bg\n : theme.colors.content.primary;\n\n const derivedLabel = [asPlainText(title), asPlainText(description)]\n .filter(Boolean)\n .join(\", \");\n\n const media = (\n <Box\n position=\"relative\"\n width=\"100%\"\n overflow=\"hidden\"\n borderRadius={isOnImage ? metrics.radius : 0}\n style={{ aspectRatio: 1 }}\n testID=\"card-selector-media\"\n >\n {imageSrc && (\n <Box\n as=\"img\"\n src={imageSrc}\n alt={imageAlt}\n position=\"absolute\"\n top={0}\n left={0}\n width=\"100%\"\n height=\"100%\"\n style={{ objectFit: \"cover\", pointerEvents: \"none\" }}\n testID=\"card-selector-image\"\n />\n )}\n\n {isOnImage && showControl && (\n <Box\n position=\"absolute\"\n top={0}\n left={0}\n right={0}\n bottom={0}\n alignItems=\"center\"\n justifyContent=\"center\"\n zIndex={1}\n >\n <SelectionControl\n type={type}\n checked={checked}\n disabled={isDisabled}\n metrics={metrics}\n theme={theme}\n />\n </Box>\n )}\n </Box>\n );\n\n const label = (\n <Box flexDirection=\"column\" gap={2} flexShrink={1} overflow=\"hidden\">\n <Text\n color={titleColor}\n fontFamily={theme.fonts.body}\n fontSize={metrics.titleFontSize}\n lineHeight={metrics.titleLineHeight}\n fontWeight=\"500\"\n numberOfLines={1}\n testID=\"card-selector-title\"\n >\n {title}\n </Text>\n {description && (\n <Text\n color={theme.colors.content.tertiary}\n fontFamily={theme.fonts.body}\n fontSize={metrics.descriptionFontSize}\n lineHeight={metrics.descriptionLineHeight}\n fontWeight=\"400\"\n numberOfLines={1}\n testID=\"card-selector-description\"\n >\n {description}\n </Text>\n )}\n </Box>\n );\n\n // Roving tabindex: exactly one radio in a group is tabbable — the selected\n // card, or the first enabled card while the group holds no selection.\n const hasGroupSelection = group?.selectedValue !== undefined;\n const isRovingTarget =\n checked || (!hasGroupSelection && Boolean(group?.isFirstEnabled(value)));\n const isRovingRadio = Boolean(group) && type === \"radio\";\n const tabIndex = isDisabled || (isRovingRadio && !isRovingTarget) ? -1 : 0;\n\n return (\n <Box\n ref={nodeRef}\n width={width}\n flexDirection=\"column\"\n gap={isOnImage ? metrics.gap : 0}\n opacity={isDisabled ? 0.48 : 1}\n cursor={isDisabled ? \"not-allowed\" : \"pointer\"}\n role={type === \"radio\" ? \"radio\" : \"checkbox\"}\n aria-checked={checked}\n aria-disabled={isDisabled || undefined}\n aria-label={ariaLabel || derivedLabel || undefined}\n tabIndex={tabIndex}\n onPress={isDisabled ? undefined : toggle}\n onKeyDown={handleKeyDown}\n focusStyle={{\n outlineColor: theme.colors.border.brand,\n outlineWidth: 2,\n outlineOffset: 2,\n outlineStyle: \"solid\",\n }}\n testID={testID || \"card-selector\"}\n >\n <Box\n flexDirection=\"column\"\n overflow=\"hidden\"\n borderRadius={metrics.radius}\n borderWidth={2}\n borderStyle=\"solid\"\n borderColor={checked ? theme.colors.border.brand : \"transparent\"}\n backgroundColor={surfaceBackground}\n hoverStyle={\n !isDisabled && !checked && !isOnImage\n ? { backgroundColor: theme.colors.overlay.monoHover }\n : undefined\n }\n pressStyle={\n !isDisabled && !isOnImage\n ? { backgroundColor: theme.colors.overlay.brand }\n : undefined\n }\n testID=\"card-selector-surface\"\n >\n {media}\n\n {!isOnImage && (\n <Box\n flexDirection=\"row\"\n alignItems=\"center\"\n gap={metrics.gap}\n padding={metrics.padding}\n testID=\"card-selector-bar\"\n >\n <SelectionControl\n type={type}\n checked={checked}\n disabled={isDisabled}\n metrics={metrics}\n theme={theme}\n />\n {label}\n </Box>\n )}\n </Box>\n\n {isOnImage && label}\n </Box>\n );\n};\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 { createContext, useContext } from \"react\";\nimport type {\n CardSelectorControlPlacement,\n CardSelectorPlatform,\n CardSelectorType,\n} from \"./types\";\n\nexport interface CardSelectorGroupItem {\n value: string;\n disabled: boolean;\n focus: () => void;\n}\n\nexport interface CardSelectorGroupContextValue {\n type: CardSelectorType;\n platform?: CardSelectorPlatform;\n control?: CardSelectorControlPlacement;\n disabled: boolean;\n /** Selected value, `radio` only. */\n selectedValue?: string;\n /** Selects a value, `radio` only. */\n select: (value: string) => void;\n /** Registers a card so the group can drive roving focus. Returns an unregister fn. */\n register: (item: CardSelectorGroupItem) => () => void;\n /** Moves focus (and, for `radio`, selection) by `delta` positions, wrapping. */\n moveFocus: (fromValue: string | undefined, delta: number) => void;\n /** True when the group holds no selection — the first enabled card stays tabbable. */\n isFirstEnabled: (value: string | undefined) => boolean;\n}\n\nexport const CardSelectorGroupContext =\n createContext<CardSelectorGroupContextValue | null>(null);\n\nexport const useCardSelectorGroup = (): CardSelectorGroupContextValue | null =>\n useContext(CardSelectorGroupContext);\n","import React, { useCallback, useMemo, useState } from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box } from \"@xsolla/xui-primitives\";\nimport { useResolvedTheme } from \"@xsolla/xui-core\";\nimport {\n CardSelectorGroupContext,\n type CardSelectorGroupContextValue,\n type CardSelectorGroupItem,\n} from \"./CardSelectorGroupContext\";\nimport type { CardSelectorGroupProps } from \"./types\";\n\n/**\n * Wraps a set of `CardSelector` cards.\n *\n * For `type=\"radio\"` it renders a `radiogroup`, keeps the cards mutually\n * exclusive, and implements the roving-tabindex + Arrow-key behaviour the\n * Figma spec requires (3446:1761 — \"within a Radio group, Arrow keys move\n * focus and selection between cards\"). For `type=\"check\"` the cards toggle\n * independently and the wrapper is a plain `group`.\n */\nexport const CardSelectorGroup: React.FC<CardSelectorGroupProps> = ({\n children,\n type = \"radio\",\n value,\n defaultValue,\n onValueChange,\n platform,\n control,\n disabled = false,\n gap = 12,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n testID,\n themeMode,\n themeProductContext = \"b2c\",\n}) => {\n // Resolved so the group participates in the same theme scope as its cards.\n useResolvedTheme({ themeMode, themeProductContext });\n\n // Registration lives in state, not a ref, so the roving tabindex is correct\n // on the render right after the cards mount.\n const [items, setItems] = useState<CardSelectorGroupItem[]>([]);\n const [internalValue, setInternalValue] = useState(defaultValue);\n const isControlled = value !== undefined;\n const selectedValue = isControlled ? value : internalValue;\n\n const register = useCallback((item: CardSelectorGroupItem) => {\n setItems((prev) => [...prev, item]);\n return () => setItems((prev) => prev.filter((entry) => entry !== item));\n }, []);\n\n const select = useCallback(\n (next: string) => {\n if (!isControlled) setInternalValue(next);\n onValueChange?.(next);\n },\n [isControlled, onValueChange]\n );\n\n const moveFocus = useCallback(\n (fromValue: string | undefined, delta: number) => {\n const enabled = items.filter((item) => !item.disabled);\n if (enabled.length === 0) return;\n\n const currentIndex = enabled.findIndex(\n (item) => item.value === fromValue\n );\n const nextIndex =\n currentIndex === -1\n ? 0\n : (currentIndex + delta + enabled.length) % enabled.length;\n const next = enabled[nextIndex];\n if (!next) return;\n\n next.focus();\n if (type === \"radio\" && next.value) select(next.value);\n },\n [items, select, type]\n );\n\n const isFirstEnabled = useCallback(\n (candidate: string | undefined) => {\n const first = items.find((item) => !item.disabled);\n return Boolean(first) && first?.value === candidate;\n },\n [items]\n );\n\n const contextValue = useMemo<CardSelectorGroupContextValue>(\n () => ({\n type,\n platform,\n control,\n disabled,\n selectedValue,\n select,\n register,\n moveFocus,\n isFirstEnabled,\n }),\n [\n control,\n disabled,\n isFirstEnabled,\n moveFocus,\n platform,\n register,\n select,\n selectedValue,\n type,\n ]\n );\n\n return (\n <CardSelectorGroupContext.Provider value={contextValue}>\n <Box\n role={type === \"radio\" ? \"radiogroup\" : \"group\"}\n aria-label={ariaLabelledBy ? undefined : ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-disabled={disabled || undefined}\n flexDirection=\"row\"\n flexWrap=\"wrap\"\n alignItems=\"flex-start\"\n gap={gap}\n testID={testID || \"card-selector-group\"}\n >\n {children}\n </Box>\n </CardSelectorGroupContext.Provider>\n );\n};\n"],"mappings":"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","names":["jsx","jsx","useCallback","useState","useResolvedTheme","jsx","useResolvedTheme","useState","useCallback"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xsolla/xui-b2c-card-selector",
|
|
3
|
+
"version": "0.217.0",
|
|
4
|
+
"main": "./web/index.js",
|
|
5
|
+
"module": "./web/index.mjs",
|
|
6
|
+
"types": "./web/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "yarn build:web && yarn build:native",
|
|
9
|
+
"build:web": "PLATFORM=web tsup",
|
|
10
|
+
"build:native": "PLATFORM=native tsup",
|
|
11
|
+
"test": "vitest",
|
|
12
|
+
"test:run": "vitest run",
|
|
13
|
+
"test:coverage": "vitest run --coverage"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@xsolla/xui-core": "0.217.0",
|
|
17
|
+
"@xsolla/xui-primitives-core": "0.217.0"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"react": ">=16.8.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
24
|
+
"@testing-library/react": "^14.1.2",
|
|
25
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
26
|
+
"jsdom": "^24.0.0",
|
|
27
|
+
"react": "^18.0.0",
|
|
28
|
+
"react-dom": "^18.0.0",
|
|
29
|
+
"tsup": "^8.0.0",
|
|
30
|
+
"vitest": "^4.0.18"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"sideEffects": false,
|
|
34
|
+
"react-native": "./native/index.js",
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"react-native": {
|
|
38
|
+
"types": "./native/index.d.ts",
|
|
39
|
+
"import": "./native/index.mjs",
|
|
40
|
+
"require": "./native/index.js"
|
|
41
|
+
},
|
|
42
|
+
"import": {
|
|
43
|
+
"types": "./web/index.d.ts",
|
|
44
|
+
"default": "./web/index.mjs"
|
|
45
|
+
},
|
|
46
|
+
"require": {
|
|
47
|
+
"types": "./web/index.d.ts",
|
|
48
|
+
"default": "./web/index.js"
|
|
49
|
+
},
|
|
50
|
+
"default": {
|
|
51
|
+
"types": "./web/index.d.ts",
|
|
52
|
+
"default": "./web/index.js"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
package/web/index.d.mts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
import { ThemeOverrideProps } from '@xsolla/xui-core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Figma `CardSelector` property `Platform`. Adjusts the card's internal
|
|
6
|
+
* proportions — control size, title scale and padding.
|
|
7
|
+
*/
|
|
8
|
+
type CardSelectorPlatform = "mobile" | "desktop";
|
|
9
|
+
/**
|
|
10
|
+
* Figma `CardSelector` property `Type`. `radio` = mutually exclusive choice
|
|
11
|
+
* within a group, `check` = independent toggle.
|
|
12
|
+
*/
|
|
13
|
+
type CardSelectorType = "radio" | "check";
|
|
14
|
+
/**
|
|
15
|
+
* Figma `CardSelector` property `Control`.
|
|
16
|
+
*
|
|
17
|
+
* - `text` — the selection control and the label sit in a solid text bar
|
|
18
|
+
* below the image. The control is always visible.
|
|
19
|
+
* - `onImage` — the control overlays the image and only renders when the card
|
|
20
|
+
* is selected; the label is a caption beneath the card.
|
|
21
|
+
*/
|
|
22
|
+
type CardSelectorControlPlacement = "text" | "onImage";
|
|
23
|
+
/** Figma `CardSelector` property `State`. `selected` is derived from `checked`. */
|
|
24
|
+
type CardSelectorState = "default" | "hover" | "press" | "disabled";
|
|
25
|
+
interface CardSelectorProps extends ThemeOverrideProps {
|
|
26
|
+
/**
|
|
27
|
+
* Option label. Required, single-line, truncates with an ellipsis — aim for
|
|
28
|
+
* under 24 characters.
|
|
29
|
+
*/
|
|
30
|
+
title: ReactNode;
|
|
31
|
+
/**
|
|
32
|
+
* Optional single-line clarification. Use it only when the title alone is
|
|
33
|
+
* ambiguous; never repeat the title.
|
|
34
|
+
*/
|
|
35
|
+
description?: ReactNode;
|
|
36
|
+
/** Media thumbnail (1:1). The image should preview the option itself. */
|
|
37
|
+
imageSrc?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Alt text. Defaults to `""` because the title normally describes the
|
|
40
|
+
* option; supply a meaningful value when the image carries meaning the
|
|
41
|
+
* title does not convey.
|
|
42
|
+
*/
|
|
43
|
+
imageAlt?: string;
|
|
44
|
+
/** Identifies the card inside a `CardSelectorGroup`. Required for `radio`. */
|
|
45
|
+
value?: string;
|
|
46
|
+
/** Inherited from `CardSelectorGroup` when unset. Defaults to `"radio"`. */
|
|
47
|
+
type?: CardSelectorType;
|
|
48
|
+
/** Inherited from `CardSelectorGroup` when unset. Defaults to `"text"`. */
|
|
49
|
+
control?: CardSelectorControlPlacement;
|
|
50
|
+
/** Inherited from `CardSelectorGroup` when unset. Defaults to `"mobile"`. */
|
|
51
|
+
platform?: CardSelectorPlatform;
|
|
52
|
+
/** Controlled selection state. Inside a radio group, derived from the group. */
|
|
53
|
+
checked?: boolean;
|
|
54
|
+
/** Uncontrolled initial selection state. */
|
|
55
|
+
defaultChecked?: boolean;
|
|
56
|
+
/** Fired when the card toggles. Not fired while disabled. */
|
|
57
|
+
onChange?: (checked: boolean, value?: string) => void;
|
|
58
|
+
/**
|
|
59
|
+
* Interactive state. Web applies hover and press automatically — pass this
|
|
60
|
+
* explicitly only for design review. `selected` is not a member: it is
|
|
61
|
+
* derived from `checked` and takes visual priority over hover.
|
|
62
|
+
*/
|
|
63
|
+
state?: CardSelectorState;
|
|
64
|
+
/** Same effect as `state="disabled"`: 48% opacity, no interactivity, untabbable. */
|
|
65
|
+
disabled?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Card width. Defaults to `"100%"` so the card fills the grid cell the
|
|
68
|
+
* consumer lays out.
|
|
69
|
+
*/
|
|
70
|
+
width?: number | string;
|
|
71
|
+
/** Overrides the accessible name derived from the title and description. */
|
|
72
|
+
"aria-label"?: string;
|
|
73
|
+
testID?: string;
|
|
74
|
+
}
|
|
75
|
+
interface CardSelectorGroupProps extends ThemeOverrideProps {
|
|
76
|
+
children: ReactNode;
|
|
77
|
+
/** `radio` renders a `radiogroup`; `check` renders a plain `group`. */
|
|
78
|
+
type?: CardSelectorType;
|
|
79
|
+
/** Selected value for `type="radio"` (controlled). */
|
|
80
|
+
value?: string;
|
|
81
|
+
/** Initial selected value for `type="radio"` (uncontrolled). */
|
|
82
|
+
defaultValue?: string;
|
|
83
|
+
/** Fired with the newly selected value. `type="radio"` only. */
|
|
84
|
+
onValueChange?: (value: string) => void;
|
|
85
|
+
/** Applied to every card in the group unless the card overrides it. */
|
|
86
|
+
platform?: CardSelectorPlatform;
|
|
87
|
+
/** Applied to every card in the group unless the card overrides it. */
|
|
88
|
+
control?: CardSelectorControlPlacement;
|
|
89
|
+
/** Disables every card in the group. */
|
|
90
|
+
disabled?: boolean;
|
|
91
|
+
/** Grid gap between cards. */
|
|
92
|
+
gap?: number;
|
|
93
|
+
/** Accessible name for the group. Required unless `aria-labelledby` is set. */
|
|
94
|
+
"aria-label"?: string;
|
|
95
|
+
"aria-labelledby"?: string;
|
|
96
|
+
testID?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
declare const CardSelector: React.FC<CardSelectorProps>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Wraps a set of `CardSelector` cards.
|
|
103
|
+
*
|
|
104
|
+
* For `type="radio"` it renders a `radiogroup`, keeps the cards mutually
|
|
105
|
+
* exclusive, and implements the roving-tabindex + Arrow-key behaviour the
|
|
106
|
+
* Figma spec requires (3446:1761 — "within a Radio group, Arrow keys move
|
|
107
|
+
* focus and selection between cards"). For `type="check"` the cards toggle
|
|
108
|
+
* independently and the wrapper is a plain `group`.
|
|
109
|
+
*/
|
|
110
|
+
declare const CardSelectorGroup: React.FC<CardSelectorGroupProps>;
|
|
111
|
+
|
|
112
|
+
export { CardSelector, type CardSelectorControlPlacement, CardSelectorGroup, type CardSelectorGroupProps, type CardSelectorPlatform, type CardSelectorProps, type CardSelectorState, type CardSelectorType };
|
package/web/index.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
import { ThemeOverrideProps } from '@xsolla/xui-core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Figma `CardSelector` property `Platform`. Adjusts the card's internal
|
|
6
|
+
* proportions — control size, title scale and padding.
|
|
7
|
+
*/
|
|
8
|
+
type CardSelectorPlatform = "mobile" | "desktop";
|
|
9
|
+
/**
|
|
10
|
+
* Figma `CardSelector` property `Type`. `radio` = mutually exclusive choice
|
|
11
|
+
* within a group, `check` = independent toggle.
|
|
12
|
+
*/
|
|
13
|
+
type CardSelectorType = "radio" | "check";
|
|
14
|
+
/**
|
|
15
|
+
* Figma `CardSelector` property `Control`.
|
|
16
|
+
*
|
|
17
|
+
* - `text` — the selection control and the label sit in a solid text bar
|
|
18
|
+
* below the image. The control is always visible.
|
|
19
|
+
* - `onImage` — the control overlays the image and only renders when the card
|
|
20
|
+
* is selected; the label is a caption beneath the card.
|
|
21
|
+
*/
|
|
22
|
+
type CardSelectorControlPlacement = "text" | "onImage";
|
|
23
|
+
/** Figma `CardSelector` property `State`. `selected` is derived from `checked`. */
|
|
24
|
+
type CardSelectorState = "default" | "hover" | "press" | "disabled";
|
|
25
|
+
interface CardSelectorProps extends ThemeOverrideProps {
|
|
26
|
+
/**
|
|
27
|
+
* Option label. Required, single-line, truncates with an ellipsis — aim for
|
|
28
|
+
* under 24 characters.
|
|
29
|
+
*/
|
|
30
|
+
title: ReactNode;
|
|
31
|
+
/**
|
|
32
|
+
* Optional single-line clarification. Use it only when the title alone is
|
|
33
|
+
* ambiguous; never repeat the title.
|
|
34
|
+
*/
|
|
35
|
+
description?: ReactNode;
|
|
36
|
+
/** Media thumbnail (1:1). The image should preview the option itself. */
|
|
37
|
+
imageSrc?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Alt text. Defaults to `""` because the title normally describes the
|
|
40
|
+
* option; supply a meaningful value when the image carries meaning the
|
|
41
|
+
* title does not convey.
|
|
42
|
+
*/
|
|
43
|
+
imageAlt?: string;
|
|
44
|
+
/** Identifies the card inside a `CardSelectorGroup`. Required for `radio`. */
|
|
45
|
+
value?: string;
|
|
46
|
+
/** Inherited from `CardSelectorGroup` when unset. Defaults to `"radio"`. */
|
|
47
|
+
type?: CardSelectorType;
|
|
48
|
+
/** Inherited from `CardSelectorGroup` when unset. Defaults to `"text"`. */
|
|
49
|
+
control?: CardSelectorControlPlacement;
|
|
50
|
+
/** Inherited from `CardSelectorGroup` when unset. Defaults to `"mobile"`. */
|
|
51
|
+
platform?: CardSelectorPlatform;
|
|
52
|
+
/** Controlled selection state. Inside a radio group, derived from the group. */
|
|
53
|
+
checked?: boolean;
|
|
54
|
+
/** Uncontrolled initial selection state. */
|
|
55
|
+
defaultChecked?: boolean;
|
|
56
|
+
/** Fired when the card toggles. Not fired while disabled. */
|
|
57
|
+
onChange?: (checked: boolean, value?: string) => void;
|
|
58
|
+
/**
|
|
59
|
+
* Interactive state. Web applies hover and press automatically — pass this
|
|
60
|
+
* explicitly only for design review. `selected` is not a member: it is
|
|
61
|
+
* derived from `checked` and takes visual priority over hover.
|
|
62
|
+
*/
|
|
63
|
+
state?: CardSelectorState;
|
|
64
|
+
/** Same effect as `state="disabled"`: 48% opacity, no interactivity, untabbable. */
|
|
65
|
+
disabled?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Card width. Defaults to `"100%"` so the card fills the grid cell the
|
|
68
|
+
* consumer lays out.
|
|
69
|
+
*/
|
|
70
|
+
width?: number | string;
|
|
71
|
+
/** Overrides the accessible name derived from the title and description. */
|
|
72
|
+
"aria-label"?: string;
|
|
73
|
+
testID?: string;
|
|
74
|
+
}
|
|
75
|
+
interface CardSelectorGroupProps extends ThemeOverrideProps {
|
|
76
|
+
children: ReactNode;
|
|
77
|
+
/** `radio` renders a `radiogroup`; `check` renders a plain `group`. */
|
|
78
|
+
type?: CardSelectorType;
|
|
79
|
+
/** Selected value for `type="radio"` (controlled). */
|
|
80
|
+
value?: string;
|
|
81
|
+
/** Initial selected value for `type="radio"` (uncontrolled). */
|
|
82
|
+
defaultValue?: string;
|
|
83
|
+
/** Fired with the newly selected value. `type="radio"` only. */
|
|
84
|
+
onValueChange?: (value: string) => void;
|
|
85
|
+
/** Applied to every card in the group unless the card overrides it. */
|
|
86
|
+
platform?: CardSelectorPlatform;
|
|
87
|
+
/** Applied to every card in the group unless the card overrides it. */
|
|
88
|
+
control?: CardSelectorControlPlacement;
|
|
89
|
+
/** Disables every card in the group. */
|
|
90
|
+
disabled?: boolean;
|
|
91
|
+
/** Grid gap between cards. */
|
|
92
|
+
gap?: number;
|
|
93
|
+
/** Accessible name for the group. Required unless `aria-labelledby` is set. */
|
|
94
|
+
"aria-label"?: string;
|
|
95
|
+
"aria-labelledby"?: string;
|
|
96
|
+
testID?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
declare const CardSelector: React.FC<CardSelectorProps>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Wraps a set of `CardSelector` cards.
|
|
103
|
+
*
|
|
104
|
+
* For `type="radio"` it renders a `radiogroup`, keeps the cards mutually
|
|
105
|
+
* exclusive, and implements the roving-tabindex + Arrow-key behaviour the
|
|
106
|
+
* Figma spec requires (3446:1761 — "within a Radio group, Arrow keys move
|
|
107
|
+
* focus and selection between cards"). For `type="check"` the cards toggle
|
|
108
|
+
* independently and the wrapper is a plain `group`.
|
|
109
|
+
*/
|
|
110
|
+
declare const CardSelectorGroup: React.FC<CardSelectorGroupProps>;
|
|
111
|
+
|
|
112
|
+
export { CardSelector, type CardSelectorControlPlacement, CardSelectorGroup, type CardSelectorGroupProps, type CardSelectorPlatform, type CardSelectorProps, type CardSelectorState, type CardSelectorType };
|