jfs-components 0.0.71 → 0.0.72

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/lib/commonjs/components/CardAdvisory/CardAdvisory.js +2 -2
  3. package/lib/commonjs/components/CardFinancialCondition/CardFinancialCondition.js +213 -0
  4. package/lib/commonjs/components/Carousel/Carousel.js +9 -7
  5. package/lib/commonjs/components/HoldingsCard/HoldingsCard.js +2 -2
  6. package/lib/commonjs/components/InstitutionBadge/InstitutionBadge.js +132 -0
  7. package/lib/commonjs/components/Radio/Radio.js +194 -0
  8. package/lib/commonjs/components/RadioButton/RadioButton.js +21 -188
  9. package/lib/commonjs/components/index.js +21 -0
  10. package/lib/commonjs/icons/registry.js +1 -1
  11. package/lib/module/components/CardAdvisory/CardAdvisory.js +2 -2
  12. package/lib/module/components/CardFinancialCondition/CardFinancialCondition.js +207 -0
  13. package/lib/module/components/Carousel/Carousel.js +9 -7
  14. package/lib/module/components/HoldingsCard/HoldingsCard.js +2 -2
  15. package/lib/module/components/InstitutionBadge/InstitutionBadge.js +127 -0
  16. package/lib/module/components/Radio/Radio.js +188 -0
  17. package/lib/module/components/RadioButton/RadioButton.js +20 -185
  18. package/lib/module/components/index.js +7 -0
  19. package/lib/module/icons/registry.js +1 -1
  20. package/lib/typescript/src/components/CardFinancialCondition/CardFinancialCondition.d.ts +50 -0
  21. package/lib/typescript/src/components/InstitutionBadge/InstitutionBadge.d.ts +30 -0
  22. package/lib/typescript/src/components/Radio/Radio.d.ts +30 -0
  23. package/lib/typescript/src/components/RadioButton/RadioButton.d.ts +20 -28
  24. package/lib/typescript/src/components/index.d.ts +7 -0
  25. package/lib/typescript/src/icons/registry.d.ts +1 -1
  26. package/package.json +1 -1
  27. package/src/components/CardAdvisory/CardAdvisory.tsx +2 -2
  28. package/src/components/CardFinancialCondition/CardFinancialCondition.tsx +366 -0
  29. package/src/components/Carousel/Carousel.tsx +14 -6
  30. package/src/components/HoldingsCard/HoldingsCard.tsx +2 -2
  31. package/src/components/InstitutionBadge/InstitutionBadge.tsx +216 -0
  32. package/src/components/Radio/Radio.tsx +227 -0
  33. package/src/components/RadioButton/RadioButton.tsx +23 -225
  34. package/src/components/index.ts +7 -0
  35. package/src/icons/registry.ts +1 -1
