@xsolla/xui-radio-group 0.218.1 → 0.219.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 CHANGED
@@ -122,6 +122,10 @@ export default function DistributionModelPicker() {
122
122
  `columns` lays the cards out side by side. Children are distributed
123
123
  round-robin, so the reading order stays left-to-right then top-to-bottom.
124
124
 
125
+ Every card in a row is stretched to the height of the tallest one, so
126
+ descriptions that wrap to a different number of lines still line up. Use
127
+ `columns` — not `flexDirection="row"` — for a multi-column card layout.
128
+
125
129
  ```tsx
126
130
  import * as React from "react";
127
131
  import { RadioGroup, RadioCard } from "@xsolla/xui-radio-group";
@@ -146,6 +150,40 @@ export default function SalesChannelPicker() {
146
150
  }
147
151
  ```
148
152
 
153
+ ### Top-Aligned Card Content
154
+
155
+ In a stretched row, a card can end up taller than its own content. By default
156
+ its title and description are centred against the radio indicator. Pass
157
+ `contentAlign="top"` to pin the text to the top of the card instead — the
158
+ indicator stays vertically centred either way, which is the split a single
159
+ `align-items` on the card cannot express.
160
+
161
+ ```tsx
162
+ import * as React from "react";
163
+ import { RadioGroup, RadioCard } from "@xsolla/xui-radio-group";
164
+
165
+ export default function TopAlignedCards() {
166
+ const [channel, setChannel] = React.useState("launcher");
167
+
168
+ return (
169
+ <RadioGroup view="card" columns={2} value={channel} onChange={setChannel}>
170
+ <RadioCard
171
+ contentAlign="top"
172
+ value="launcher"
173
+ title="Sell in launcher"
174
+ description="After clicking the Buy button a payment widget opens, selling the game key under the SKU specified above."
175
+ />
176
+ <RadioCard
177
+ contentAlign="top"
178
+ value="browser"
179
+ title="Sell in browser"
180
+ description="After clicking the Buy button the link opens in a browser."
181
+ />
182
+ </RadioGroup>
183
+ );
184
+ }
185
+ ```
186
+
149
187
  ### Disabled Radio Group
150
188
 
151
189
  ```tsx
@@ -300,7 +338,7 @@ Container component that provides selection context to Radio children.
300
338
  | flexDirection | `"row" \| "column"` | `"column"` | Layout direction of the group. |
301
339
  | gap | `number` | `12` / `4` | Gap between items in pixels. Defaults to `4` when `view="card"`. |
302
340
  | view | `"default" \| "card"` | `"default"`| Visual treatment of the options. `"card"` is for `RadioCard` children. |
303
- | columns | `number` | `1` | Number of equal-width columns. Values above `1` take precedence over `flexDirection`. |
341
+ | columns | `number` | `1` | Number of equal-width columns. Values above `1` take precedence over `flexDirection`, and stretch every option in a row to the height of the tallest one. |
304
342
 
305
343
  ### RadioCard
306
344
 
@@ -310,18 +348,19 @@ indicator, with the whole card as the click target. Must be used inside a
310
348
 
311
349
  **RadioCard Props:**
312
350
 
313
- | Prop | Type | Default | Description |
314
- | :------------ | :----------------------------- | :------ | :------------------------------------------------------------------------------------------------ |
315
- | `testID` | `string` | — | Test ID for testing frameworks. |
316
- | value | `string` | - | **Required.** Value this card selects in the parent group. |
317
- | title | `string` | - | **Required.** Primary line of the card. |
318
- | description | `string` | - | Supporting line rendered under the title. |
319
- | size | `"sm" \| "md" \| "lg" \| "xl"` | group | Size of the card and its indicator. Falls back to the group's `size`. |
320
- | checked | `boolean` | group | Selected state. Normally derived from the parent group's `value`. |
321
- | disabled | `boolean` | `false` | Disables this card only. The group's `disabled` disables every card. |
322
- | padding | `number \| string` | by size | Overrides the size-derived internal padding (`8`/`12`/`16`/`16` vertical, `12`/`16`/`16`/`20` horizontal). |
323
- | onPress | `() => void` | - | Called after the card is selected, in addition to the group's `onChange`. |
324
- | themeMode | `ThemeMode` | context | Per-component theme mode override. |
351
+ | Prop | Type | Default | Description |
352
+ | :------------ | :----------------------------- | :--------- | :------------------------------------------------------------------------------------------------ |
353
+ | `testID` | `string` | — | Test ID for testing frameworks. |
354
+ | value | `string` | - | **Required.** Value this card selects in the parent group. |
355
+ | title | `string` | - | **Required.** Primary line of the card. |
356
+ | description | `string` | - | Supporting line rendered under the title. |
357
+ | size | `"sm" \| "md" \| "lg" \| "xl"` | group | Size of the card and its indicator. Falls back to the group's `size`. |
358
+ | checked | `boolean` | group | Selected state. Normally derived from the parent group's `value`. |
359
+ | disabled | `boolean` | `false` | Disables this card only. The group's `disabled` disables every card. |
360
+ | padding | `number \| string` | by size | Overrides the size-derived internal padding (`8`/`12`/`16`/`16` vertical, `12`/`16`/`16`/`20` horizontal). |
361
+ | contentAlign | `"center" \| "top"` | `"center"` | Where the title/description block sits when the card is taller than its own content. The radio indicator stays vertically centred either way. |
362
+ | onPress | `() => void` | - | Called after the card is selected, in addition to the group's `onChange`. |
363
+ | themeMode | `ThemeMode` | context | Per-component theme mode override. |
325
364
 
326
365
  ### RadioGroup Context
327
366
 
@@ -38,6 +38,11 @@ interface RadioGroupProps {
38
38
  * short option list. Children are distributed round-robin so the visual
39
39
  * reading order stays left-to-right, then top-to-bottom.
40
40
  *
41
+ * Every option in the row is stretched to the height of the tallest one, so
42
+ * cards whose descriptions wrap to a different number of lines still line
43
+ * up. Pair it with `RadioCard`'s `contentAlign="top"` when the text should
44
+ * stay pinned to the top of the stretched card.
45
+ *
41
46
  * Takes precedence over `flexDirection`.
42
47
  */
43
48
  columns?: number;
@@ -47,6 +52,22 @@ interface RadioGroupProps {
47
52
  declare const RadioGroup: React.FC<RadioGroupProps>;
48
53
 
49
54
  type RadioCardSize = "sm" | "md" | "lg" | "xl";
55
+ /**
56
+ * Cross-axis placement of the card's text block.
57
+ *
58
+ * The radio indicator is *not* covered by this — it stays vertically centred
59
+ * in the card either way, which is the split a single `alignItems` on the
60
+ * card's row cannot express.
61
+ *
62
+ * - `center` — title and description centred against the indicator. The
63
+ * historical behaviour, and indistinguishable from `top` on a card that is
64
+ * only as tall as its own content.
65
+ * - `top` — title and description pinned to the top edge of the card's
66
+ * content box. Use this when the card is stretched taller than its own
67
+ * content, e.g. inside a `RadioGroup` with `columns > 1`, where every card
68
+ * in a row is equalised to the tallest one.
69
+ */
70
+ type RadioCardContentAlign = "center" | "top";
50
71
  interface RadioCardProps extends ThemeOverrideProps {
51
72
  /** Value this card selects in the parent `RadioGroup`. */
52
73
  value: string;
@@ -65,6 +86,13 @@ interface RadioCardProps extends ThemeOverrideProps {
65
86
  * `padding` accepts.
66
87
  */
67
88
  padding?: number | string;
89
+ /**
90
+ * Where the title/description block sits when the card is taller than its
91
+ * own content. The radio indicator stays vertically centred regardless.
92
+ *
93
+ * @default "center"
94
+ */
95
+ contentAlign?: RadioCardContentAlign;
68
96
  /** Called after the card is selected, in addition to the group's `onChange`. */
69
97
  onPress?: () => void;
70
98
  /** Accessible label, when `title` is not descriptive enough on its own. */
@@ -88,4 +116,4 @@ interface RadioCardProps extends ThemeOverrideProps {
88
116
  */
89
117
  declare const RadioCard: React.FC<RadioCardProps>;
90
118
 
91
- export { RadioCard, type RadioCardProps, RadioGroup, type RadioGroupContextProps, type RadioGroupProps, type RadioGroupView, useRadioGroup };
119
+ export { RadioCard, type RadioCardContentAlign, type RadioCardProps, RadioGroup, type RadioGroupContextProps, type RadioGroupProps, type RadioGroupView, useRadioGroup };
package/native/index.d.ts CHANGED
@@ -38,6 +38,11 @@ interface RadioGroupProps {
38
38
  * short option list. Children are distributed round-robin so the visual
39
39
  * reading order stays left-to-right, then top-to-bottom.
40
40
  *
41
+ * Every option in the row is stretched to the height of the tallest one, so
42
+ * cards whose descriptions wrap to a different number of lines still line
43
+ * up. Pair it with `RadioCard`'s `contentAlign="top"` when the text should
44
+ * stay pinned to the top of the stretched card.
45
+ *
41
46
  * Takes precedence over `flexDirection`.
42
47
  */
43
48
  columns?: number;
@@ -47,6 +52,22 @@ interface RadioGroupProps {
47
52
  declare const RadioGroup: React.FC<RadioGroupProps>;
48
53
 
49
54
  type RadioCardSize = "sm" | "md" | "lg" | "xl";
55
+ /**
56
+ * Cross-axis placement of the card's text block.
57
+ *
58
+ * The radio indicator is *not* covered by this — it stays vertically centred
59
+ * in the card either way, which is the split a single `alignItems` on the
60
+ * card's row cannot express.
61
+ *
62
+ * - `center` — title and description centred against the indicator. The
63
+ * historical behaviour, and indistinguishable from `top` on a card that is
64
+ * only as tall as its own content.
65
+ * - `top` — title and description pinned to the top edge of the card's
66
+ * content box. Use this when the card is stretched taller than its own
67
+ * content, e.g. inside a `RadioGroup` with `columns > 1`, where every card
68
+ * in a row is equalised to the tallest one.
69
+ */
70
+ type RadioCardContentAlign = "center" | "top";
50
71
  interface RadioCardProps extends ThemeOverrideProps {
51
72
  /** Value this card selects in the parent `RadioGroup`. */
52
73
  value: string;
@@ -65,6 +86,13 @@ interface RadioCardProps extends ThemeOverrideProps {
65
86
  * `padding` accepts.
66
87
  */
67
88
  padding?: number | string;
89
+ /**
90
+ * Where the title/description block sits when the card is taller than its
91
+ * own content. The radio indicator stays vertically centred regardless.
92
+ *
93
+ * @default "center"
94
+ */
95
+ contentAlign?: RadioCardContentAlign;
68
96
  /** Called after the card is selected, in addition to the group's `onChange`. */
69
97
  onPress?: () => void;
70
98
  /** Accessible label, when `title` is not descriptive enough on its own. */
@@ -88,4 +116,4 @@ interface RadioCardProps extends ThemeOverrideProps {
88
116
  */
89
117
  declare const RadioCard: React.FC<RadioCardProps>;
90
118
 
91
- export { RadioCard, type RadioCardProps, RadioGroup, type RadioGroupContextProps, type RadioGroupProps, type RadioGroupView, useRadioGroup };
119
+ export { RadioCard, type RadioCardContentAlign, type RadioCardProps, RadioGroup, type RadioGroupContextProps, type RadioGroupProps, type RadioGroupView, useRadioGroup };
package/native/index.js CHANGED
@@ -74,6 +74,7 @@ var toGapValue = (gap) => {
74
74
  const asNumber = Number(gap);
75
75
  return Number.isFinite(asNumber) ? asNumber : void 0;
76
76
  };
77
+ var toNativeDisplay = (display) => display === "none" ? "none" : void 0;
77
78
  var Box = ({
78
79
  children,
79
80
  onPress,
@@ -105,6 +106,7 @@ var Box = ({
105
106
  marginBottom,
106
107
  marginLeft,
107
108
  marginRight,
109
+ display,
108
110
  flexDirection,
109
111
  flexWrap,
110
112
  alignItems,
@@ -168,6 +170,7 @@ var Box = ({
168
170
  marginBottom,
169
171
  marginLeft,
170
172
  marginRight,
173
+ display: toNativeDisplay(display),
171
174
  flexDirection,
172
175
  flexWrap,
173
176
  alignItems,
@@ -342,7 +345,18 @@ var Text = ({
342
345
  };
343
346
 
344
347
  // src/RadioGroup.tsx
345
- var import_jsx_runtime3 = require("react/jsx-runtime");
348
+ var import_jsx_runtime3 = (
349
+ // One slot per option. The column's main axis is vertical, so
350
+ // an option does not grow into the column's spare height by
351
+ // itself — the slot claims that space (`flexGrow`) and then
352
+ // hands it to the option, which stretches across the slot's
353
+ // cross axis because the slot is a row.
354
+ //
355
+ // `flexBasis: auto` rather than the `flex: 1` shorthand's `0`:
356
+ // a slot never starts smaller than its own content, so a tall
357
+ // option is never squashed to an equal share.
358
+ require("react/jsx-runtime")
359
+ );
346
360
  var RadioGroupContext = (0, import_react.createContext)({});
347
361
  var useRadioGroup = () => (0, import_react.useContext)(RadioGroupContext);
348
362
  var DEFAULT_GAP = 12;
@@ -383,7 +397,7 @@ var RadioGroup = ({
383
397
  role: "radiogroup",
384
398
  "aria-disabled": disabled || void 0,
385
399
  flexDirection: "row",
386
- alignItems: "flex-start",
400
+ alignItems: "stretch",
387
401
  gap: resolvedGap,
388
402
  children: columnBuckets.map((bucket, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
389
403
  Box,
@@ -392,7 +406,15 @@ var RadioGroup = ({
392
406
  gap: resolvedGap,
393
407
  flex: 1,
394
408
  style: { minWidth: 0 },
395
- children: bucket
409
+ children: bucket.map((child, childIndex) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
410
+ Box,
411
+ {
412
+ flexDirection: "row",
413
+ style: { flexGrow: 1, flexBasis: "auto", minWidth: 0 },
414
+ children: child
415
+ },
416
+ childIndex
417
+ ))
396
418
  },
397
419
  index
398
420
  ))
@@ -430,6 +452,7 @@ var RadioCard = ({
430
452
  checked: propChecked,
431
453
  disabled = false,
432
454
  padding,
455
+ contentAlign = "center",
433
456
  onPress,
434
457
  "aria-label": ariaLabel,
435
458
  testID,
@@ -502,6 +525,7 @@ var RadioCard = ({
502
525
  {
503
526
  flexDirection: "column",
504
527
  gap: sizing.textGap,
528
+ alignSelf: contentAlign === "top" ? "flex-start" : void 0,
505
529
  style: { flex: 1, minWidth: 0 },
506
530
  children: [
507
531
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.tsx","../../src/RadioGroup.tsx","../../../../foundation/primitives-native/src/Box.tsx","../../../../foundation/primitives-native/src/Text.tsx","../../src/RadioCard.tsx"],"sourcesContent":["export * from \"./RadioGroup\";\nexport * from \"./RadioCard\";\n","import React, { createContext, useContext, useMemo } from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box } from \"@xsolla/xui-primitives\";\n\n/**\n * Visual treatment of the group's options.\n *\n * - `default` — bare `Radio` rows, the historical behaviour.\n * - `card` — each option is a selectable card (`RadioCard`) with a title, a\n * description and a radio indicator, where the whole card is the click\n * target.\n */\nexport type RadioGroupView = \"default\" | \"card\";\n\nexport interface RadioGroupContextProps {\n value?: string;\n onChange?: (value: string) => void;\n size?: \"sm\" | \"md\" | \"lg\" | \"xl\";\n disabled?: boolean;\n view?: RadioGroupView;\n testID?: string;\n}\n\nconst RadioGroupContext = createContext<RadioGroupContextProps>({});\n\nexport const useRadioGroup = () => useContext(RadioGroupContext);\n\nexport interface RadioGroupProps {\n value?: string;\n onChange?: (value: string) => void;\n size?: \"sm\" | \"md\" | \"lg\" | \"xl\";\n disabled?: boolean;\n children: React.ReactNode;\n flexDirection?: \"row\" | \"column\";\n gap?: number;\n /**\n * Visual treatment of the options. `card` renders `RadioCard` children as\n * selectable cards; it does not change the selection semantics.\n */\n view?: RadioGroupView;\n /**\n * Number of side-by-side columns. Values above `1` lay the options out as a\n * row of equal-width columns, which is how the Pentagram screens present a\n * short option list. Children are distributed round-robin so the visual\n * reading order stays left-to-right, then top-to-bottom.\n *\n * Takes precedence over `flexDirection`.\n */\n columns?: number;\n /** Test ID for testing frameworks */\n testID?: string;\n}\n\n/** Gap between bare `Radio` rows. */\nconst DEFAULT_GAP = 12;\n/**\n * Gap between cards. Matches the `itemSpacing` bound on the Figma \"Row\" /\n * \"Column\" frames of the distribution-model picker (node 5725:44606).\n */\nconst CARD_GAP = 4;\n\nexport const RadioGroup: React.FC<RadioGroupProps> = ({\n value,\n onChange,\n size = \"md\",\n disabled = false,\n children,\n flexDirection = \"column\",\n gap,\n view = \"default\",\n columns = 1,\n testID,\n}) => {\n const resolvedGap = gap ?? (view === \"card\" ? CARD_GAP : DEFAULT_GAP);\n\n // Distribute the children across `columns` equal-width columns. Round-robin\n // (rather than chunking) keeps the reading order row-major, so option 2 sits\n // to the right of option 1 instead of below it.\n const columnBuckets = useMemo(() => {\n if (columns <= 1) return null;\n const items = React.Children.toArray(children);\n const buckets: React.ReactNode[][] = Array.from(\n { length: columns },\n () => []\n );\n items.forEach((child, index) => {\n buckets[index % columns].push(child);\n });\n return buckets;\n }, [children, columns]);\n\n return (\n <RadioGroupContext.Provider\n value={{ value, onChange, size, disabled, view }}\n >\n {columnBuckets ? (\n <Box\n testID={testID}\n role=\"radiogroup\"\n aria-disabled={disabled || undefined}\n flexDirection=\"row\"\n alignItems=\"flex-start\"\n gap={resolvedGap}\n >\n {columnBuckets.map((bucket, index) => (\n <Box\n key={index}\n flexDirection=\"column\"\n gap={resolvedGap}\n flex={1}\n style={{ minWidth: 0 }}\n >\n {bucket}\n </Box>\n ))}\n </Box>\n ) : (\n <Box\n testID={testID}\n role=\"radiogroup\"\n aria-disabled={disabled || undefined}\n flexDirection={flexDirection}\n gap={resolvedGap}\n >\n {children}\n </Box>\n )}\n </RadioGroupContext.Provider>\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 React from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box, Text } from \"@xsolla/xui-primitives\";\nimport {\n useResolvedTheme,\n radiusScale,\n type ThemeOverrideProps,\n} from \"@xsolla/xui-core\";\nimport { useRadioGroup } from \"./RadioGroup\";\n\ntype RadioCardSize = \"sm\" | \"md\" | \"lg\" | \"xl\";\n\n/**\n * Comfortable internal padding per size, so a card never renders its content\n * edge-to-edge out of the box. Derived from the Pentagram distribution-model\n * picker (Figma node 5725:44606); override with the `padding` prop.\n */\nconst CARD_PADDING: Record<\n RadioCardSize,\n { vertical: number; horizontal: number }\n> = {\n sm: { vertical: 8, horizontal: 12 },\n md: { vertical: 12, horizontal: 16 },\n lg: { vertical: 16, horizontal: 16 },\n xl: { vertical: 16, horizontal: 20 },\n};\n\n/** Gap between the text block and the radio indicator. */\nconst CONTENT_GAP = 12;\n\nexport interface RadioCardProps extends ThemeOverrideProps {\n /** Value this card selects in the parent `RadioGroup`. */\n value: string;\n /** Primary line of the card. */\n title: string;\n /** Supporting line rendered under the title. */\n description?: string;\n /** Size of the card and its radio indicator. Falls back to the group's size. */\n size?: RadioCardSize;\n /** Selected state. Normally derived from the parent `RadioGroup`. */\n checked?: boolean;\n /** Disables this card only. The group's `disabled` disables every card. */\n disabled?: boolean;\n /**\n * Overrides the size-derived internal padding. Accepts anything `Box`'s\n * `padding` accepts.\n */\n padding?: number | string;\n /** Called after the card is selected, in addition to the group's `onChange`. */\n onPress?: () => void;\n /** Accessible label, when `title` is not descriptive enough on its own. */\n \"aria-label\"?: string;\n /** Test ID for testing frameworks */\n testID?: string;\n}\n\n/**\n * A selectable card option for `RadioGroup`.\n *\n * The whole card is the click target — not just the radio dot — and the\n * selected card is filled with the brand-tinted control surface. Compose them\n * inside a `RadioGroup` exactly like `Radio`:\n *\n * ```tsx\n * <RadioGroup view=\"card\" value={value} onChange={setValue}>\n * <RadioCard value=\"f2p\" title=\"Free-to-play (F2P)\" description=\"...\" />\n * <RadioCard value=\"p2p\" title=\"Pay-to-play (P2P)\" description=\"...\" />\n * </RadioGroup>\n * ```\n */\nexport const RadioCard: React.FC<RadioCardProps> = ({\n value,\n title,\n description,\n size: propSize,\n checked: propChecked,\n disabled = false,\n padding,\n onPress,\n \"aria-label\": ariaLabel,\n testID,\n themeMode,\n themeProductContext,\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const radioGroup = useRadioGroup();\n\n const size: RadioCardSize = propSize || radioGroup.size || \"md\";\n const checked =\n propChecked !== undefined ? propChecked : radioGroup.value === value;\n const isDisabled = disabled || Boolean(radioGroup.disabled);\n\n const sizing = theme.sizing.radio(size);\n const checkColors = theme.colors.control.check;\n const faintColors = theme.colors.control.faint;\n const textColors = theme.colors.control.text;\n const brandSurface = theme.colors.control.brand.secondary;\n const spacing = CARD_PADDING[size];\n\n const surfaceColor = checked\n ? brandSurface.bg\n : theme.colors.background.secondary;\n\n // Hover is an overlay laid over the resting fill, not a swap to a different\n // surface colour — a plain `backgroundColor` would replace the brand tint on\n // the selected card. Compose both layers the way Cell's surface view does.\n const overlay = theme.colors.overlay.mono;\n const hoverStyle = isDisabled\n ? undefined\n : {\n background: `linear-gradient(0deg, ${overlay} 0%, ${overlay} 100%), ${surfaceColor}`,\n };\n\n const handlePress = () => {\n if (isDisabled) return;\n if (radioGroup.onChange) {\n radioGroup.onChange(value);\n }\n if (onPress) {\n onPress();\n }\n };\n\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (isDisabled) return;\n if (event.key === \" \" || event.key === \"Enter\") {\n event.preventDefault(); // Space would otherwise scroll the page\n handlePress();\n }\n };\n\n const indicatorBg = isDisabled\n ? checkColors.bgDisable\n : checked\n ? checkColors.bg\n : faintColors.bg;\n\n const indicatorBorder = isDisabled\n ? checkColors.borderDisable\n : checked\n ? checkColors.border\n : faintColors.border;\n\n const dotColor = isDisabled ? textColors.disable : checkColors.icon;\n const titleColor = isDisabled ? textColors.disable : textColors.primary;\n const descriptionColor = isDisabled\n ? textColors.disable\n : theme.colors.content.tertiary;\n\n return (\n <Box\n testID={testID}\n role=\"radio\"\n aria-checked={checked}\n aria-disabled={isDisabled || undefined}\n aria-label={ariaLabel}\n tabIndex={isDisabled ? -1 : 0}\n onPress={handlePress}\n onKeyDown={handleKeyDown}\n cursor={isDisabled ? \"not-allowed\" : \"pointer\"}\n flexDirection=\"row\"\n alignItems=\"center\"\n justifyContent=\"space-between\"\n gap={CONTENT_GAP}\n width=\"100%\"\n backgroundColor={surfaceColor}\n borderRadius={theme.shape.cell.borderRadius}\n padding={padding}\n paddingVertical={padding === undefined ? spacing.vertical : undefined}\n paddingHorizontal={padding === undefined ? spacing.horizontal : undefined}\n hoverStyle={hoverStyle}\n >\n <Box\n flexDirection=\"column\"\n gap={sizing.textGap}\n style={{ flex: 1, minWidth: 0 }}\n >\n <Text\n color={titleColor}\n fontSize={sizing.fontSize}\n lineHeight={sizing.lineHeight}\n fontWeight=\"400\"\n >\n {title}\n </Text>\n {description && (\n <Text\n color={descriptionColor}\n fontSize={sizing.descriptionFontSize}\n lineHeight={sizing.descriptionLineHeight}\n >\n {description}\n </Text>\n )}\n </Box>\n\n {/*\n The indicator is presentational: the card itself carries `role=\"radio\"`\n and the press handler, so nesting an interactive `Radio` here would\n duplicate the radio role and its tab stop.\n */}\n <Box\n width={sizing.size}\n height={sizing.size}\n backgroundColor={indicatorBg}\n borderColor={indicatorBorder}\n borderWidth={sizing.borderWidth}\n borderRadius={radiusScale[\"999\"]}\n alignItems=\"center\"\n justifyContent=\"center\"\n style={{ flexShrink: 0 }}\n >\n {checked && (\n <Box\n width={sizing.iconSize}\n height={sizing.iconSize}\n backgroundColor={dotColor}\n borderRadius={radiusScale[\"999\"]}\n />\n )}\n </Box>\n </Box>\n );\n};\n"],"mappings":"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","names":["import_react_native","import_jsx_runtime","RNText","import_jsx_runtime","React","import_jsx_runtime"]}
1
+ {"version":3,"sources":["../../src/index.tsx","../../src/RadioGroup.tsx","../../../../foundation/primitives-native/src/Box.tsx","../../../../foundation/primitives-native/src/Text.tsx","../../src/RadioCard.tsx"],"sourcesContent":["export * from \"./RadioGroup\";\nexport * from \"./RadioCard\";\n","import React, { createContext, useContext, useMemo } from \"react\";\n// @ts-expect-error - this will be resolved at build time\nimport { Box } from \"@xsolla/xui-primitives\";\n\n/**\n * Visual treatment of the group's options.\n *\n * - `default` — bare `Radio` rows, the historical behaviour.\n * - `card` — each option is a selectable card (`RadioCard`) with a title, a\n * description and a radio indicator, where the whole card is the click\n * target.\n */\nexport type RadioGroupView = \"default\" | \"card\";\n\nexport interface RadioGroupContextProps {\n value?: string;\n onChange?: (value: string) => void;\n size?: \"sm\" | \"md\" | \"lg\" | \"xl\";\n disabled?: boolean;\n view?: RadioGroupView;\n testID?: string;\n}\n\nconst RadioGroupContext = createContext<RadioGroupContextProps>({});\n\nexport const useRadioGroup = () => useContext(RadioGroupContext);\n\nexport interface RadioGroupProps {\n value?: string;\n onChange?: (value: string) => void;\n size?: \"sm\" | \"md\" | \"lg\" | \"xl\";\n disabled?: boolean;\n children: React.ReactNode;\n flexDirection?: \"row\" | \"column\";\n gap?: number;\n /**\n * Visual treatment of the options. `card` renders `RadioCard` children as\n * selectable cards; it does not change the selection semantics.\n */\n view?: RadioGroupView;\n /**\n * Number of side-by-side columns. Values above `1` lay the options out as a\n * row of equal-width columns, which is how the Pentagram screens present a\n * short option list. Children are distributed round-robin so the visual\n * reading order stays left-to-right, then top-to-bottom.\n *\n * Every option in the row is stretched to the height of the tallest one, so\n * cards whose descriptions wrap to a different number of lines still line\n * up. Pair it with `RadioCard`'s `contentAlign=\"top\"` when the text should\n * stay pinned to the top of the stretched card.\n *\n * Takes precedence over `flexDirection`.\n */\n columns?: number;\n /** Test ID for testing frameworks */\n testID?: string;\n}\n\n/** Gap between bare `Radio` rows. */\nconst DEFAULT_GAP = 12;\n/**\n * Gap between cards. Matches the `itemSpacing` bound on the Figma \"Row\" /\n * \"Column\" frames of the distribution-model picker (node 5725:44606).\n */\nconst CARD_GAP = 4;\n\nexport const RadioGroup: React.FC<RadioGroupProps> = ({\n value,\n onChange,\n size = \"md\",\n disabled = false,\n children,\n flexDirection = \"column\",\n gap,\n view = \"default\",\n columns = 1,\n testID,\n}) => {\n const resolvedGap = gap ?? (view === \"card\" ? CARD_GAP : DEFAULT_GAP);\n\n // Distribute the children across `columns` equal-width columns. Round-robin\n // (rather than chunking) keeps the reading order row-major, so option 2 sits\n // to the right of option 1 instead of below it.\n const columnBuckets = useMemo(() => {\n if (columns <= 1) return null;\n const items = React.Children.toArray(children);\n const buckets: React.ReactNode[][] = Array.from(\n { length: columns },\n () => []\n );\n items.forEach((child, index) => {\n buckets[index % columns].push(child);\n });\n return buckets;\n }, [children, columns]);\n\n return (\n <RadioGroupContext.Provider\n value={{ value, onChange, size, disabled, view }}\n >\n {columnBuckets ? (\n <Box\n testID={testID}\n role=\"radiogroup\"\n aria-disabled={disabled || undefined}\n flexDirection=\"row\"\n // `stretch` (not `flex-start`) is what makes every column as tall as\n // the tallest one. On its own that only stretches the column — the\n // option inside it is stretched by its slot wrapper below.\n alignItems=\"stretch\"\n gap={resolvedGap}\n >\n {columnBuckets.map((bucket, index) => (\n <Box\n key={index}\n flexDirection=\"column\"\n gap={resolvedGap}\n flex={1}\n style={{ minWidth: 0 }}\n >\n {bucket.map((child, childIndex) => (\n // One slot per option. The column's main axis is vertical, so\n // an option does not grow into the column's spare height by\n // itself — the slot claims that space (`flexGrow`) and then\n // hands it to the option, which stretches across the slot's\n // cross axis because the slot is a row.\n //\n // `flexBasis: auto` rather than the `flex: 1` shorthand's `0`:\n // a slot never starts smaller than its own content, so a tall\n // option is never squashed to an equal share.\n <Box\n key={childIndex}\n flexDirection=\"row\"\n style={{ flexGrow: 1, flexBasis: \"auto\", minWidth: 0 }}\n >\n {child}\n </Box>\n ))}\n </Box>\n ))}\n </Box>\n ) : (\n <Box\n testID={testID}\n role=\"radiogroup\"\n aria-disabled={disabled || undefined}\n flexDirection={flexDirection}\n gap={resolvedGap}\n >\n {children}\n </Box>\n )}\n </RadioGroupContext.Provider>\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\n/**\n * Yoga has no inline formatting context, so RN's ViewStyle.display only\n * accepts \"flex\" | \"none\". Every inline-ish web value collapses to a plain\n * flex container — the box already fills only its content on native, which is\n * what those callers wanted on web anyway. Returning undefined (rather than\n * \"flex\") leaves RN's own default in place.\n */\nconst toNativeDisplay = (\n display: BoxProps[\"display\"]\n): ViewStyle[\"display\"] => (display === \"none\" ? \"none\" : undefined);\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 display,\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 display: toNativeDisplay(display),\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 {\n useResolvedTheme,\n radiusScale,\n type ThemeOverrideProps,\n} from \"@xsolla/xui-core\";\nimport { useRadioGroup } from \"./RadioGroup\";\n\ntype RadioCardSize = \"sm\" | \"md\" | \"lg\" | \"xl\";\n\n/**\n * Cross-axis placement of the card's text block.\n *\n * The radio indicator is *not* covered by this — it stays vertically centred\n * in the card either way, which is the split a single `alignItems` on the\n * card's row cannot express.\n *\n * - `center` — title and description centred against the indicator. The\n * historical behaviour, and indistinguishable from `top` on a card that is\n * only as tall as its own content.\n * - `top` — title and description pinned to the top edge of the card's\n * content box. Use this when the card is stretched taller than its own\n * content, e.g. inside a `RadioGroup` with `columns > 1`, where every card\n * in a row is equalised to the tallest one.\n */\nexport type RadioCardContentAlign = \"center\" | \"top\";\n\n/**\n * Comfortable internal padding per size, so a card never renders its content\n * edge-to-edge out of the box. Derived from the Pentagram distribution-model\n * picker (Figma node 5725:44606); override with the `padding` prop.\n */\nconst CARD_PADDING: Record<\n RadioCardSize,\n { vertical: number; horizontal: number }\n> = {\n sm: { vertical: 8, horizontal: 12 },\n md: { vertical: 12, horizontal: 16 },\n lg: { vertical: 16, horizontal: 16 },\n xl: { vertical: 16, horizontal: 20 },\n};\n\n/** Gap between the text block and the radio indicator. */\nconst CONTENT_GAP = 12;\n\nexport interface RadioCardProps extends ThemeOverrideProps {\n /** Value this card selects in the parent `RadioGroup`. */\n value: string;\n /** Primary line of the card. */\n title: string;\n /** Supporting line rendered under the title. */\n description?: string;\n /** Size of the card and its radio indicator. Falls back to the group's size. */\n size?: RadioCardSize;\n /** Selected state. Normally derived from the parent `RadioGroup`. */\n checked?: boolean;\n /** Disables this card only. The group's `disabled` disables every card. */\n disabled?: boolean;\n /**\n * Overrides the size-derived internal padding. Accepts anything `Box`'s\n * `padding` accepts.\n */\n padding?: number | string;\n /**\n * Where the title/description block sits when the card is taller than its\n * own content. The radio indicator stays vertically centred regardless.\n *\n * @default \"center\"\n */\n contentAlign?: RadioCardContentAlign;\n /** Called after the card is selected, in addition to the group's `onChange`. */\n onPress?: () => void;\n /** Accessible label, when `title` is not descriptive enough on its own. */\n \"aria-label\"?: string;\n /** Test ID for testing frameworks */\n testID?: string;\n}\n\n/**\n * A selectable card option for `RadioGroup`.\n *\n * The whole card is the click target — not just the radio dot — and the\n * selected card is filled with the brand-tinted control surface. Compose them\n * inside a `RadioGroup` exactly like `Radio`:\n *\n * ```tsx\n * <RadioGroup view=\"card\" value={value} onChange={setValue}>\n * <RadioCard value=\"f2p\" title=\"Free-to-play (F2P)\" description=\"...\" />\n * <RadioCard value=\"p2p\" title=\"Pay-to-play (P2P)\" description=\"...\" />\n * </RadioGroup>\n * ```\n */\nexport const RadioCard: React.FC<RadioCardProps> = ({\n value,\n title,\n description,\n size: propSize,\n checked: propChecked,\n disabled = false,\n padding,\n contentAlign = \"center\",\n onPress,\n \"aria-label\": ariaLabel,\n testID,\n themeMode,\n themeProductContext,\n}) => {\n const { theme } = useResolvedTheme({ themeMode, themeProductContext });\n const radioGroup = useRadioGroup();\n\n const size: RadioCardSize = propSize || radioGroup.size || \"md\";\n const checked =\n propChecked !== undefined ? propChecked : radioGroup.value === value;\n const isDisabled = disabled || Boolean(radioGroup.disabled);\n\n const sizing = theme.sizing.radio(size);\n const checkColors = theme.colors.control.check;\n const faintColors = theme.colors.control.faint;\n const textColors = theme.colors.control.text;\n const brandSurface = theme.colors.control.brand.secondary;\n const spacing = CARD_PADDING[size];\n\n const surfaceColor = checked\n ? brandSurface.bg\n : theme.colors.background.secondary;\n\n // Hover is an overlay laid over the resting fill, not a swap to a different\n // surface colour — a plain `backgroundColor` would replace the brand tint on\n // the selected card. Compose both layers the way Cell's surface view does.\n const overlay = theme.colors.overlay.mono;\n const hoverStyle = isDisabled\n ? undefined\n : {\n background: `linear-gradient(0deg, ${overlay} 0%, ${overlay} 100%), ${surfaceColor}`,\n };\n\n const handlePress = () => {\n if (isDisabled) return;\n if (radioGroup.onChange) {\n radioGroup.onChange(value);\n }\n if (onPress) {\n onPress();\n }\n };\n\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if (isDisabled) return;\n if (event.key === \" \" || event.key === \"Enter\") {\n event.preventDefault(); // Space would otherwise scroll the page\n handlePress();\n }\n };\n\n const indicatorBg = isDisabled\n ? checkColors.bgDisable\n : checked\n ? checkColors.bg\n : faintColors.bg;\n\n const indicatorBorder = isDisabled\n ? checkColors.borderDisable\n : checked\n ? checkColors.border\n : faintColors.border;\n\n const dotColor = isDisabled ? textColors.disable : checkColors.icon;\n const titleColor = isDisabled ? textColors.disable : textColors.primary;\n const descriptionColor = isDisabled\n ? textColors.disable\n : theme.colors.content.tertiary;\n\n return (\n <Box\n testID={testID}\n role=\"radio\"\n aria-checked={checked}\n aria-disabled={isDisabled || undefined}\n aria-label={ariaLabel}\n tabIndex={isDisabled ? -1 : 0}\n onPress={handlePress}\n onKeyDown={handleKeyDown}\n cursor={isDisabled ? \"not-allowed\" : \"pointer\"}\n flexDirection=\"row\"\n // Keeps the indicator centred. The text block opts out of this via its\n // own `alignSelf` below, which is what lets the two be aligned\n // independently.\n alignItems=\"center\"\n justifyContent=\"space-between\"\n gap={CONTENT_GAP}\n width=\"100%\"\n backgroundColor={surfaceColor}\n borderRadius={theme.shape.cell.borderRadius}\n padding={padding}\n paddingVertical={padding === undefined ? spacing.vertical : undefined}\n paddingHorizontal={padding === undefined ? spacing.horizontal : undefined}\n hoverStyle={hoverStyle}\n >\n <Box\n flexDirection=\"column\"\n gap={sizing.textGap}\n alignSelf={contentAlign === \"top\" ? \"flex-start\" : undefined}\n style={{ flex: 1, minWidth: 0 }}\n >\n <Text\n color={titleColor}\n fontSize={sizing.fontSize}\n lineHeight={sizing.lineHeight}\n fontWeight=\"400\"\n >\n {title}\n </Text>\n {description && (\n <Text\n color={descriptionColor}\n fontSize={sizing.descriptionFontSize}\n lineHeight={sizing.descriptionLineHeight}\n >\n {description}\n </Text>\n )}\n </Box>\n\n {/*\n The indicator is presentational: the card itself carries `role=\"radio\"`\n and the press handler, so nesting an interactive `Radio` here would\n duplicate the radio role and its tab stop.\n */}\n <Box\n width={sizing.size}\n height={sizing.size}\n backgroundColor={indicatorBg}\n borderColor={indicatorBorder}\n borderWidth={sizing.borderWidth}\n borderRadius={radiusScale[\"999\"]}\n alignItems=\"center\"\n justifyContent=\"center\"\n style={{ flexShrink: 0 }}\n >\n {checked && (\n <Box\n width={sizing.iconSize}\n height={sizing.iconSize}\n backgroundColor={dotColor}\n borderRadius={radiusScale[\"999\"]}\n />\n )}\n </Box>\n </Box>\n );\n};\n"],"mappings":"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","names":["import_react_native","import_jsx_runtime","RNText","import_jsx_runtime","React","import_jsx_runtime"]}
package/native/index.mjs CHANGED
@@ -40,6 +40,7 @@ var toGapValue = (gap) => {
40
40
  const asNumber = Number(gap);
41
41
  return Number.isFinite(asNumber) ? asNumber : void 0;
42
42
  };
