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,236 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
type StyleProp,
|
|
6
|
+
type ViewStyle,
|
|
7
|
+
type TextStyle,
|
|
8
|
+
type ImageSourcePropType,
|
|
9
|
+
} from 'react-native'
|
|
10
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
11
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
12
|
+
import Image from '../Image/Image'
|
|
13
|
+
import ProductLabel from '../ProductLabel/ProductLabel'
|
|
14
|
+
|
|
15
|
+
export type ProductOverviewStat = {
|
|
16
|
+
/** The large prominent value shown on top, e.g. "995", "3%". */
|
|
17
|
+
value: string
|
|
18
|
+
/** The descriptive label rendered beneath the value, e.g. "Purity". */
|
|
19
|
+
label: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ProductOverviewProps = {
|
|
23
|
+
/** Hero image source rendered at the top of the card. */
|
|
24
|
+
imageSource?: ImageSourcePropType | string
|
|
25
|
+
/** Aspect ratio for the hero image. Defaults to the Figma frame (288:170). */
|
|
26
|
+
imageRatio?: number
|
|
27
|
+
/** Avatar source for the inline ProductLabel chip. */
|
|
28
|
+
labelImageSource?: ImageSourcePropType | string
|
|
29
|
+
/** Text shown next to the avatar in the ProductLabel chip. */
|
|
30
|
+
label?: string
|
|
31
|
+
/** The large product name, e.g. "0.5g Gold Coin". */
|
|
32
|
+
productName?: string
|
|
33
|
+
/** The short multi-line description below the product name. */
|
|
34
|
+
description?: string
|
|
35
|
+
/**
|
|
36
|
+
* Stats rendered in the bottom row. Each stat shows a large value above a
|
|
37
|
+
* smaller label. Pass an empty array (or `null`) to hide the row.
|
|
38
|
+
*/
|
|
39
|
+
stats?: ProductOverviewStat[]
|
|
40
|
+
/** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
|
|
41
|
+
modes?: Record<string, any>
|
|
42
|
+
/** Container style override. */
|
|
43
|
+
style?: StyleProp<ViewStyle>
|
|
44
|
+
/**
|
|
45
|
+
* Custom slot rendered between the description and the stats row, useful
|
|
46
|
+
* for badges, callouts, etc. Receives the same `modes` as the parent.
|
|
47
|
+
*/
|
|
48
|
+
children?: React.ReactNode
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const DEFAULT_STATS: ProductOverviewStat[] = [
|
|
52
|
+
{ value: '995', label: 'Purity' },
|
|
53
|
+
{ value: '3%', label: 'GST' },
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
const ProductOverview = ({
|
|
57
|
+
imageSource,
|
|
58
|
+
imageRatio = 288 / 170,
|
|
59
|
+
labelImageSource,
|
|
60
|
+
label = 'Gold',
|
|
61
|
+
productName = '0.5g Gold Coin',
|
|
62
|
+
description = 'Your gold is insured from our vault to you. If lost or damaged, we’ll replace it.',
|
|
63
|
+
stats = DEFAULT_STATS,
|
|
64
|
+
modes = EMPTY_MODES,
|
|
65
|
+
style,
|
|
66
|
+
children,
|
|
67
|
+
}: ProductOverviewProps) => {
|
|
68
|
+
const padding = (getVariableByName('productOverview/padding', modes) as number | null) ?? 24
|
|
69
|
+
const gap = (getVariableByName('productOverview/gap', modes) as number | null) ?? 12
|
|
70
|
+
const background =
|
|
71
|
+
(getVariableByName('productOverview/background', modes) as string | null) ?? '#ffffff'
|
|
72
|
+
|
|
73
|
+
const productNameColor =
|
|
74
|
+
(getVariableByName('productOverview/productName/color', modes) as string | null) ??
|
|
75
|
+
'#0d0d0f'
|
|
76
|
+
const productNameFontFamily =
|
|
77
|
+
(getVariableByName('productOverview/productName/fontFamily', modes) as string | null) ??
|
|
78
|
+
'JioType Var'
|
|
79
|
+
const productNameFontSize =
|
|
80
|
+
(getVariableByName('productOverview/productName/fontSize', modes) as number | null) ?? 26
|
|
81
|
+
const productNameFontWeight =
|
|
82
|
+
getVariableByName('productOverview/productName/fontWeight', modes) ?? 900
|
|
83
|
+
const productNameLineHeight =
|
|
84
|
+
(getVariableByName('productOverview/productName/lineHeight', modes) as number | null) ?? 26
|
|
85
|
+
|
|
86
|
+
const descriptionColor =
|
|
87
|
+
(getVariableByName('productOverview/description/color', modes) as string | null) ??
|
|
88
|
+
'#1a1c1f'
|
|
89
|
+
const descriptionFontFamily =
|
|
90
|
+
(getVariableByName('productOverview/description/fontFamily', modes) as string | null) ??
|
|
91
|
+
'JioType Var'
|
|
92
|
+
const descriptionFontSize =
|
|
93
|
+
(getVariableByName('productOverview/description/fontSize', modes) as number | null) ?? 14
|
|
94
|
+
const descriptionFontWeight =
|
|
95
|
+
getVariableByName('productOverview/description/fontWeight', modes) ?? 500
|
|
96
|
+
const descriptionLineHeight =
|
|
97
|
+
(getVariableByName('productOverview/description/lineHeight', modes) as number | null) ??
|
|
98
|
+
18.2
|
|
99
|
+
|
|
100
|
+
const statGap = (getVariableByName('productOverview/stat/gap', modes) as number | null) ?? 2
|
|
101
|
+
const statValueColor =
|
|
102
|
+
(getVariableByName('productOverview/stat/value/color', modes) as string | null) ??
|
|
103
|
+
'#141414'
|
|
104
|
+
const statValueFontFamily =
|
|
105
|
+
(getVariableByName('productOverview/stat/value/fontFamily', modes) as string | null) ??
|
|
106
|
+
'JioType Var'
|
|
107
|
+
const statValueFontSize =
|
|
108
|
+
(getVariableByName('productOverview/stat/value/fontSize', modes) as number | null) ?? 20
|
|
109
|
+
const statValueFontWeight =
|
|
110
|
+
getVariableByName('productOverview/stat/value/fontWeight', modes) ?? 900
|
|
111
|
+
const statValueLineHeight =
|
|
112
|
+
(getVariableByName('productOverview/stat/value/lineHeight', modes) as number | null) ?? 20
|
|
113
|
+
|
|
114
|
+
const statLabelColor = productNameColor
|
|
115
|
+
const statLabelFontFamily =
|
|
116
|
+
(getVariableByName('productOverview/stat/label/fontFamily', modes) as string | null) ??
|
|
117
|
+
'JioType Var'
|
|
118
|
+
const statLabelFontSize =
|
|
119
|
+
(getVariableByName('productOverview/stat/label/fontSize', modes) as number | null) ?? 12
|
|
120
|
+
const statLabelFontWeight =
|
|
121
|
+
getVariableByName('productOverview/stat/label/fontWeight', modes) ?? 400
|
|
122
|
+
const statLabelLineHeight =
|
|
123
|
+
(getVariableByName('productOverview/stat/label/lineHeight', modes) as number | null) ??
|
|
124
|
+
15.6
|
|
125
|
+
|
|
126
|
+
const productNameStyle: TextStyle = {
|
|
127
|
+
color: productNameColor,
|
|
128
|
+
fontFamily: productNameFontFamily,
|
|
129
|
+
fontSize: productNameFontSize,
|
|
130
|
+
fontWeight: String(productNameFontWeight) as TextStyle['fontWeight'],
|
|
131
|
+
lineHeight: productNameLineHeight,
|
|
132
|
+
textAlign: 'center',
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const descriptionStyle: TextStyle = {
|
|
136
|
+
color: descriptionColor,
|
|
137
|
+
fontFamily: descriptionFontFamily,
|
|
138
|
+
fontSize: descriptionFontSize,
|
|
139
|
+
fontWeight: String(descriptionFontWeight) as TextStyle['fontWeight'],
|
|
140
|
+
lineHeight: descriptionLineHeight,
|
|
141
|
+
textAlign: 'center',
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const statValueStyle: TextStyle = {
|
|
145
|
+
color: statValueColor,
|
|
146
|
+
fontFamily: statValueFontFamily,
|
|
147
|
+
fontSize: statValueFontSize,
|
|
148
|
+
fontWeight: String(statValueFontWeight) as TextStyle['fontWeight'],
|
|
149
|
+
lineHeight: statValueLineHeight,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const statLabelStyle: TextStyle = {
|
|
153
|
+
color: statLabelColor,
|
|
154
|
+
fontFamily: statLabelFontFamily,
|
|
155
|
+
fontSize: statLabelFontSize,
|
|
156
|
+
fontWeight: String(statLabelFontWeight) as TextStyle['fontWeight'],
|
|
157
|
+
lineHeight: statLabelLineHeight,
|
|
158
|
+
textAlign: 'center',
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const showStats = Array.isArray(stats) && stats.length > 0
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<View
|
|
165
|
+
style={[
|
|
166
|
+
{
|
|
167
|
+
backgroundColor: background,
|
|
168
|
+
padding,
|
|
169
|
+
gap,
|
|
170
|
+
alignItems: 'center',
|
|
171
|
+
width: '100%',
|
|
172
|
+
},
|
|
173
|
+
style,
|
|
174
|
+
]}
|
|
175
|
+
>
|
|
176
|
+
{imageSource != null && (
|
|
177
|
+
<Image
|
|
178
|
+
imageSource={imageSource}
|
|
179
|
+
ratio={imageRatio}
|
|
180
|
+
resizeMode="contain"
|
|
181
|
+
accessibilityElementsHidden
|
|
182
|
+
importantForAccessibility="no"
|
|
183
|
+
/>
|
|
184
|
+
)}
|
|
185
|
+
|
|
186
|
+
<ProductLabel
|
|
187
|
+
label={label}
|
|
188
|
+
{...(labelImageSource != null && { imageSource: labelImageSource })}
|
|
189
|
+
modes={modes}
|
|
190
|
+
/>
|
|
191
|
+
|
|
192
|
+
{productName ? (
|
|
193
|
+
<Text style={productNameStyle} accessibilityRole="header">
|
|
194
|
+
{productName}
|
|
195
|
+
</Text>
|
|
196
|
+
) : null}
|
|
197
|
+
|
|
198
|
+
{description ? <Text style={descriptionStyle}>{description}</Text> : null}
|
|
199
|
+
|
|
200
|
+
{children ? <>{cloneChildrenWithModes(children, modes)}</> : null}
|
|
201
|
+
|
|
202
|
+
{showStats && (
|
|
203
|
+
<View
|
|
204
|
+
style={{
|
|
205
|
+
flexDirection: 'row',
|
|
206
|
+
alignItems: 'center',
|
|
207
|
+
justifyContent: 'space-between',
|
|
208
|
+
width: '100%',
|
|
209
|
+
}}
|
|
210
|
+
>
|
|
211
|
+
{stats.map((stat, index) => (
|
|
212
|
+
<View
|
|
213
|
+
key={`${stat.label}-${index}`}
|
|
214
|
+
style={{
|
|
215
|
+
flex: 1,
|
|
216
|
+
minWidth: 0,
|
|
217
|
+
alignItems: 'center',
|
|
218
|
+
gap: statGap,
|
|
219
|
+
overflow: 'hidden',
|
|
220
|
+
}}
|
|
221
|
+
>
|
|
222
|
+
<Text style={statValueStyle} numberOfLines={1}>
|
|
223
|
+
{stat.value}
|
|
224
|
+
</Text>
|
|
225
|
+
<Text style={statLabelStyle} numberOfLines={1}>
|
|
226
|
+
{stat.label}
|
|
227
|
+
</Text>
|
|
228
|
+
</View>
|
|
229
|
+
))}
|
|
230
|
+
</View>
|
|
231
|
+
)}
|
|
232
|
+
</View>
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export default ProductOverview
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import React, { useMemo, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Pressable,
|
|
4
|
+
View,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Platform,
|
|
7
|
+
ViewStyle,
|
|
8
|
+
DimensionValue,
|
|
9
|
+
StyleProp,
|
|
10
|
+
} from 'react-native'
|
|
11
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
12
|
+
import { EMPTY_MODES } from '../../utils/react-utils'
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Props
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export interface RadioProps {
|
|
19
|
+
/**
|
|
20
|
+
* Whether the radio is selected.
|
|
21
|
+
*/
|
|
22
|
+
selected?: boolean
|
|
23
|
+
/**
|
|
24
|
+
* Whether the radio is disabled.
|
|
25
|
+
*/
|
|
26
|
+
disabled?: boolean
|
|
27
|
+
/**
|
|
28
|
+
* Function to call when the radio is pressed.
|
|
29
|
+
*/
|
|
30
|
+
onPress?: () => void
|
|
31
|
+
/**
|
|
32
|
+
* Modes object for design-token resolution.
|
|
33
|
+
*/
|
|
34
|
+
modes?: Record<string, any>
|
|
35
|
+
/**
|
|
36
|
+
* Custom style for the radio container.
|
|
37
|
+
*/
|
|
38
|
+
style?: StyleProp<ViewStyle>
|
|
39
|
+
/**
|
|
40
|
+
* Test ID for testing.
|
|
41
|
+
*/
|
|
42
|
+
testID?: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Radio
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
export function Radio({
|
|
50
|
+
selected = false,
|
|
51
|
+
disabled = false,
|
|
52
|
+
onPress,
|
|
53
|
+
modes = EMPTY_MODES,
|
|
54
|
+
style,
|
|
55
|
+
testID,
|
|
56
|
+
}: RadioProps) {
|
|
57
|
+
// ---- Refs & State ----
|
|
58
|
+
const [hovered, setHovered] = useState(false)
|
|
59
|
+
const [focused, setFocused] = useState(false)
|
|
60
|
+
const [pressed, setPressed] = useState(false)
|
|
61
|
+
|
|
62
|
+
// ---- Dimensions ----
|
|
63
|
+
const widthStr = getVariableByName('radio/width', modes) || '18'
|
|
64
|
+
const heightStr = getVariableByName('radio/height', modes) || '18'
|
|
65
|
+
const selectorSizeStr = getVariableByName('radio/selector/size', modes) || '10'
|
|
66
|
+
|
|
67
|
+
const width = parseFloat(widthStr?.toString() || '18')
|
|
68
|
+
const height = parseFloat(heightStr?.toString() || '18')
|
|
69
|
+
const selectorSize = parseFloat(selectorSizeStr?.toString() || '10')
|
|
70
|
+
|
|
71
|
+
// ---- State Logic ----
|
|
72
|
+
// Priority: Disabled -> Focused -> Hover/Pressed -> Idle
|
|
73
|
+
// Note: Design treats Active (Pressed) similar to Selected for some styles,
|
|
74
|
+
// but usually in Radios, Pressed is a transient state.
|
|
75
|
+
// We will map:
|
|
76
|
+
// - Disabled -> 'disabled'
|
|
77
|
+
// - Focused -> 'focus'
|
|
78
|
+
// - Hovered -> 'hover'
|
|
79
|
+
// - Idle -> 'idle'
|
|
80
|
+
|
|
81
|
+
// We handle `selected` as a separate dimension derived from state.
|
|
82
|
+
|
|
83
|
+
let visualState = 'idle'
|
|
84
|
+
if (disabled) {
|
|
85
|
+
visualState = 'disabled'
|
|
86
|
+
} else if (focused) {
|
|
87
|
+
visualState = 'focus'
|
|
88
|
+
} else if (hovered || pressed) {
|
|
89
|
+
visualState = 'hover'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Construct token paths based on state + selected
|
|
93
|
+
let prefix = `radio/${visualState}`
|
|
94
|
+
if (visualState === 'idle' && selected) {
|
|
95
|
+
prefix = `radio/selected`
|
|
96
|
+
} else if (selected) {
|
|
97
|
+
prefix = `radio/${visualState}Selected`
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---- Colors & Border ----
|
|
101
|
+
|
|
102
|
+
const resolveColor = (path: string, fallback: string) => {
|
|
103
|
+
return getVariableByName(path, modes) || fallback
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Background Color
|
|
107
|
+
let bgColorVar = `${prefix}/background/color`
|
|
108
|
+
// Fix for disabledSelected weirdness if needed
|
|
109
|
+
if (visualState === 'disabled' && selected) {
|
|
110
|
+
// Check specific path from dump: `radio/disabledSelected/background`
|
|
111
|
+
if (!getVariableByName(`${prefix}/background/color`, modes)) {
|
|
112
|
+
bgColorVar = `${prefix}/background`
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Border Color
|
|
117
|
+
let borderColorVar = `${prefix}/border/color`
|
|
118
|
+
|
|
119
|
+
// Border Width
|
|
120
|
+
let borderWidthVar = `${prefix}/border/size`
|
|
121
|
+
// Fix for huge path: `radio/disabled/radio/disabled/border/size`
|
|
122
|
+
if (visualState === 'disabled' && !selected) {
|
|
123
|
+
if (getVariableByName('radio/disabled/radio/disabled/border/size', modes)) {
|
|
124
|
+
borderWidthVar = 'radio/disabled/radio/disabled/border/size'
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Selector Color
|
|
129
|
+
let selectorBgVar = `${prefix}/selector/background/color`
|
|
130
|
+
if (!selected) {
|
|
131
|
+
selectorBgVar = 'transparent'
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Shadows (Glow)
|
|
135
|
+
let shadowSizeVar = `${prefix}/boxShadow/size`
|
|
136
|
+
let shadowColorVar = `${prefix}/shadow/color`
|
|
137
|
+
|
|
138
|
+
// Resolve Values
|
|
139
|
+
const backgroundColor = resolveColor(bgColorVar, 'transparent')
|
|
140
|
+
const borderColor = resolveColor(borderColorVar, 'transparent')
|
|
141
|
+
const borderWidth = parseFloat(getVariableByName(borderWidthVar, modes)?.toString() || '1')
|
|
142
|
+
const selectorColor = resolveColor(selectorBgVar, 'transparent')
|
|
143
|
+
|
|
144
|
+
const shadowSize = parseFloat(getVariableByName(shadowSizeVar, modes)?.toString() || '0')
|
|
145
|
+
const shadowColor = resolveColor(shadowColorVar, 'transparent')
|
|
146
|
+
|
|
147
|
+
// Styles
|
|
148
|
+
const containerStyle: any = {
|
|
149
|
+
width,
|
|
150
|
+
height,
|
|
151
|
+
borderRadius: width / 2, // 9999px -> circle
|
|
152
|
+
borderWidth,
|
|
153
|
+
borderColor,
|
|
154
|
+
backgroundColor,
|
|
155
|
+
justifyContent: 'center',
|
|
156
|
+
alignItems: 'center',
|
|
157
|
+
// Web shadow (ring)
|
|
158
|
+
...(Platform.OS === 'web' && shadowSize > 0 ? {
|
|
159
|
+
boxShadow: `0px 0px 0px ${shadowSize}px ${shadowColor}`,
|
|
160
|
+
} : {}),
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const selectorStyle: ViewStyle = {
|
|
164
|
+
width: selectorSize,
|
|
165
|
+
height: selectorSize,
|
|
166
|
+
borderRadius: selectorSize / 2,
|
|
167
|
+
backgroundColor: selectorColor,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Dummy block for token extraction (static analysis)
|
|
171
|
+
if (false as boolean) {
|
|
172
|
+
getVariableByName('radio/idle/background/color')
|
|
173
|
+
getVariableByName('radio/idle/border/color')
|
|
174
|
+
getVariableByName('radio/selector/size')
|
|
175
|
+
getVariableByName('radio/width')
|
|
176
|
+
getVariableByName('radio/height')
|
|
177
|
+
getVariableByName('radio/background/color')
|
|
178
|
+
getVariableByName('radio/hover/background/color')
|
|
179
|
+
getVariableByName('radio/hover/border/color')
|
|
180
|
+
getVariableByName('radio/hover/boxShadow/size')
|
|
181
|
+
getVariableByName('radio/hover/shadow/color')
|
|
182
|
+
getVariableByName('radio/selected/background/color')
|
|
183
|
+
getVariableByName('radio/selected/border/color')
|
|
184
|
+
getVariableByName('radio/selected/selector/background/color')
|
|
185
|
+
getVariableByName('radio/hoverSelected/background/color')
|
|
186
|
+
getVariableByName('radio/hoverSelected/border/color')
|
|
187
|
+
getVariableByName('radio/hoverSelected/boxShadow/size')
|
|
188
|
+
getVariableByName('radio/hoverSelected/shadow/color')
|
|
189
|
+
getVariableByName('radio/hoverSelected/selector/background/color')
|
|
190
|
+
getVariableByName('radio/focus/background/color')
|
|
191
|
+
getVariableByName('radio/focus/border/color')
|
|
192
|
+
getVariableByName('radio/focus/border/size')
|
|
193
|
+
getVariableByName('radio/focus/boxShadow/size')
|
|
194
|
+
getVariableByName('radio/focus/shadow/color')
|
|
195
|
+
getVariableByName('radio/focusSelected/background/color')
|
|
196
|
+
getVariableByName('radio/focusSelected/selector/background/color')
|
|
197
|
+
getVariableByName('radio/focusSelected/border/size')
|
|
198
|
+
getVariableByName('radio/disabled/radio/disabled/border/size')
|
|
199
|
+
getVariableByName('radio/disabled/background/color')
|
|
200
|
+
getVariableByName('radio/disabled/border/color')
|
|
201
|
+
getVariableByName('radio/disabledSelected/selector/background/color')
|
|
202
|
+
getVariableByName('radio/disabledSelected/background')
|
|
203
|
+
getVariableByName('radio/disabledSelected/border/color')
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<Pressable
|
|
208
|
+
testID={testID}
|
|
209
|
+
disabled={disabled}
|
|
210
|
+
onPress={onPress}
|
|
211
|
+
onHoverIn={() => setHovered(true)}
|
|
212
|
+
onHoverOut={() => setHovered(false)}
|
|
213
|
+
onFocus={() => setFocused(true)}
|
|
214
|
+
onBlur={() => setFocused(false)}
|
|
215
|
+
onPressIn={() => setPressed(true)}
|
|
216
|
+
onPressOut={() => setPressed(false)}
|
|
217
|
+
style={({ pressed: isPressed }) => [
|
|
218
|
+
containerStyle,
|
|
219
|
+
style,
|
|
220
|
+
]}
|
|
221
|
+
>
|
|
222
|
+
<View style={selectorStyle} />
|
|
223
|
+
</Pressable>
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export default Radio
|