@@ -31,8 +31,8 @@ const toFontWeight = (value, fallback) => {
31
31
  function resolveCardAdvisoryTokens(modes) {
32
32
  const width = toNumber(getVariableByName('cardAdvisory/width', modes), 360);
33
33
  const gap = toNumber(getVariableByName('cardAdvisory/gap', modes), 16);
34
- const paddingHorizontal = toNumber(getVariableByName('cardAdvisory/padding/horizontal', modes), 16);
35
- const paddingVertical = toNumber(getVariableByName('cardAdvisory/padding/vertical', modes), 12);
34
+ const paddingHorizontal = toNumber(getVariableByName('cardAdvisory/padding/horizontal', modes), 0);
35
+ const paddingVertical = toNumber(getVariableByName('cardAdvisory/padding/vertical', modes), 0);
36
36
  const radius = toNumber(getVariableByName('cardAdvisory/radius', modes), 0);
37
37
  const background = getVariableByName('cardAdvisory/background', modes) || '#ffffff';
38
38
  const mainContentGap = toNumber(getVariableByName('cardAdvisory/mainContent/gap', modes), 16);
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+
3
+ import React, { useMemo } from 'react';
4
+ import { Text, View } from 'react-native';
5
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
+ import { useTokens } from '../../design-tokens/JFSThemeProvider';
7
+ import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
8
+ import Button from '../Button/Button';
9
+ import CircularProgressBar from '../CircularProgressBar/CircularProgressBar';
10
+ import Divider from '../Divider/Divider';
11
+ import Nudge from '../Nudge/Nudge';
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ const toNumber = (value, fallback) => {
14
+ if (typeof value === 'number') {
15
+ return Number.isFinite(value) ? value : fallback;
16
+ }
17
+ if (typeof value === 'string') {
18
+ const parsed = Number(value);
19
+ return Number.isFinite(parsed) ? parsed : fallback;
20
+ }
21
+ return fallback;
22
+ };
23
+ const toFontWeight = (value, fallback) => {
24
+ if (typeof value === 'number') {
25
+ return String(value);
26
+ }
27
+ if (typeof value === 'string') {
28
+ return value;
29
+ }
30
+ return fallback;
31
+ };
32
+ function resolveCardFinancialConditionTokens(modes) {
33
+ const background = getVariableByName('financialConditionCard/background', modes) || '#ffffff';
34
+ const radius = toNumber(getVariableByName('financialConditionCard/radius', modes), 16);
35
+ const paddingH = toNumber(getVariableByName('financialConditionCard/padding/horizontal', modes), 16);
36
+ const paddingV = toNumber(getVariableByName('financialConditionCard/padding/vertical', modes), 16);
37
+ const gap = toNumber(getVariableByName('financialConditionCard/gap', modes), 8);
38
+ const headerGap = toNumber(getVariableByName('financialConditionCard/header/gap', modes), 16);
39
+ const textContentGap = toNumber(getVariableByName('financialConditionCard/textContent/gap', modes), 8);
40
+ const titleColor = getVariableByName('financialConditionCard/title/color', modes) || '#0c0d10';
41
+ const titleFontSize = toNumber(getVariableByName('financialConditionCard/title/fontSize', modes), 16);
42
+ const titleFontFamily = getVariableByName('financialConditionCard/title/fontFamily', modes) || 'JioType Var';
43
+ const titleLineHeight = toNumber(getVariableByName('financialConditionCard/title/lineHeight', modes), 18);
44
+ const titleFontWeight = toFontWeight(getVariableByName('financialConditionCard/title/fontWeight', modes), '700');
45
+ const bodyColor = getVariableByName('financialConditionCard/body/color', modes) || '#0c0d10';
46
+ const bodyFontSize = toNumber(getVariableByName('financialConditionCard/body/fontSize', modes), 14);
47
+ const bodyFontFamily = getVariableByName('financialConditionCard/body/fontFamily', modes) || 'JioType Var';
48
+ const bodyLineHeight = toNumber(getVariableByName('financialConditionCard/body/lineHeight', modes), 17);
49
+ const bodyFontWeight = toFontWeight(getVariableByName('financialConditionCard/body/fontWeight', modes), '400');
50
+ const nudgeBodyColor = getVariableByName('nudge/body/color', modes) || '#1a1c1f';
51
+ const nudgeBodyFontSize = toNumber(getVariableByName('nudge/body/fontSize', modes), 12);
52
+ const nudgeBodyFontFamily = getVariableByName('nudge/body/fontFamily', modes) || 'JioType Var';
53
+ const nudgeBodyLineHeight = toNumber(getVariableByName('nudge/body/lineHeight', modes), 16);
54
+ const nudgeBodyFontWeight = toFontWeight(getVariableByName('nudge/body/fontWeight', modes), '500');
55
+ return {
56
+ containerStyle: {
57
+ alignItems: 'flex-start',
58
+ backgroundColor: background,
59
+ borderRadius: radius,
60
+ gap,
61
+ justifyContent: 'center',
62
+ overflow: 'hidden',
63
+ paddingHorizontal: paddingH,
64
+ paddingVertical: paddingV
65
+ },
66
+ headerStyle: {
67
+ alignItems: 'center',
68
+ flexDirection: 'row',
69
+ gap: headerGap,
70
+ width: '100%'
71
+ },
72
+ textContentStyle: {
73
+ alignItems: 'flex-start',
74
+ flex: 1,
75
+ gap: textContentGap,
76
+ minWidth: 1
77
+ },
78
+ titleStyle: {
79
+ color: titleColor,
80
+ fontFamily: titleFontFamily,
81
+ fontSize: titleFontSize,
82
+ fontWeight: titleFontWeight,
83
+ lineHeight: titleLineHeight
84
+ },
85
+ bodyStyle: {
86
+ color: bodyColor,
87
+ fontFamily: bodyFontFamily,
88
+ fontSize: bodyFontSize,
89
+ fontWeight: bodyFontWeight,
90
+ lineHeight: bodyLineHeight
91
+ },
92
+ nudgeBodyStyle: {
93
+ color: nudgeBodyColor,
94
+ fontFamily: nudgeBodyFontFamily,
95
+ fontSize: nudgeBodyFontSize,
96
+ fontWeight: nudgeBodyFontWeight,
97
+ lineHeight: nudgeBodyLineHeight
98
+ },
99
+ buttonWrapStyle: {
100
+ alignSelf: 'stretch',
101
+ alignItems: 'flex-start'
102
+ }
103
+ };
104
+ }
105
+ function CardFinancialCondition({
106
+ title = 'Protection',
107
+ body = 'Check your coverage and gaps',
108
+ value = 0,
109
+ progressState = 'Inactive',
110
+ valueLabel,
111
+ showNudge = true,
112
+ nudgeBody = 'There may be gaps in your health or life cover\nAdd coverage to stay financially secure',
113
+ showDivider = true,
114
+ showButton = true,
115
+ buttonLabel = 'View Details',
116
+ onPressButton,
117
+ progressSlot,
118
+ nudgeSlot,
119
+ buttonSlot,
120
+ modes: propModes = EMPTY_MODES,
121
+ style,
122
+ headerStyle,
123
+ titleStyle,
124
+ bodyStyle,
125
+ accessibilityLabel,
126
+ ...rest
127
+ }) {
128
+ const {
129
+ modes: globalModes
130
+ } = useTokens();
131
+ const modes = useMemo(() => globalModes === EMPTY_MODES && propModes === EMPTY_MODES ? EMPTY_MODES : {
132
+ ...globalModes,
133
+ ...propModes
134
+ }, [globalModes, propModes]);
135
+ const tokens = useMemo(() => resolveCardFinancialConditionTokens(modes), [modes]);
136
+
137
+ // Match Figma: the action button is the primary call-to-action and uses the
138
+ // Secondary brand (purple) regardless of the card's overall AppearanceBrand,
139
+ // while Nudge / CircularProgressBar still follow the user-supplied modes.
140
+ const buttonModes = useMemo(() => ({
141
+ ...modes,
142
+ AppearanceBrand: 'Secondary'
143
+ }), [modes]);
144
+ const processedProgressSlot = useMemo(() => {
145
+ if (!progressSlot) return null;
146
+ const processed = cloneChildrenWithModes(React.Children.toArray(progressSlot), modes);
147
+ return processed.length === 1 ? processed[0] : processed;
148
+ }, [progressSlot, modes]);
149
+ const processedNudgeSlot = useMemo(() => {
150
+ if (!nudgeSlot) return null;
151
+ const processed = cloneChildrenWithModes(React.Children.toArray(nudgeSlot), modes);
152
+ return processed.length === 1 ? processed[0] : processed;
153
+ }, [nudgeSlot, modes]);
154
+ const processedButtonSlot = useMemo(() => {
155
+ if (!buttonSlot) return null;
156
+ const processed = cloneChildrenWithModes(React.Children.toArray(buttonSlot), buttonModes);
157
+ return processed.length === 1 ? processed[0] : processed;
158
+ }, [buttonSlot, buttonModes]);
159
+ const defaultAccessibilityLabel = accessibilityLabel ?? `${title}. ${body}.`;
160
+ return /*#__PURE__*/_jsxs(View, {
161
+ accessibilityLabel: defaultAccessibilityLabel,
162
+ style: [tokens.containerStyle, style],
163
+ ...rest,
164
+ children: [/*#__PURE__*/_jsxs(View, {
165
+ style: [tokens.headerStyle, headerStyle],
166
+ children: [/*#__PURE__*/_jsxs(View, {
167
+ style: tokens.textContentStyle,
168
+ children: [/*#__PURE__*/_jsx(Text, {
169
+ style: [tokens.titleStyle, titleStyle],
170
+ children: title
171
+ }), /*#__PURE__*/_jsx(Text, {
172
+ style: [tokens.bodyStyle, bodyStyle],
173
+ children: body
174
+ })]
175
+ }), processedProgressSlot || /*#__PURE__*/_jsx(CircularProgressBar, {
176
+ state: progressState,
177
+ value: value,
178
+ modes: modes,
179
+ ...(valueLabel ? {
180
+ valueLabel
181
+ } : {})
182
+ })]
183
+ }), showNudge ? processedNudgeSlot || /*#__PURE__*/_jsx(Nudge, {
184
+ type: "inline-compact",
185
+ modes: modes,
186
+ style: {
187
+ width: '100%'
188
+ },
189
+ children: /*#__PURE__*/_jsx(Text, {
190
+ style: tokens.nudgeBodyStyle,
191
+ children: nudgeBody
192
+ })
193
+ }) : null, showDivider ? /*#__PURE__*/_jsx(Divider, {
194
+ modes: modes
195
+ }) : null, showButton ? /*#__PURE__*/_jsx(View, {
196
+ style: tokens.buttonWrapStyle,
197
+ children: processedButtonSlot || /*#__PURE__*/_jsx(Button, {
198
+ label: buttonLabel,
199
+ modes: buttonModes,
200
+ ...(onPressButton ? {
201
+ onPress: onPressButton
202
+ } : {})
203
+ })
204
+ }) : null]
205
+ });
206
+ }
207
+ export default /*#__PURE__*/React.memo(CardFinancialCondition);
@@ -43,6 +43,8 @@ export function Carousel({
43
43
  const gap = gapProp ?? tokenGap;
44
44
  const containerPaddingH = parseFloat(getVariableByName('carousel/padding/horizontal', modes) || '0');
45
45
  const containerPaddingV = parseFloat(getVariableByName('carousel/padding/vertical', modes) || '0');
46
+ // Outer container max height per Figma (`carousel/maxHeight`).
47
+ const maxHeight = parseFloat(getVariableByName('carousel/maxHeight', modes) || '280');
46
48
  // Spacing between the cards row and the pagination dots uses `carousel/gap`.
47
49
  const paginationOffset = gap;
48
50
 
@@ -154,7 +156,8 @@ export function Carousel({
154
156
 
155
157
  // ---- Render ----
156
158
  const outerStyle = {
157
- paddingVertical: containerPaddingV
159
+ paddingVertical: containerPaddingV,
160
+ maxHeight
158
161
  };
159
162
  const contentContainerStyle = {
160
163
  paddingHorizontal: containerPaddingH * 2,
@@ -262,13 +265,12 @@ export function Pagination({
262
265
  const modes = propModes || ctxModes || {};
263
266
 
264
267
  // Token resolution for dots — matches Figma tokens
265
- // (carousel/pagination/gap, carousel/pagination/indicator/{activecolor,inactivecolor,radius}).
266
- // Dot dimensions are fixed per Figma spec: inactive 6x6, active 16x6.
267
- const dotSize = 6;
268
- const dotActiveWidth = 16;
268
+ // (carousel/pagination/gap, carousel/pagination/indicator/{activeColor,inactiveColor,radius,size,activeWidth}).
269
+ const dotSize = parseFloat(getVariableByName('carousel/pagination/indicator/size', modes) || '6');
270
+ const dotActiveWidth = parseFloat(getVariableByName('carousel/pagination/indicator/activeWidth', modes) || '16');
269
271
  const dotGap = parseFloat(getVariableByName('carousel/pagination/gap', modes) || '4');
270
- const dotColor = getVariableByName('carousel/pagination/indicator/inactivecolor', modes) || 'rgba(0,0,0,0.3)';
271
- const dotActiveColor = getVariableByName('carousel/pagination/indicator/activecolor', modes) || '#170d0a';
272
+ const dotColor = getVariableByName('carousel/pagination/indicator/inactiveColor', modes) || 'rgba(0,0,0,0.3)';
273
+ const dotActiveColor = getVariableByName('carousel/pagination/indicator/activeColor', modes) || '#170d0a';
272
274
  const dotRadius = parseFloat(getVariableByName('carousel/pagination/indicator/radius', modes) || '9999');
273
275
  const containerStyle = {
274
276
  flexDirection: 'row',
@@ -3,7 +3,7 @@
3
3
  import React from 'react';
4
4
  import { View, Text, Pressable, Image } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
- import RadioButton from '../RadioButton/RadioButton';
6
+ import Radio from '../Radio/Radio';
7
7
  import { EMPTY_MODES } from '../../utils/react-utils';
8
8
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
9
  const GRID_COLUMNS = 3;
@@ -123,7 +123,7 @@ export default function HoldingsCard({
123
123
  }
124
124
  }), /*#__PURE__*/_jsx(View, {
125
125
  pointerEvents: "none",
126
- children: /*#__PURE__*/_jsx(RadioButton, {
126
+ children: /*#__PURE__*/_jsx(Radio, {
127
127
  selected: selected,
128
128
  disabled: disabled,
129
129
  modes: modes
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+
3
+ import React, { useMemo } from 'react';
4
+ import { Text, View } from 'react-native';
5
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
+ import { useTokens } from '../../design-tokens/JFSThemeProvider';
7
+ import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
8
+ import MediaSource from '../../utils/MediaSource';
9
+
10
+ // Default avatar asset (shared with the Avatar component) so that
11
+ // InstitutionBadge has a sensible visual fallback when no `source` is
12
+ // provided in stories or playgrounds.
13
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
+ const DEFAULT_AVATAR_IMAGE = require('../Avatar/31595e70c4181263f9971590224b12934b280c9b.png');
15
+ const toNumber = (value, fallback) => {
16
+ if (typeof value === 'number') {
17
+ return Number.isFinite(value) ? value : fallback;
18
+ }
19
+ if (typeof value === 'string') {
20
+ const parsed = Number(value);
21
+ return Number.isFinite(parsed) ? parsed : fallback;
22
+ }
23
+ return fallback;
24
+ };
25
+ const toFontWeight = (value, fallback) => {
26
+ if (typeof value === 'number') {
27
+ return String(value);
28
+ }
29
+ if (typeof value === 'string') {
30
+ return value;
31
+ }
32
+ return fallback;
33
+ };
34
+ function resolveInstitutionBadgeTokens(modes) {
35
+ const gap = toNumber(getVariableByName('institutionBadge/gap', modes), 8);
36
+ const foreground = getVariableByName('institutionBadge/foreground', modes) || '#080d1a';
37
+ const fontSize = toNumber(getVariableByName('institutionBadge/fontSize', modes), 14);
38
+ const fontFamily = getVariableByName('institutionBadge/fontFamily', modes) || 'JioType Var';
39
+ const lineHeight = toNumber(getVariableByName('institutionBadge/lineHeight', modes), 18);
40
+ const fontWeight = toFontWeight(getVariableByName('institutionBadge/fontWeight', modes), '500');
41
+
42
+ // Avatar wrapper styled from shared `avatar/*` tokens so the badge stays
43
+ // visually consistent with other Avatar-bearing components.
44
+ const avatarSize = toNumber(getVariableByName('avatar/size', modes), 36);
45
+ const avatarRadiusRaw = toNumber(getVariableByName('avatar/radius', modes), 9999);
46
+ // 9999 is the design-token sentinel for "perfect circle".
47
+ const avatarRadius = avatarRadiusRaw === 9999 ? avatarSize / 2 : avatarRadiusRaw;
48
+ const avatarBorderColor = getVariableByName('avatar/border/color', modes) || 'rgba(255,255,255,0)';
49
+ const avatarBorderSize = toNumber(getVariableByName('avatar/border/size', modes), 1);
50
+ return {
51
+ containerStyle: {
52
+ alignItems: 'center',
53
+ flexDirection: 'row',
54
+ gap
55
+ },
56
+ avatarStyle: {
57
+ width: avatarSize,
58
+ height: avatarSize,
59
+ borderRadius: avatarRadius,
60
+ borderWidth: avatarBorderSize,
61
+ borderColor: avatarBorderColor,
62
+ overflow: 'hidden'
63
+ },
64
+ avatarSize,
65
+ labelStyle: {
66
+ color: foreground,
67
+ fontFamily,
68
+ fontSize,
69
+ fontWeight,
70
+ lineHeight
71
+ }
72
+ };
73
+ }
74
+
75
+ // Default Avatar Size = M (36px in tokens) — matches the Figma badge. Callers
76
+ // can override via `modes` if needed.
77
+ const DEFAULT_AVATAR_MODE = 'M';
78
+ function InstitutionBadge({
79
+ label = 'State Bank of India',
80
+ source,
81
+ avatarSlot,
82
+ modes: propModes = EMPTY_MODES,
83
+ style,
84
+ labelStyle,
85
+ accessibilityLabel,
86
+ ...rest
87
+ }) {
88
+ const {
89
+ modes: globalModes
90
+ } = useTokens();
91
+ const modes = useMemo(() => globalModes === EMPTY_MODES && propModes === EMPTY_MODES ? EMPTY_MODES : {
92
+ ...globalModes,
93
+ ...propModes
94
+ }, [globalModes, propModes]);
95
+ const avatarModes = useMemo(() => ({
96
+ 'Avatar Size': DEFAULT_AVATAR_MODE,
97
+ ...modes
98
+ }), [modes]);
99
+ const tokens = useMemo(() => resolveInstitutionBadgeTokens(avatarModes), [avatarModes]);
100
+ const processedAvatarSlot = useMemo(() => {
101
+ if (!avatarSlot) return null;
102
+ const processed = cloneChildrenWithModes(React.Children.toArray(avatarSlot), avatarModes);
103
+ return processed.length === 1 ? processed[0] : processed;
104
+ }, [avatarSlot, avatarModes]);
105
+ const resolvedSource = source ?? DEFAULT_AVATAR_IMAGE;
106
+ const defaultAccessibilityLabel = accessibilityLabel ?? label;
107
+ return /*#__PURE__*/_jsxs(View, {
108
+ accessibilityLabel: defaultAccessibilityLabel,
109
+ style: [tokens.containerStyle, style],
110
+ ...rest,
111
+ children: [processedAvatarSlot || /*#__PURE__*/_jsx(View, {
112
+ style: tokens.avatarStyle,
113
+ children: /*#__PURE__*/_jsx(MediaSource, {
114
+ source: resolvedSource,
115
+ size: tokens.avatarSize,
116
+ resizeMode: "cover",
117
+ accessibilityElementsHidden: true,
118
+ importantForAccessibility: "no"
119
+ })
120
+ }), /*#__PURE__*/_jsx(Text, {
121
+ style: [tokens.labelStyle, labelStyle],
122
+ numberOfLines: 1,
123
+ children: label
124
+ })]
125
+ });
126
+ }
127
+ export default /*#__PURE__*/React.memo(InstitutionBadge);
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+
3
+ import React, { useState } from 'react';
4
+ import { Pressable, View, Platform } from 'react-native';
5
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
+ import { EMPTY_MODES } from '../../utils/react-utils';
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Props
10
+ // ---------------------------------------------------------------------------
11
+ import { jsx as _jsx } from "react/jsx-runtime";
12
+ // ---------------------------------------------------------------------------
13
+ // Radio
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export function Radio({
17
+ selected = false,
18
+ disabled = false,
19
+ onPress,
20
+ modes = EMPTY_MODES,
21
+ style,
22
+ testID
23
+ }) {
24
+ // ---- Refs & State ----
25
+ const [hovered, setHovered] = useState(false);
26
+ const [focused, setFocused] = useState(false);
27
+ const [pressed, setPressed] = useState(false);
28
+
29
+ // ---- Dimensions ----
30
+ const widthStr = getVariableByName('radio/width', modes) || '18';
31
+ const heightStr = getVariableByName('radio/height', modes) || '18';
32
+ const selectorSizeStr = getVariableByName('radio/selector/size', modes) || '10';
33
+ const width = parseFloat(widthStr?.toString() || '18');
34
+ const height = parseFloat(heightStr?.toString() || '18');
35
+ const selectorSize = parseFloat(selectorSizeStr?.toString() || '10');
36
+
37
+ // ---- State Logic ----
38
+ // Priority: Disabled -> Focused -> Hover/Pressed -> Idle
39
+ // Note: Design treats Active (Pressed) similar to Selected for some styles,
40
+ // but usually in Radios, Pressed is a transient state.
41
+ // We will map:
42
+ // - Disabled -> 'disabled'
43
+ // - Focused -> 'focus'
44
+ // - Hovered -> 'hover'
45
+ // - Idle -> 'idle'
46
+
47
+ // We handle `selected` as a separate dimension derived from state.
48
+
49
+ let visualState = 'idle';
50
+ if (disabled) {
51
+ visualState = 'disabled';
52
+ } else if (focused) {
53
+ visualState = 'focus';
54
+ } else if (hovered || pressed) {
55
+ visualState = 'hover';
56
+ }
57
+
58
+ // Construct token paths based on state + selected
59
+ let prefix = `radio/${visualState}`;
60
+ if (visualState === 'idle' && selected) {
61
+ prefix = `radio/selected`;
62
+ } else if (selected) {
63
+ prefix = `radio/${visualState}Selected`;
64
+ }
65
+
66
+ // ---- Colors & Border ----
67
+
68
+ const resolveColor = (path, fallback) => {
69
+ return getVariableByName(path, modes) || fallback;
70
+ };
71
+
72
+ // Background Color
73
+ let bgColorVar = `${prefix}/background/color`;
74
+ // Fix for disabledSelected weirdness if needed
75
+ if (visualState === 'disabled' && selected) {
76
+ // Check specific path from dump: `radio/disabledSelected/background`
77
+ if (!getVariableByName(`${prefix}/background/color`, modes)) {
78
+ bgColorVar = `${prefix}/background`;
79
+ }
80
+ }
81
+
82
+ // Border Color
83
+ let borderColorVar = `${prefix}/border/color`;
84
+
85
+ // Border Width
86
+ let borderWidthVar = `${prefix}/border/size`;
87
+ // Fix for huge path: `radio/disabled/radio/disabled/border/size`
88
+ if (visualState === 'disabled' && !selected) {
89
+ if (getVariableByName('radio/disabled/radio/disabled/border/size', modes)) {
90
+ borderWidthVar = 'radio/disabled/radio/disabled/border/size';
91
+ }
92
+ }
93
+
94
+ // Selector Color
95
+ let selectorBgVar = `${prefix}/selector/background/color`;
96
+ if (!selected) {
97
+ selectorBgVar = 'transparent';
98
+ }
99
+
100
+ // Shadows (Glow)
101
+ let shadowSizeVar = `${prefix}/boxShadow/size`;
102
+ let shadowColorVar = `${prefix}/shadow/color`;
103
+
104
+ // Resolve Values
105
+ const backgroundColor = resolveColor(bgColorVar, 'transparent');
106
+ const borderColor = resolveColor(borderColorVar, 'transparent');
107
+ const borderWidth = parseFloat(getVariableByName(borderWidthVar, modes)?.toString() || '1');
108
+ const selectorColor = resolveColor(selectorBgVar, 'transparent');
109
+ const shadowSize = parseFloat(getVariableByName(shadowSizeVar, modes)?.toString() || '0');
110
+ const shadowColor = resolveColor(shadowColorVar, 'transparent');
111
+
112
+ // Styles
113
+ const containerStyle = {
114
+ width,
115
+ height,
116
+ borderRadius: width / 2,
117
+ // 9999px -> circle
118
+ borderWidth,
119
+ borderColor,
120
+ backgroundColor,
121
+ justifyContent: 'center',
122
+ alignItems: 'center',
123
+ // Web shadow (ring)
124
+ ...(Platform.OS === 'web' && shadowSize > 0 ? {
125
+ boxShadow: `0px 0px 0px ${shadowSize}px ${shadowColor}`
126
+ } : {})
127
+ };
128
+ const selectorStyle = {
129
+ width: selectorSize,
130
+ height: selectorSize,
131
+ borderRadius: selectorSize / 2,
132
+ backgroundColor: selectorColor
133
+ };
134
+
135
+ // Dummy block for token extraction (static analysis)
136
+ if (false) {
137
+ getVariableByName('radio/idle/background/color');
138
+ getVariableByName('radio/idle/border/color');
139
+ getVariableByName('radio/selector/size');
140
+ getVariableByName('radio/width');
141
+ getVariableByName('radio/height');
142
+ getVariableByName('radio/background/color');
143
+ getVariableByName('radio/hover/background/color');
144
+ getVariableByName('radio/hover/border/color');
145
+ getVariableByName('radio/hover/boxShadow/size');
146
+ getVariableByName('radio/hover/shadow/color');
147
+ getVariableByName('radio/selected/background/color');
148
+ getVariableByName('radio/selected/border/color');
149
+ getVariableByName('radio/selected/selector/background/color');
150
+ getVariableByName('radio/hoverSelected/background/color');
151
+ getVariableByName('radio/hoverSelected/border/color');
152
+ getVariableByName('radio/hoverSelected/boxShadow/size');
153
+ getVariableByName('radio/hoverSelected/shadow/color');
154
+ getVariableByName('radio/hoverSelected/selector/background/color');
155
+ getVariableByName('radio/focus/background/color');
156
+ getVariableByName('radio/focus/border/color');
157
+ getVariableByName('radio/focus/border/size');
158
+ getVariableByName('radio/focus/boxShadow/size');
159
+ getVariableByName('radio/focus/shadow/color');
160
+ getVariableByName('radio/focusSelected/background/color');
161
+ getVariableByName('radio/focusSelected/selector/background/color');
162
+ getVariableByName('radio/focusSelected/border/size');
163
+ getVariableByName('radio/disabled/radio/disabled/border/size');
164
+ getVariableByName('radio/disabled/background/color');
165
+ getVariableByName('radio/disabled/border/color');
166
+ getVariableByName('radio/disabledSelected/selector/background/color');
167
+ getVariableByName('radio/disabledSelected/background');
168
+ getVariableByName('radio/disabledSelected/border/color');
169
+ }
170
+ return /*#__PURE__*/_jsx(Pressable, {
171
+ testID: testID,
172
+ disabled: disabled,
173
+ onPress: onPress,
174
+ onHoverIn: () => setHovered(true),
175
+ onHoverOut: () => setHovered(false),
176
+ onFocus: () => setFocused(true),
177
+ onBlur: () => setFocused(false),
178
+ onPressIn: () => setPressed(true),
179
+ onPressOut: () => setPressed(false),
180
+ style: ({
181
+ pressed: isPressed
182
+ }) => [containerStyle, style],
183
+ children: /*#__PURE__*/_jsx(View, {
184
+ style: selectorStyle
185
+ })
186
+ });
187
+ }
188
+ export default Radio;