jfs-components 0.0.71 → 0.0.73
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/CHANGELOG.md +60 -0
- package/lib/commonjs/components/AccordionCheckbox/AccordionCheckbox.js +239 -0
- package/lib/commonjs/components/BrandChip/BrandChip.js +149 -0
- package/lib/commonjs/components/CardAdvisory/CardAdvisory.js +2 -2
- package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +213 -0
- package/lib/commonjs/components/CardFinancialCondition/CardFinancialCondition.js +213 -0
- package/lib/commonjs/components/CardInsight/CardInsight.js +166 -0
- package/lib/commonjs/components/Carousel/Carousel.js +9 -7
- package/lib/commonjs/components/CheckboxGroup/CheckboxGroup.js +67 -0
- package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +125 -0
- package/lib/commonjs/components/CircularProgressBar/CircularProgressBar.js +56 -9
- package/lib/commonjs/components/CoverageBarComparison/CoverageBarComparison.js +272 -0
- package/lib/commonjs/components/CoverageRing/CoverageRing.js +141 -0
- package/lib/commonjs/components/DonutChart/DonutChart.js +309 -0
- package/lib/commonjs/components/DonutChartSummary/DonutChartSummary.js +155 -0
- package/lib/commonjs/components/HoldingsCard/HoldingsCard.js +2 -2
- package/lib/commonjs/components/InstitutionBadge/InstitutionBadge.js +132 -0
- package/lib/commonjs/components/LinearMeter/LinearMeter.js +9 -28
- package/lib/commonjs/components/LinearProgress/LinearProgress.js +68 -0
- package/lib/commonjs/components/MetricLegendItem/MetricLegendItem.js +95 -0
- package/lib/commonjs/components/MonthlyStatusGrid/MonthlyStatusGrid.js +286 -0
- package/lib/commonjs/components/OTP/OTP.js +381 -37
- package/lib/commonjs/components/ProductOverview/ProductOverview.js +147 -0
- package/lib/commonjs/components/Radio/Radio.js +194 -0
- package/lib/commonjs/components/RadioButton/RadioButton.js +21 -188
- package/lib/commonjs/components/RangeTrack/RangeTrack.js +269 -0
- package/lib/commonjs/components/SavingsGoalSummary/SavingsGoalSummary.js +181 -0
- package/lib/commonjs/components/SegmentedTrack/SegmentedTrack.js +171 -0
- package/lib/commonjs/components/StatGroup/StatGroup.js +128 -0
- package/lib/commonjs/components/StatItem/StatItem.js +65 -35
- package/lib/commonjs/components/StrengthIndicator/StrengthIndicator.js +157 -0
- package/lib/commonjs/components/SummaryTile/SummaryTile.js +150 -0
- package/lib/commonjs/components/index.js +192 -1
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/commonjs/utils/index.js +7 -0
- package/lib/commonjs/utils/number-utils.js +57 -0
- package/lib/module/components/AccordionCheckbox/AccordionCheckbox.js +233 -0
- package/lib/module/components/BrandChip/BrandChip.js +143 -0
- package/lib/module/components/CardAdvisory/CardAdvisory.js +2 -2
- package/lib/module/components/CardBankAccount/CardBankAccount.js +208 -0
- package/lib/module/components/CardFinancialCondition/CardFinancialCondition.js +207 -0
- package/lib/module/components/CardInsight/CardInsight.js +161 -0
- package/lib/module/components/Carousel/Carousel.js +9 -7
- package/lib/module/components/CheckboxGroup/CheckboxGroup.js +62 -0
- package/lib/module/components/CheckboxItem/CheckboxItem.js +119 -0
- package/lib/module/components/CircularProgressBar/CircularProgressBar.js +56 -9
- package/lib/module/components/CoverageBarComparison/CoverageBarComparison.js +266 -0
- package/lib/module/components/CoverageRing/CoverageRing.js +136 -0
- package/lib/module/components/DonutChart/DonutChart.js +303 -0
- package/lib/module/components/DonutChartSummary/DonutChartSummary.js +150 -0
- package/lib/module/components/HoldingsCard/HoldingsCard.js +2 -2
- package/lib/module/components/InstitutionBadge/InstitutionBadge.js +127 -0
- package/lib/module/components/LinearMeter/LinearMeter.js +9 -28
- package/lib/module/components/LinearProgress/LinearProgress.js +63 -0
- package/lib/module/components/MetricLegendItem/MetricLegendItem.js +90 -0
- package/lib/module/components/MonthlyStatusGrid/MonthlyStatusGrid.js +281 -0
- package/lib/module/components/OTP/OTP.js +381 -38
- package/lib/module/components/ProductOverview/ProductOverview.js +142 -0
- package/lib/module/components/Radio/Radio.js +188 -0
- package/lib/module/components/RadioButton/RadioButton.js +20 -185
- package/lib/module/components/RangeTrack/RangeTrack.js +263 -0
- package/lib/module/components/SavingsGoalSummary/SavingsGoalSummary.js +175 -0
- package/lib/module/components/SegmentedTrack/SegmentedTrack.js +166 -0
- package/lib/module/components/StatGroup/StatGroup.js +123 -0
- package/lib/module/components/StatItem/StatItem.js +66 -36
- package/lib/module/components/StrengthIndicator/StrengthIndicator.js +152 -0
- package/lib/module/components/SummaryTile/SummaryTile.js +145 -0
- package/lib/module/components/index.js +28 -1
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/icons/registry.js +1 -1
- package/lib/module/utils/index.js +2 -1
- package/lib/module/utils/number-utils.js +53 -0
- package/lib/typescript/src/components/AccordionCheckbox/AccordionCheckbox.d.ts +71 -0
- package/lib/typescript/src/components/BrandChip/BrandChip.d.ts +43 -0
- package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +79 -0
- package/lib/typescript/src/components/CardFinancialCondition/CardFinancialCondition.d.ts +50 -0
- package/lib/typescript/src/components/CardInsight/CardInsight.d.ts +48 -0
- package/lib/typescript/src/components/CheckboxGroup/CheckboxGroup.d.ts +41 -0
- package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +56 -0
- package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +11 -1
- package/lib/typescript/src/components/CoverageBarComparison/CoverageBarComparison.d.ts +105 -0
- package/lib/typescript/src/components/CoverageRing/CoverageRing.d.ts +90 -0
- package/lib/typescript/src/components/DonutChart/DonutChart.d.ts +117 -0
- package/lib/typescript/src/components/DonutChartSummary/DonutChartSummary.d.ts +103 -0
- package/lib/typescript/src/components/InstitutionBadge/InstitutionBadge.d.ts +30 -0
- package/lib/typescript/src/components/LinearProgress/LinearProgress.d.ts +17 -0
- package/lib/typescript/src/components/MetricLegendItem/MetricLegendItem.d.ts +37 -0
- package/lib/typescript/src/components/MonthlyStatusGrid/MonthlyStatusGrid.d.ts +119 -0
- package/lib/typescript/src/components/OTP/OTP.d.ts +88 -2
- package/lib/typescript/src/components/ProductOverview/ProductOverview.d.ts +39 -0
- package/lib/typescript/src/components/Radio/Radio.d.ts +30 -0
- package/lib/typescript/src/components/RadioButton/RadioButton.d.ts +20 -28
- package/lib/typescript/src/components/RangeTrack/RangeTrack.d.ts +173 -0
- package/lib/typescript/src/components/SavingsGoalSummary/SavingsGoalSummary.d.ts +95 -0
- package/lib/typescript/src/components/SegmentedTrack/SegmentedTrack.d.ts +108 -0
- package/lib/typescript/src/components/StatGroup/StatGroup.d.ts +45 -0
- package/lib/typescript/src/components/StatItem/StatItem.d.ts +24 -7
- package/lib/typescript/src/components/StrengthIndicator/StrengthIndicator.d.ts +58 -0
- package/lib/typescript/src/components/SummaryTile/SummaryTile.d.ts +60 -0
- package/lib/typescript/src/components/index.d.ts +29 -2
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/lib/typescript/src/utils/index.d.ts +1 -0
- package/lib/typescript/src/utils/number-utils.d.ts +29 -0
- package/package.json +1 -1
- package/src/components/AccordionCheckbox/AccordionCheckbox.tsx +323 -0
- package/src/components/BrandChip/BrandChip.tsx +235 -0
- package/src/components/CardAdvisory/CardAdvisory.tsx +2 -2
- package/src/components/CardBankAccount/CardBankAccount.tsx +295 -0
- package/src/components/CardFinancialCondition/CardFinancialCondition.tsx +366 -0
- package/src/components/CardInsight/CardInsight.tsx +239 -0
- package/src/components/Carousel/Carousel.tsx +14 -6
- package/src/components/CheckboxGroup/CheckboxGroup.tsx +86 -0
- package/src/components/CheckboxItem/CheckboxItem.tsx +174 -0
- package/src/components/CircularProgressBar/CircularProgressBar.tsx +74 -9
- package/src/components/CoverageBarComparison/CoverageBarComparison.tsx +378 -0
- package/src/components/CoverageRing/CoverageRing.tsx +225 -0
- package/src/components/DonutChart/DonutChart.tsx +503 -0
- package/src/components/DonutChartSummary/DonutChartSummary.tsx +256 -0
- package/src/components/HoldingsCard/HoldingsCard.tsx +2 -2
- package/src/components/InstitutionBadge/InstitutionBadge.tsx +216 -0
- package/src/components/LinearMeter/LinearMeter.tsx +9 -39
- package/src/components/LinearProgress/LinearProgress.tsx +92 -0
- package/src/components/MetricLegendItem/MetricLegendItem.tsx +167 -0
- package/src/components/MonthlyStatusGrid/MonthlyStatusGrid.tsx +438 -0
- package/src/components/OTP/OTP.tsx +476 -29
- package/src/components/ProductOverview/ProductOverview.tsx +236 -0
- package/src/components/Radio/Radio.tsx +227 -0
- package/src/components/RadioButton/RadioButton.tsx +23 -225
- package/src/components/RangeTrack/RangeTrack.tsx +394 -0
- package/src/components/SavingsGoalSummary/SavingsGoalSummary.tsx +269 -0
- package/src/components/SegmentedTrack/SegmentedTrack.tsx +268 -0
- package/src/components/StatGroup/StatGroup.tsx +169 -0
- package/src/components/StatItem/StatItem.tsx +117 -40
- package/src/components/StrengthIndicator/StrengthIndicator.tsx +205 -0
- package/src/components/SummaryTile/SummaryTile.tsx +251 -0
- package/src/components/index.ts +39 -2
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/icons/registry.ts +1 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/number-utils.ts +60 -0
|
@@ -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;
|
|
@@ -1,188 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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 Radio Buttons, 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
|
-
}
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated `RadioButton` has been renamed to `Radio`.
|
|
5
|
+
*
|
|
6
|
+
* This file is kept as a backward-compatibility shim for teams that may be
|
|
7
|
+
* importing `RadioButton` directly from this deep path:
|
|
8
|
+
*
|
|
9
|
+
* import RadioButton from 'jfs-components/src/components/RadioButton/RadioButton'
|
|
10
|
+
* import { RadioButton, RadioButtonProps } from '...'
|
|
11
|
+
*
|
|
12
|
+
* The recommended public import is now:
|
|
13
|
+
*
|
|
14
|
+
* import { Radio, type RadioProps } from 'jfs-components'
|
|
15
|
+
*
|
|
16
|
+
* Going forward, this component is called `Radio`. This shim only re-exports
|
|
17
|
+
* the new `Radio` component under the old `RadioButton` names; please migrate
|
|
18
|
+
* existing usages to `Radio` at your earliest convenience.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { Radio } from '../Radio/Radio';
|
|
22
|
+
export const RadioButton = Radio;
|
|
188
23
|
export default RadioButton;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useCallback, useState } from 'react';
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider';
|
|
7
|
+
import { EMPTY_MODES } from '../../utils/react-utils';
|
|
8
|
+
import MetricLegendItem from '../MetricLegendItem/MetricLegendItem';
|
|
9
|
+
import SegmentedTrack from '../SegmentedTrack/SegmentedTrack';
|
|
10
|
+
import Tabs from '../Tabs/Tabs';
|
|
11
|
+
import TabItem from '../Tabs/TabItem';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* One row of data inside a `RangeTrack` tab.
|
|
15
|
+
*
|
|
16
|
+
* Each item drives BOTH a `SegmentedTrack` segment and the matching
|
|
17
|
+
* `MetricLegendItem` row, so the segment color and legend indicator
|
|
18
|
+
* always share the same color by construction (same `Emphasis / DataViz`
|
|
19
|
+
* cascade as the standalone `SegmentedTrack`).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* One tab inside a `RangeTrack`. Each tab carries its own `items`
|
|
24
|
+
* array, which is the single source of truth for both the
|
|
25
|
+
* `SegmentedTrack` segments AND the `MetricLegendItem` legend rows
|
|
26
|
+
* rendered for that tab.
|
|
27
|
+
*/
|
|
28
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
29
|
+
const DEFAULT_EMPHASIS_CYCLE = ['High', 'Medium', 'Low'];
|
|
30
|
+
const DEFAULT_TABS = [{
|
|
31
|
+
label: 'Tab item',
|
|
32
|
+
items: [{
|
|
33
|
+
label: 'Large cap',
|
|
34
|
+
value: 1,
|
|
35
|
+
displayValue: '25%'
|
|
36
|
+
}, {
|
|
37
|
+
label: 'Mid cap',
|
|
38
|
+
value: 1,
|
|
39
|
+
displayValue: '25%'
|
|
40
|
+
}, {
|
|
41
|
+
label: 'Small cap',
|
|
42
|
+
value: 1,
|
|
43
|
+
displayValue: '25%'
|
|
44
|
+
}]
|
|
45
|
+
}, {
|
|
46
|
+
label: 'Tab item',
|
|
47
|
+
items: [{
|
|
48
|
+
label: 'Large cap',
|
|
49
|
+
value: 1,
|
|
50
|
+
displayValue: '25%'
|
|
51
|
+
}, {
|
|
52
|
+
label: 'Mid cap',
|
|
53
|
+
value: 1,
|
|
54
|
+
displayValue: '25%'
|
|
55
|
+
}, {
|
|
56
|
+
label: 'Small cap',
|
|
57
|
+
value: 1,
|
|
58
|
+
displayValue: '25%'
|
|
59
|
+
}]
|
|
60
|
+
}, {
|
|
61
|
+
label: 'Tab item',
|
|
62
|
+
items: [{
|
|
63
|
+
label: 'Large cap',
|
|
64
|
+
value: 1,
|
|
65
|
+
displayValue: '25%'
|
|
66
|
+
}, {
|
|
67
|
+
label: 'Mid cap',
|
|
68
|
+
value: 1,
|
|
69
|
+
displayValue: '25%'
|
|
70
|
+
}, {
|
|
71
|
+
label: 'Small cap',
|
|
72
|
+
value: 1,
|
|
73
|
+
displayValue: '25%'
|
|
74
|
+
}]
|
|
75
|
+
}];
|
|
76
|
+
const defaultEmphasisFor = index => DEFAULT_EMPHASIS_CYCLE[index % DEFAULT_EMPHASIS_CYCLE.length];
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the shared color for an item. Honors any explicit `color`
|
|
80
|
+
* override, then falls back to `dataViz/bg` for the merged mode set,
|
|
81
|
+
* then to the Figma reference value.
|
|
82
|
+
*
|
|
83
|
+
* Mirrors the cascade used by `SegmentedTrack` (per-index `Emphasis /
|
|
84
|
+
* DataViz` defaults of `High` → `Medium` → `Low`, cycling) so the
|
|
85
|
+
* pre-computed color we pass to both the segment and the legend
|
|
86
|
+
* indicator matches what the `SegmentedTrack` would have computed on
|
|
87
|
+
* its own. This is what keeps segments and legend rows in sync by
|
|
88
|
+
* construction.
|
|
89
|
+
*/
|
|
90
|
+
function resolveItemColor(parentModes, item, index) {
|
|
91
|
+
if (item.color) return item.color;
|
|
92
|
+
const itemModes = {
|
|
93
|
+
...parentModes,
|
|
94
|
+
'Emphasis / DataViz': defaultEmphasisFor(index),
|
|
95
|
+
...(item.modes || {})
|
|
96
|
+
};
|
|
97
|
+
return getVariableByName('dataViz/bg', itemModes) ?? '#cea15a';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Resolve what to render in the legend row's right-side slot. The
|
|
102
|
+
* order of precedence is:
|
|
103
|
+
* 1. `item.displayValue` if explicitly provided (including `null` /
|
|
104
|
+
* `false`, which the underlying `MetricLegendItem` treats as
|
|
105
|
+
* "hide the value slot").
|
|
106
|
+
* 2. `formatValue(item.value)` when a parent-level formatter exists.
|
|
107
|
+
* 3. `undefined` — the value slot is hidden.
|
|
108
|
+
*/
|
|
109
|
+
function resolveLegendValue(item, formatValue) {
|
|
110
|
+
if (item.displayValue !== undefined) return item.displayValue;
|
|
111
|
+
if (formatValue) return formatValue(item.value);
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* `RangeTrack` pairs a tab row with a `SegmentedTrack` and a vertical
|
|
117
|
+
* stack of `MetricLegendItem` rows. Each tab carries its own `items`
|
|
118
|
+
* array which is the **single source of truth** for both the segments
|
|
119
|
+
* and the legend rows of that tab — every segment has exactly one
|
|
120
|
+
* legend row and they share the same color through the same
|
|
121
|
+
* `Emphasis / DataViz` cascade as the standalone `SegmentedTrack`.
|
|
122
|
+
*
|
|
123
|
+
* Switching tabs swaps the segments and the legend together so the
|
|
124
|
+
* two visualizations can never drift out of sync.
|
|
125
|
+
*
|
|
126
|
+
* The default 3-item layout per tab receives per-index
|
|
127
|
+
* `Emphasis / DataViz` defaults (item 1 → `High`, 2 → `Medium`, 3 →
|
|
128
|
+
* `Low`, then cycles). Override `Appearance / DataViz` on the parent
|
|
129
|
+
* `modes` to retheme the whole component, or set `modes` per item to
|
|
130
|
+
* remix.
|
|
131
|
+
*
|
|
132
|
+
* The component supports both **controlled** and **uncontrolled**
|
|
133
|
+
* tab selection — pass `activeTabIndex` + `onTabChange` for the
|
|
134
|
+
* controlled mode, or omit them and the component will manage the
|
|
135
|
+
* selection internally starting from `defaultActiveTabIndex`.
|
|
136
|
+
*
|
|
137
|
+
* @component
|
|
138
|
+
* @param {RangeTrackProps} props
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```tsx
|
|
142
|
+
* <RangeTrack
|
|
143
|
+
* tabs={[
|
|
144
|
+
* {
|
|
145
|
+
* label: 'Sectoral',
|
|
146
|
+
* items: [
|
|
147
|
+
* { label: 'Large cap', value: 50, displayValue: '50%' },
|
|
148
|
+
* { label: 'Mid cap', value: 30, displayValue: '30%' },
|
|
149
|
+
* { label: 'Small cap', value: 20, displayValue: '20%' },
|
|
150
|
+
* ],
|
|
151
|
+
* },
|
|
152
|
+
* {
|
|
153
|
+
* label: 'Geography',
|
|
154
|
+
* items: [
|
|
155
|
+
* { label: 'India', value: 70, displayValue: '70%' },
|
|
156
|
+
* { label: 'US', value: 20, displayValue: '20%' },
|
|
157
|
+
* { label: 'Other', value: 10, displayValue: '10%' },
|
|
158
|
+
* ],
|
|
159
|
+
* },
|
|
160
|
+
* ]}
|
|
161
|
+
* />
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
function RangeTrack({
|
|
165
|
+
tabs,
|
|
166
|
+
formatValue,
|
|
167
|
+
activeTabIndex,
|
|
168
|
+
defaultActiveTabIndex = 0,
|
|
169
|
+
onTabChange,
|
|
170
|
+
scrollableTabs = false,
|
|
171
|
+
modes: propModes = EMPTY_MODES,
|
|
172
|
+
style,
|
|
173
|
+
tabsStyle,
|
|
174
|
+
trackStyle,
|
|
175
|
+
legendStyle,
|
|
176
|
+
accessibilityLabel
|
|
177
|
+
}) {
|
|
178
|
+
const {
|
|
179
|
+
modes: globalModes
|
|
180
|
+
} = useTokens();
|
|
181
|
+
const modes = {
|
|
182
|
+
...globalModes,
|
|
183
|
+
...propModes
|
|
184
|
+
};
|
|
185
|
+
const resolvedTabs = tabs && tabs.length > 0 ? tabs : DEFAULT_TABS;
|
|
186
|
+
const [internalIndex, setInternalIndex] = useState(() => clampIndex(defaultActiveTabIndex, resolvedTabs.length));
|
|
187
|
+
const isControlled = activeTabIndex !== undefined;
|
|
188
|
+
const rawIndex = isControlled ? activeTabIndex : internalIndex;
|
|
189
|
+
const safeIndex = clampIndex(rawIndex, resolvedTabs.length);
|
|
190
|
+
const handleTabPress = useCallback(index => {
|
|
191
|
+
const nextTab = resolvedTabs[index];
|
|
192
|
+
if (!nextTab) return;
|
|
193
|
+
if (!isControlled) setInternalIndex(index);
|
|
194
|
+
onTabChange?.(index, nextTab);
|
|
195
|
+
}, [isControlled, onTabChange, resolvedTabs]);
|
|
196
|
+
const containerGap = getVariableByName('rangeTrack/gap', modes) ?? 28;
|
|
197
|
+
// Vertical gap between legend rows is not exposed as its own token —
|
|
198
|
+
// the Figma design uses 8px between rows, mirroring the
|
|
199
|
+
// `donutChartSummary/legend/gap` default. Keep the value in step
|
|
200
|
+
// with `DonutChartSummary` so the two summary components feel
|
|
201
|
+
// cohesive when stacked.
|
|
202
|
+
const legendRowGap = 8;
|
|
203
|
+
const activeTab = resolvedTabs[safeIndex];
|
|
204
|
+
const activeItems = activeTab?.items ?? [];
|
|
205
|
+
const segments = activeItems.map((item, index) => ({
|
|
206
|
+
key: item.key ?? `segment-${index}`,
|
|
207
|
+
value: item.value,
|
|
208
|
+
color: resolveItemColor(modes, item, index),
|
|
209
|
+
accessibilityLabel: item.accessibilityLabel
|
|
210
|
+
}));
|
|
211
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
212
|
+
accessibilityRole: "summary",
|
|
213
|
+
accessibilityLabel: accessibilityLabel,
|
|
214
|
+
style: [{
|
|
215
|
+
width: '100%',
|
|
216
|
+
flexDirection: 'column',
|
|
217
|
+
alignItems: 'flex-start',
|
|
218
|
+
gap: containerGap
|
|
219
|
+
}, style],
|
|
220
|
+
children: [/*#__PURE__*/_jsx(Tabs, {
|
|
221
|
+
modes: modes,
|
|
222
|
+
scrollable: scrollableTabs,
|
|
223
|
+
style: tabsStyle,
|
|
224
|
+
children: resolvedTabs.map((tab, index) => /*#__PURE__*/_jsx(TabItem, {
|
|
225
|
+
label: tab.label,
|
|
226
|
+
active: index === safeIndex,
|
|
227
|
+
onPress: () => handleTabPress(index),
|
|
228
|
+
accessibilityLabel: tab.accessibilityLabel ?? tab.label
|
|
229
|
+
}, tab.key ?? tab.label ?? `tab-${index}`))
|
|
230
|
+
}), /*#__PURE__*/_jsx(SegmentedTrack, {
|
|
231
|
+
modes: modes,
|
|
232
|
+
segments: segments,
|
|
233
|
+
style: trackStyle,
|
|
234
|
+
accessibilityLabel: activeTab?.accessibilityLabel ?? activeTab?.label
|
|
235
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
236
|
+
style: [{
|
|
237
|
+
width: '100%',
|
|
238
|
+
flexDirection: 'column',
|
|
239
|
+
alignItems: 'flex-start',
|
|
240
|
+
gap: legendRowGap
|
|
241
|
+
}, legendStyle],
|
|
242
|
+
children: activeItems.map((item, index) => /*#__PURE__*/_jsx(MetricLegendItem, {
|
|
243
|
+
label: item.label,
|
|
244
|
+
value: resolveLegendValue(item, formatValue),
|
|
245
|
+
indicatorColor: resolveItemColor(modes, item, index),
|
|
246
|
+
modes: modes
|
|
247
|
+
}, item.key ?? `legend-${index}`))
|
|
248
|
+
})]
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Clamp a tab index into `[0, length)`. Negative or out-of-bounds
|
|
254
|
+
* values fall back to `0` to avoid rendering an undefined tab.
|
|
255
|
+
*/
|
|
256
|
+
function clampIndex(index, length) {
|
|
257
|
+
if (length <= 0) return 0;
|
|
258
|
+
if (!Number.isFinite(index)) return 0;
|
|
259
|
+
if (index < 0) return 0;
|
|
260
|
+
if (index >= length) return length - 1;
|
|
261
|
+
return Math.floor(index);
|
|
262
|
+
}
|
|
263
|
+
export default RangeTrack;
|