43
+ var toNativeDisplay = (display) => display === "none" ? "none" : void 0;
43
44
  var Box = ({
44
45
  children,
45
46
  onPress,
@@ -71,6 +72,7 @@ var Box = ({
71
72
  marginBottom,
72
73
  marginLeft,
73
74
  marginRight,
75
+ display,
74
76
  flexDirection,
75
77
  flexWrap,
76
78
  alignItems,
@@ -134,6 +136,7 @@ var Box = ({
134
136
  marginBottom,
135
137
  marginLeft,
136
138
  marginRight,
139
+ display: toNativeDisplay(display),
137
140
  flexDirection,
138
141
  flexWrap,
139
142
  alignItems,
@@ -352,7 +355,7 @@ var RadioGroup = ({
352
355
  role: "radiogroup",
353
356
  "aria-disabled": disabled || void 0,
354
357
  flexDirection: "row",
355
- alignItems: "flex-start",
358
+ alignItems: "stretch",
356
359
  gap: resolvedGap,
357
360
  children: columnBuckets.map((bucket, index) => /* @__PURE__ */ jsx3(
358
361
  Box,
@@ -361,7 +364,26 @@ var RadioGroup = ({
361
364
  gap: resolvedGap,
362
365
  flex: 1,
363
366
  style: { minWidth: 0 },
364
- children: bucket
367
+ children: bucket.map((child, childIndex) => (
368
+ // One slot per option. The column's main axis is vertical, so
369
+ // an option does not grow into the column's spare height by
370
+ // itself — the slot claims that space (`flexGrow`) and then
371
+ // hands it to the option, which stretches across the slot's
372
+ // cross axis because the slot is a row.
373
+ //
374
+ // `flexBasis: auto` rather than the `flex: 1` shorthand's `0`:
375
+ // a slot never starts smaller than its own content, so a tall
376
+ // option is never squashed to an equal share.
377
+ /* @__PURE__ */ jsx3(
378
+ Box,
379
+ {
380
+ flexDirection: "row",
381
+ style: { flexGrow: 1, flexBasis: "auto", minWidth: 0 },
382
+ children: child
383
+ },
384
+ childIndex
385
+ )
386
+ ))
365
387
  },
366
388
  index
367
389
  ))
@@ -402,6 +424,7 @@ var RadioCard = ({
402
424
  checked: propChecked,
403
425
  disabled = false,
404
426
  padding,
427
+ contentAlign = "center",
405
428
  onPress,
406
429
  "aria-label": ariaLabel,
407
430
  testID,
@@ -474,6 +497,7 @@ var RadioCard = ({
474
497
  {
475
498
  flexDirection: "column",
476
499
  gap: sizing.textGap,
500
+ alignSelf: contentAlign === "top" ? "flex-start" : void 0,
477
501
  style: { flex: 1, minWidth: 0 },
478
502
  children: [
479
503
  /* @__PURE__ */ jsx4(