jfs-components 0.0.72 → 0.0.74
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 +28 -0
- package/lib/commonjs/components/AccordionCheckbox/AccordionCheckbox.js +239 -0
- package/lib/commonjs/components/AccountCard/AccountCard.js +247 -0
- package/lib/commonjs/components/AppBar/AppBar.js +17 -11
- package/lib/commonjs/components/BrandChip/BrandChip.js +149 -0
- package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +229 -0
- package/lib/commonjs/components/CardInsight/CardInsight.js +166 -0
- package/lib/commonjs/components/CheckboxGroup/CheckboxGroup.js +67 -0
- package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +140 -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/Dropdown/Dropdown.js +214 -0
- package/lib/commonjs/components/DropdownInput/DropdownInput.js +542 -0
- package/lib/commonjs/components/FormField/FormField.js +328 -178
- package/lib/commonjs/components/LinearMeter/LinearMeter.js +9 -28
- package/lib/commonjs/components/LinearProgress/LinearProgress.js +68 -0
- package/lib/commonjs/components/LottieIntroBlock/LottieIntroBlock.js +150 -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/PageHero/PageHero.js +153 -0
- package/lib/commonjs/components/PoweredByLabel/PoweredByLabel.js +135 -0
- package/lib/commonjs/components/PoweredByLabel/finvu.png +0 -0
- package/lib/commonjs/components/ProductOverview/ProductOverview.js +147 -0
- 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/Text/Text.js +9 -2
- package/lib/commonjs/components/Tooltip/Tooltip.js +34 -27
- package/lib/commonjs/components/index.js +231 -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/AccountCard/AccountCard.js +241 -0
- package/lib/module/components/AppBar/AppBar.js +17 -11
- package/lib/module/components/BrandChip/BrandChip.js +143 -0
- package/lib/module/components/CardBankAccount/CardBankAccount.js +223 -0
- package/lib/module/components/CardInsight/CardInsight.js +161 -0
- package/lib/module/components/CheckboxGroup/CheckboxGroup.js +62 -0
- package/lib/module/components/CheckboxItem/CheckboxItem.js +134 -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/Dropdown/Dropdown.js +206 -0
- package/lib/module/components/DropdownInput/DropdownInput.js +536 -0
- package/lib/module/components/FormField/FormField.js +330 -180
- package/lib/module/components/LinearMeter/LinearMeter.js +9 -28
- package/lib/module/components/LinearProgress/LinearProgress.js +63 -0
- package/lib/module/components/LottieIntroBlock/LottieIntroBlock.js +144 -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/PageHero/PageHero.js +147 -0
- package/lib/module/components/PoweredByLabel/PoweredByLabel.js +130 -0
- package/lib/module/components/PoweredByLabel/finvu.png +0 -0
- package/lib/module/components/ProductOverview/ProductOverview.js +142 -0
- 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/Text/Text.js +9 -2
- package/lib/module/components/Tooltip/Tooltip.js +34 -27
- package/lib/module/components/index.js +28 -2
- 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/AccountCard/AccountCard.d.ts +81 -0
- package/lib/typescript/src/components/BrandChip/BrandChip.d.ts +43 -0
- package/lib/typescript/src/components/CardBankAccount/CardBankAccount.d.ts +86 -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 +72 -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/Dropdown/Dropdown.d.ts +62 -0
- package/lib/typescript/src/components/DropdownInput/DropdownInput.d.ts +107 -0
- package/lib/typescript/src/components/FormField/FormField.d.ts +76 -19
- package/lib/typescript/src/components/LinearProgress/LinearProgress.d.ts +17 -0
- package/lib/typescript/src/components/LottieIntroBlock/LottieIntroBlock.d.ts +58 -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/PageHero/PageHero.d.ts +53 -0
- package/lib/typescript/src/components/PoweredByLabel/PoweredByLabel.d.ts +70 -0
- package/lib/typescript/src/components/ProductOverview/ProductOverview.d.ts +39 -0
- 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/Text/Text.d.ts +12 -2
- package/lib/typescript/src/components/Tooltip/Tooltip.d.ts +13 -2
- package/lib/typescript/src/components/index.d.ts +29 -3
- 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 -3
- package/src/components/AccordionCheckbox/AccordionCheckbox.tsx +323 -0
- package/src/components/AccountCard/AccountCard.tsx +376 -0
- package/src/components/AppBar/AppBar.tsx +25 -14
- package/src/components/BrandChip/BrandChip.tsx +235 -0
- package/src/components/CardBankAccount/CardBankAccount.tsx +321 -0
- package/src/components/CardInsight/CardInsight.tsx +239 -0
- package/src/components/CheckboxGroup/CheckboxGroup.tsx +86 -0
- package/src/components/CheckboxItem/CheckboxItem.tsx +209 -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/Dropdown/Dropdown.tsx +331 -0
- package/src/components/DropdownInput/DropdownInput.tsx +819 -0
- package/src/components/FormField/FormField.tsx +542 -215
- package/src/components/LinearMeter/LinearMeter.tsx +9 -39
- package/src/components/LinearProgress/LinearProgress.tsx +92 -0
- package/src/components/LottieIntroBlock/LottieIntroBlock.tsx +202 -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/PageHero/PageHero.tsx +200 -0
- package/src/components/PoweredByLabel/PoweredByLabel.tsx +221 -0
- package/src/components/PoweredByLabel/finvu.png +0 -0
- package/src/components/ProductOverview/ProductOverview.tsx +236 -0
- 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/Text/Text.tsx +24 -3
- package/src/components/Tooltip/Tooltip.tsx +50 -25
- package/src/components/index.ts +47 -3
- 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,205 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, type StyleProp, type ViewStyle } from 'react-native'
|
|
3
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
|
+
import { EMPTY_MODES } from '../../utils/react-utils'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Confidence levels supported by the indicator. Mirrors the Figma `Confidence`
|
|
8
|
+
* mode collection exactly (`High` | `Medium` | `Low` | `None`).
|
|
9
|
+
*/
|
|
10
|
+
export type StrengthIndicatorConfidence = 'High' | 'Medium' | 'Low' | 'None'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Accepted shape for the `confidence` prop. Either:
|
|
14
|
+
* - one of the named levels (`'High' | 'Medium' | 'Low' | 'None'`), or
|
|
15
|
+
* - a float in `[0, 1]`. Floats are bucketed to the nearest level using
|
|
16
|
+
* midpoint thresholds (1/6, 1/2, 5/6) so anchor values land squarely:
|
|
17
|
+
* `0` → `None`, `~0.33` → `Low`, `~0.66` → `Medium`, `1` → `High`.
|
|
18
|
+
* Values outside `[0, 1]` are clamped.
|
|
19
|
+
*/
|
|
20
|
+
export type StrengthIndicatorConfidenceValue =
|
|
21
|
+
| StrengthIndicatorConfidence
|
|
22
|
+
| number
|
|
23
|
+
|
|
24
|
+
export type StrengthIndicatorProps = {
|
|
25
|
+
/**
|
|
26
|
+
* Overall confidence level. Accepts either the named enum value
|
|
27
|
+
* (`'High' | 'Medium' | 'Low' | 'None'`) or a float in `[0, 1]`:
|
|
28
|
+
*
|
|
29
|
+
* `1.0` (or `'High'`) → bars `[High, High, High]` (3/3 lit)
|
|
30
|
+
* `0.66` (or `'Medium'`) → bars `[Medium, Medium, None]` (2/3 lit)
|
|
31
|
+
* `0.33` (or `'Low'`) → bars `[Low, None, None]` (1/3 lit)
|
|
32
|
+
* `0.0` (or `'None'`) → bars `[None, None, None]` (0/3 lit)
|
|
33
|
+
*
|
|
34
|
+
* The component maps the resolved level to a per-bar confidence mode
|
|
35
|
+
* internally, so each bar's color resolves through the
|
|
36
|
+
* `strengthIndicator/bar/<size>/background` token using its own
|
|
37
|
+
* `Confidence` mode.
|
|
38
|
+
*
|
|
39
|
+
* Default: `'High'`.
|
|
40
|
+
*/
|
|
41
|
+
confidence?: StrengthIndicatorConfidenceValue
|
|
42
|
+
/** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
|
|
43
|
+
modes?: Record<string, any>
|
|
44
|
+
/** Override container styles. */
|
|
45
|
+
style?: StyleProp<ViewStyle>
|
|
46
|
+
} & Omit<React.ComponentProps<typeof View>, 'style'>
|
|
47
|
+
|
|
48
|
+
type BarSize = 'small' | 'medium' | 'large'
|
|
49
|
+
|
|
50
|
+
const BAR_SIZES: BarSize[] = ['small', 'medium', 'large']
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Maps an overall confidence level to per-bar confidence modes. Bars are
|
|
54
|
+
* filled from the smallest (left) to the largest (right) as confidence
|
|
55
|
+
* rises, with each lit bar tinted at its own confidence level so the
|
|
56
|
+
* indicator visually communicates *which* level the value is at — not just
|
|
57
|
+
* how many bars are lit.
|
|
58
|
+
*
|
|
59
|
+
* High -> [High, High, High] (3/3 lit, bright)
|
|
60
|
+
* Medium -> [Medium, Medium, None] (2/3 lit, mid intensity)
|
|
61
|
+
* Low -> [Low, None, None] (1/3 lit, low intensity)
|
|
62
|
+
* None -> [None, None, None] (0/3 lit)
|
|
63
|
+
*
|
|
64
|
+
* Each bar resolves its color through `getVariableByName` with its own
|
|
65
|
+
* per-bar `Confidence` mode, so the mapping survives any token updates in
|
|
66
|
+
* the Figma variables export.
|
|
67
|
+
*/
|
|
68
|
+
const CONFIDENCE_TO_BARS: Record<
|
|
69
|
+
StrengthIndicatorConfidence,
|
|
70
|
+
[StrengthIndicatorConfidence, StrengthIndicatorConfidence, StrengthIndicatorConfidence]
|
|
71
|
+
> = {
|
|
72
|
+
High: ['High', 'High', 'High'],
|
|
73
|
+
Medium: ['Medium', 'Medium', 'None'],
|
|
74
|
+
Low: ['Low', 'None', 'None'],
|
|
75
|
+
None: ['None', 'None', 'None'],
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Fallback colors used when the `strengthIndicator/*` design tokens have not
|
|
80
|
+
* yet been imported into `Coin Variables-variables-full.json`. The `High`
|
|
81
|
+
* fallback matches the Figma export (`#25ab21`); the intermediate levels
|
|
82
|
+
* use progressively lighter greens, and `None` uses a neutral gray for
|
|
83
|
+
* unlit bars. Once the tokens land, `getVariableByName` returns the
|
|
84
|
+
* canonical value and these fallbacks are bypassed.
|
|
85
|
+
*/
|
|
86
|
+
const FALLBACK_BAR_COLOR: Record<StrengthIndicatorConfidence, string> = {
|
|
87
|
+
High: '#25ab21',
|
|
88
|
+
Medium: '#5cc257',
|
|
89
|
+
Low: '#a8dba2',
|
|
90
|
+
None: '#d9d9dd',
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Bucket a float `[0, 1]` into the nearest named confidence level using
|
|
95
|
+
* midpoint thresholds between the four anchor values (`0`, `1/3`, `2/3`,
|
|
96
|
+
* `1`). Out-of-range values are clamped.
|
|
97
|
+
*/
|
|
98
|
+
function floatToConfidence(value: number): StrengthIndicatorConfidence {
|
|
99
|
+
const clamped = Math.min(Math.max(value, 0), 1)
|
|
100
|
+
if (clamped > 5 / 6) return 'High'
|
|
101
|
+
if (clamped > 1 / 2) return 'Medium'
|
|
102
|
+
if (clamped > 1 / 6) return 'Low'
|
|
103
|
+
return 'None'
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Normalize the union `confidence` prop into a single named level.
|
|
108
|
+
* Strings pass through; numbers are bucketed via {@link floatToConfidence}.
|
|
109
|
+
* Anything else (e.g. `null`/`undefined`) defaults to `'High'` to match
|
|
110
|
+
* Figma's default mode.
|
|
111
|
+
*/
|
|
112
|
+
function resolveConfidence(
|
|
113
|
+
value: StrengthIndicatorConfidenceValue | undefined
|
|
114
|
+
): StrengthIndicatorConfidence {
|
|
115
|
+
if (typeof value === 'number') return floatToConfidence(value)
|
|
116
|
+
if (value === 'High' || value === 'Medium' || value === 'Low' || value === 'None') {
|
|
117
|
+
return value
|
|
118
|
+
}
|
|
119
|
+
return 'High'
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* StrengthIndicator renders three fixed-size, ascending bars that
|
|
124
|
+
* communicate a discrete confidence/strength level — typically used to
|
|
125
|
+
* signal model confidence, signal quality, or password strength.
|
|
126
|
+
*
|
|
127
|
+
* Sizes are intentionally fixed (matching the Figma spec): 3px-wide bars
|
|
128
|
+
* 6/9/12px tall with a 2px gap and 2px container padding.
|
|
129
|
+
*
|
|
130
|
+
* Pass a single `confidence` value (named enum *or* float `[0, 1]`); the
|
|
131
|
+
* component normalizes it to a level, maps that level to a per-bar
|
|
132
|
+
* confidence mode, and resolves each bar's color through design tokens
|
|
133
|
+
* (`strengthIndicator/bar/<size>/background`).
|
|
134
|
+
*
|
|
135
|
+
* @component
|
|
136
|
+
* @param {StrengthIndicatorProps} props
|
|
137
|
+
*/
|
|
138
|
+
function StrengthIndicator({
|
|
139
|
+
confidence = 'High',
|
|
140
|
+
modes = EMPTY_MODES,
|
|
141
|
+
style,
|
|
142
|
+
...rest
|
|
143
|
+
}: StrengthIndicatorProps) {
|
|
144
|
+
const containerGap =
|
|
145
|
+
(getVariableByName('strengthIndicator/container/gap', modes) as number | null) ?? 2
|
|
146
|
+
const containerPadding =
|
|
147
|
+
(getVariableByName('strengthIndicator/container/padding', modes) as number | null) ?? 2
|
|
148
|
+
const barWidth =
|
|
149
|
+
(getVariableByName('strengthIndicator/bar/width', modes) as number | null) ?? 3
|
|
150
|
+
const barRadius =
|
|
151
|
+
(getVariableByName('strengthIndicator/bar/radius', modes) as number | null) ?? 1
|
|
152
|
+
|
|
153
|
+
const barHeights: Record<BarSize, number> = {
|
|
154
|
+
small:
|
|
155
|
+
(getVariableByName('strengthIndicator/bar/height/small', modes) as number | null) ?? 6,
|
|
156
|
+
medium:
|
|
157
|
+
(getVariableByName('strengthIndicator/bar/height/medium', modes) as number | null) ?? 9,
|
|
158
|
+
large:
|
|
159
|
+
(getVariableByName('strengthIndicator/bar/height/large', modes) as number | null) ?? 12,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const resolvedLevel = resolveConfidence(confidence)
|
|
163
|
+
const perBarConfidence = CONFIDENCE_TO_BARS[resolvedLevel]
|
|
164
|
+
|
|
165
|
+
const containerStyle: ViewStyle = {
|
|
166
|
+
flexDirection: 'row',
|
|
167
|
+
alignItems: 'flex-end',
|
|
168
|
+
gap: containerGap,
|
|
169
|
+
padding: containerPadding,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<View
|
|
174
|
+
style={[containerStyle, style]}
|
|
175
|
+
accessibilityRole="image"
|
|
176
|
+
accessibilityLabel={`Strength indicator: ${resolvedLevel} confidence`}
|
|
177
|
+
{...rest}
|
|
178
|
+
>
|
|
179
|
+
{BAR_SIZES.map((size, index) => {
|
|
180
|
+
const barConfidence: StrengthIndicatorConfidence =
|
|
181
|
+
perBarConfidence[index] ?? 'None'
|
|
182
|
+
const barModes = { ...modes, Confidence: barConfidence }
|
|
183
|
+
const tokenColor = getVariableByName(
|
|
184
|
+
`strengthIndicator/bar/${size}/background`,
|
|
185
|
+
barModes
|
|
186
|
+
) as string | null
|
|
187
|
+
const backgroundColor = tokenColor ?? FALLBACK_BAR_COLOR[barConfidence]
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<View
|
|
191
|
+
key={size}
|
|
192
|
+
style={{
|
|
193
|
+
width: barWidth,
|
|
194
|
+
height: barHeights[size],
|
|
195
|
+
borderRadius: barRadius,
|
|
196
|
+
backgroundColor,
|
|
197
|
+
}}
|
|
198
|
+
/>
|
|
199
|
+
)
|
|
200
|
+
})}
|
|
201
|
+
</View>
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export default StrengthIndicator
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
Pressable,
|
|
6
|
+
type StyleProp,
|
|
7
|
+
type ViewStyle,
|
|
8
|
+
type TextStyle,
|
|
9
|
+
type PressableStateCallbackType,
|
|
10
|
+
} from 'react-native'
|
|
11
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
12
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
13
|
+
import Button from '../Button/Button'
|
|
14
|
+
import NavArrow from '../NavArrow/NavArrow'
|
|
15
|
+
import StrengthIndicator, {
|
|
16
|
+
type StrengthIndicatorConfidenceValue,
|
|
17
|
+
} from '../StrengthIndicator/StrengthIndicator'
|
|
18
|
+
|
|
19
|
+
export type SummaryTileProps = {
|
|
20
|
+
/** Bold heading shown at the top-left of the tile. */
|
|
21
|
+
title?: string
|
|
22
|
+
/** Smaller supporting copy rendered beneath the title. */
|
|
23
|
+
description?: string
|
|
24
|
+
/**
|
|
25
|
+
* Confidence level for the inline `StrengthIndicator` next to the title.
|
|
26
|
+
* Defaults to `'None'` to match the Figma reference (all bars unlit).
|
|
27
|
+
* Pass `null` to hide the indicator entirely.
|
|
28
|
+
*/
|
|
29
|
+
confidence?: StrengthIndicatorConfidenceValue | null
|
|
30
|
+
/**
|
|
31
|
+
* When `true`, render a trailing forward chevron (`NavArrow`) — the
|
|
32
|
+
* convention for "tap the tile to drill in" affordance. Defaults to
|
|
33
|
+
* `false`.
|
|
34
|
+
*/
|
|
35
|
+
chevron?: boolean
|
|
36
|
+
/**
|
|
37
|
+
* Action slot rendered to the right of the text content. Defaults to a
|
|
38
|
+
* small `Button` (`Button / Size: 'S'`) labelled "Button" to match the
|
|
39
|
+
* Figma default. Pass `null` to render no action slot (typical for
|
|
40
|
+
* static tiles that want only a chevron).
|
|
41
|
+
*/
|
|
42
|
+
children?: React.ReactNode
|
|
43
|
+
/**
|
|
44
|
+
* Optional press handler for the entire tile. When provided, the tile
|
|
45
|
+
* is wrapped in a `Pressable`, gets `accessibilityRole="button"` and
|
|
46
|
+
* the default `Button` action slot is automatically suppressed so the
|
|
47
|
+
* rendered HTML doesn't contain a `<button>` inside another `<button>`
|
|
48
|
+
* (which would trigger a hydration warning on web). Pair this with
|
|
49
|
+
* `chevron` for the navigational tile pattern.
|
|
50
|
+
*/
|
|
51
|
+
onPress?: () => void
|
|
52
|
+
/** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
|
|
53
|
+
modes?: Record<string, any>
|
|
54
|
+
/** Override container styles. */
|
|
55
|
+
style?: StyleProp<ViewStyle>
|
|
56
|
+
/** Override the title text styles. */
|
|
57
|
+
titleStyle?: StyleProp<TextStyle>
|
|
58
|
+
/** Override the description text styles. */
|
|
59
|
+
descriptionStyle?: StyleProp<TextStyle>
|
|
60
|
+
/** Accessibility label override (defaults to `title`). */
|
|
61
|
+
accessibilityLabel?: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* SummaryTile composes a title (with an inline `StrengthIndicator`), a
|
|
66
|
+
* supporting description and an action slot into a compact horizontal row,
|
|
67
|
+
* commonly used at the top of dashboard cards (e.g. "Spending — know your
|
|
68
|
+
* spending and savings"). An optional trailing chevron and tile-level
|
|
69
|
+
* `onPress` handler turn the whole row into a navigational entry point.
|
|
70
|
+
*
|
|
71
|
+
* @component
|
|
72
|
+
* @param {SummaryTileProps} props
|
|
73
|
+
*/
|
|
74
|
+
function SummaryTile({
|
|
75
|
+
title = 'Spending',
|
|
76
|
+
description = 'Know your spending and savings',
|
|
77
|
+
confidence = 'None',
|
|
78
|
+
chevron = false,
|
|
79
|
+
children,
|
|
80
|
+
onPress,
|
|
81
|
+
modes = EMPTY_MODES,
|
|
82
|
+
style,
|
|
83
|
+
titleStyle,
|
|
84
|
+
descriptionStyle,
|
|
85
|
+
accessibilityLabel,
|
|
86
|
+
}: SummaryTileProps) {
|
|
87
|
+
const containerGap =
|
|
88
|
+
(getVariableByName('summaryTile/gap', modes) as number | null) ?? 8
|
|
89
|
+
const containerPadding =
|
|
90
|
+
(getVariableByName('summaryTile/padding', modes) as number | null) ?? 0
|
|
91
|
+
|
|
92
|
+
const contentGap =
|
|
93
|
+
(getVariableByName('summaryTile/content/gap', modes) as number | null) ?? 8
|
|
94
|
+
const titleWrapGap =
|
|
95
|
+
(getVariableByName('summaryTile/titleWrap/gap', modes) as number | null) ?? 4
|
|
96
|
+
|
|
97
|
+
const titleColor =
|
|
98
|
+
(getVariableByName('summaryTile/title/color', modes) as string | null) ?? '#0d0d0f'
|
|
99
|
+
const titleFontFamily =
|
|
100
|
+
(getVariableByName('summaryTile/title/fontFamily', modes) as string | null) ??
|
|
101
|
+
'JioType Var'
|
|
102
|
+
const titleFontSize =
|
|
103
|
+
(getVariableByName('summaryTile/title/fontSize', modes) as number | null) ?? 16
|
|
104
|
+
const titleFontWeight =
|
|
105
|
+
getVariableByName('summaryTile/title/fontWeight', modes) ?? 700
|
|
106
|
+
const titleLineHeight =
|
|
107
|
+
(getVariableByName('summaryTile/title/lineHeight', modes) as number | null) ?? 18
|
|
108
|
+
|
|
109
|
+
const descriptionColor =
|
|
110
|
+
(getVariableByName('summaryTile/description/color', modes) as string | null) ??
|
|
111
|
+
'#24262b'
|
|
112
|
+
const descriptionFontFamily =
|
|
113
|
+
(getVariableByName('summaryTile/description/fontFamily', modes) as string | null) ??
|
|
114
|
+
'JioType Var'
|
|
115
|
+
const descriptionFontSize =
|
|
116
|
+
(getVariableByName('summaryTile/description/fontSize', modes) as number | null) ?? 12
|
|
117
|
+
const descriptionFontWeight =
|
|
118
|
+
getVariableByName('summaryTile/description/fontWeight', modes) ?? 500
|
|
119
|
+
const descriptionLineHeight =
|
|
120
|
+
(getVariableByName('summaryTile/description/lineHeight', modes) as number | null) ?? 16
|
|
121
|
+
|
|
122
|
+
const titleTextStyle: TextStyle = {
|
|
123
|
+
color: titleColor,
|
|
124
|
+
fontFamily: titleFontFamily,
|
|
125
|
+
fontSize: titleFontSize,
|
|
126
|
+
fontWeight: String(titleFontWeight) as TextStyle['fontWeight'],
|
|
127
|
+
lineHeight: titleLineHeight,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const descriptionTextStyle: TextStyle = {
|
|
131
|
+
color: descriptionColor,
|
|
132
|
+
fontFamily: descriptionFontFamily,
|
|
133
|
+
fontSize: descriptionFontSize,
|
|
134
|
+
fontWeight: String(descriptionFontWeight) as TextStyle['fontWeight'],
|
|
135
|
+
lineHeight: descriptionLineHeight,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const showIndicator = confidence !== null
|
|
139
|
+
|
|
140
|
+
// When the entire tile is pressable, the default `Button` slot is
|
|
141
|
+
// suppressed so we don't render a `<button>` inside another `<button>`
|
|
142
|
+
// (which is invalid HTML and triggers a React hydration warning on
|
|
143
|
+
// web). Consumers can still pass an explicit non-button node via
|
|
144
|
+
// `children` if they want a visible affordance alongside `onPress`.
|
|
145
|
+
const isPressable = onPress != null
|
|
146
|
+
|
|
147
|
+
const actionSlot =
|
|
148
|
+
children !== undefined ? (
|
|
149
|
+
children !== null ? (
|
|
150
|
+
<>{cloneChildrenWithModes(children, modes)}</>
|
|
151
|
+
) : null
|
|
152
|
+
) : isPressable ? null : (
|
|
153
|
+
<Button
|
|
154
|
+
label="Button"
|
|
155
|
+
modes={{'AppearanceBrand': 'Secondary', ...modes, 'Button / Size': 'S' }}
|
|
156
|
+
/>
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
const tileContent = (
|
|
160
|
+
<>
|
|
161
|
+
<View
|
|
162
|
+
style={{
|
|
163
|
+
flex: 1,
|
|
164
|
+
minWidth: 0,
|
|
165
|
+
flexDirection: 'column',
|
|
166
|
+
gap: contentGap,
|
|
167
|
+
alignItems: 'flex-start',
|
|
168
|
+
}}
|
|
169
|
+
>
|
|
170
|
+
<View
|
|
171
|
+
style={{
|
|
172
|
+
flexDirection: 'row',
|
|
173
|
+
alignItems: 'center',
|
|
174
|
+
gap: titleWrapGap,
|
|
175
|
+
width: '100%',
|
|
176
|
+
}}
|
|
177
|
+
>
|
|
178
|
+
{title ? (
|
|
179
|
+
<Text
|
|
180
|
+
style={[titleTextStyle, titleStyle]}
|
|
181
|
+
numberOfLines={1}
|
|
182
|
+
>
|
|
183
|
+
{title}
|
|
184
|
+
</Text>
|
|
185
|
+
) : null}
|
|
186
|
+
{showIndicator ? (
|
|
187
|
+
<StrengthIndicator
|
|
188
|
+
confidence={confidence as StrengthIndicatorConfidenceValue}
|
|
189
|
+
modes={modes}
|
|
190
|
+
/>
|
|
191
|
+
) : null}
|
|
192
|
+
</View>
|
|
193
|
+
|
|
194
|
+
{description ? (
|
|
195
|
+
<Text style={[descriptionTextStyle, descriptionStyle]}>
|
|
196
|
+
{description}
|
|
197
|
+
</Text>
|
|
198
|
+
) : null}
|
|
199
|
+
</View>
|
|
200
|
+
|
|
201
|
+
{actionSlot != null ? (
|
|
202
|
+
<View
|
|
203
|
+
style={{
|
|
204
|
+
flexDirection: 'row',
|
|
205
|
+
alignItems: 'center',
|
|
206
|
+
}}
|
|
207
|
+
>
|
|
208
|
+
{actionSlot}
|
|
209
|
+
</View>
|
|
210
|
+
) : null}
|
|
211
|
+
|
|
212
|
+
{chevron ? <NavArrow direction="Forward" modes={modes} /> : null}
|
|
213
|
+
</>
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
const containerLayoutStyle: ViewStyle = {
|
|
217
|
+
flexDirection: 'row',
|
|
218
|
+
alignItems: 'center',
|
|
219
|
+
gap: containerGap,
|
|
220
|
+
padding: containerPadding,
|
|
221
|
+
width: '100%',
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (onPress) {
|
|
225
|
+
return (
|
|
226
|
+
<Pressable
|
|
227
|
+
onPress={onPress}
|
|
228
|
+
accessibilityRole="button"
|
|
229
|
+
accessibilityLabel={accessibilityLabel ?? title}
|
|
230
|
+
style={({ pressed }: PressableStateCallbackType): StyleProp<ViewStyle> => [
|
|
231
|
+
containerLayoutStyle,
|
|
232
|
+
style,
|
|
233
|
+
pressed ? { opacity: 0.7 } : null,
|
|
234
|
+
]}
|
|
235
|
+
>
|
|
236
|
+
{tileContent}
|
|
237
|
+
</Pressable>
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<View
|
|
243
|
+
style={[containerLayoutStyle, style]}
|
|
244
|
+
accessibilityLabel={accessibilityLabel}
|
|
245
|
+
>
|
|
246
|
+
{tileContent}
|
|
247
|
+
</View>
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export default SummaryTile
|
|
@@ -4,8 +4,17 @@ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
|
4
4
|
import { EMPTY_MODES } from '../../utils/react-utils'
|
|
5
5
|
|
|
6
6
|
export type TextProps = {
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* The text content to display. You may also pass content as JSX children
|
|
9
|
+
* (e.g. `<Text>Hello</Text>`) — when both are provided, `children` wins.
|
|
10
|
+
*/
|
|
8
11
|
text?: string
|
|
12
|
+
/**
|
|
13
|
+
* Child nodes (string, number, or nested <Text>/<RNText> elements). This
|
|
14
|
+
* mirrors the standard React Native `<Text>` API so the component is a
|
|
15
|
+
* near-drop-in replacement.
|
|
16
|
+
*/
|
|
17
|
+
children?: React.ReactNode
|
|
9
18
|
/** Horizontal alignment of the text. */
|
|
10
19
|
textAlign?: 'Left' | 'Center'
|
|
11
20
|
/** Modes configuration for design token resolution. */
|
|
@@ -22,7 +31,8 @@ const TEXT_ALIGN_MAP: Record<NonNullable<TextProps['textAlign']>, TextStyle['tex
|
|
|
22
31
|
}
|
|
23
32
|
|
|
24
33
|
function Text({
|
|
25
|
-
text
|
|
34
|
+
text,
|
|
35
|
+
children,
|
|
26
36
|
textAlign = 'Left',
|
|
27
37
|
modes = EMPTY_MODES,
|
|
28
38
|
style,
|
|
@@ -45,12 +55,23 @@ function Text({
|
|
|
45
55
|
textAlign: TEXT_ALIGN_MAP[textAlign],
|
|
46
56
|
}
|
|
47
57
|
|
|
58
|
+
// Prefer JSX children when present, otherwise fall back to the `text` prop.
|
|
59
|
+
// Keep the storybook placeholder as a last resort so the Default story
|
|
60
|
+
// still renders something visible when no content is supplied via either
|
|
61
|
+
// route.
|
|
62
|
+
const content =
|
|
63
|
+
children !== undefined && children !== null && children !== false
|
|
64
|
+
? children
|
|
65
|
+
: text !== undefined
|
|
66
|
+
? text
|
|
67
|
+
: 'Korem ipsum '
|
|
68
|
+
|
|
48
69
|
return (
|
|
49
70
|
<RNText
|
|
50
71
|
style={[textStyle, style]}
|
|
51
72
|
numberOfLines={numberOfLines}
|
|
52
73
|
>
|
|
53
|
-
{
|
|
74
|
+
{content}
|
|
54
75
|
</RNText>
|
|
55
76
|
)
|
|
56
77
|
}
|
|
@@ -238,9 +238,25 @@ export function TooltipTrigger({ children, asChild, ...props }: TooltipTriggerPr
|
|
|
238
238
|
export type TooltipContentProps = {
|
|
239
239
|
children: ReactNode
|
|
240
240
|
sideOffset?: number
|
|
241
|
+
/**
|
|
242
|
+
* Vertical spacing between slot children, in pixels.
|
|
243
|
+
* The content area acts as a vertical slot: any children passed in
|
|
244
|
+
* (text, icons, custom views, etc.) are stacked top-to-bottom with this gap.
|
|
245
|
+
*/
|
|
246
|
+
gap?: number
|
|
247
|
+
/**
|
|
248
|
+
* Cross-axis alignment of slot children.
|
|
249
|
+
* Defaults to `flex-start` so multi-line/multi-element content lines up on the left.
|
|
250
|
+
*/
|
|
251
|
+
alignItems?: ViewStyle['alignItems']
|
|
241
252
|
}
|
|
242
253
|
|
|
243
|
-
export function TooltipContent({
|
|
254
|
+
export function TooltipContent({
|
|
255
|
+
children,
|
|
256
|
+
sideOffset = 4,
|
|
257
|
+
gap = 4,
|
|
258
|
+
alignItems = 'flex-start',
|
|
259
|
+
}: TooltipContentProps) {
|
|
244
260
|
const { isVisible, hide, triggerRect, setContentSize, contentSize, modes, placement: preferredPlacement } = useTooltipContext()
|
|
245
261
|
const insets = useSafeAreaInsets()
|
|
246
262
|
const { width: windowWidth, height: windowHeight } = Dimensions.get('window')
|
|
@@ -467,6 +483,37 @@ export function TooltipContent({ children, sideOffset = 4 }: TooltipContentProps
|
|
|
467
483
|
paddingVertical: paddingV,
|
|
468
484
|
}
|
|
469
485
|
|
|
486
|
+
// Vertical slot wrapper: stack arbitrary children top-to-bottom with a gap.
|
|
487
|
+
// Raw <Text> children still get auto-styled with the tooltip label tokens
|
|
488
|
+
// so the simple <TooltipContent><Text>label</Text></TooltipContent> usage
|
|
489
|
+
// keeps working without any changes.
|
|
490
|
+
const slotStyle: ViewStyle = {
|
|
491
|
+
flexDirection: 'column',
|
|
492
|
+
alignItems,
|
|
493
|
+
gap,
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const renderSlotChildren = () => {
|
|
497
|
+
if (typeof children === 'string') {
|
|
498
|
+
return <Text style={textStyle}>{children}</Text>
|
|
499
|
+
}
|
|
500
|
+
return (
|
|
501
|
+
<View style={slotStyle}>
|
|
502
|
+
{React.Children.map(children, (child) => {
|
|
503
|
+
if (
|
|
504
|
+
React.isValidElement(child) &&
|
|
505
|
+
(child.type === Text || (child.type as any).displayName === 'Text')
|
|
506
|
+
) {
|
|
507
|
+
return React.cloneElement(child, {
|
|
508
|
+
style: [textStyle, (child.props as any).style],
|
|
509
|
+
} as any)
|
|
510
|
+
}
|
|
511
|
+
return child
|
|
512
|
+
})}
|
|
513
|
+
</View>
|
|
514
|
+
)
|
|
515
|
+
}
|
|
516
|
+
|
|
470
517
|
if (!hasMeasured) {
|
|
471
518
|
return (
|
|
472
519
|
<Modal transparent visible={isVisible} animationType="fade">
|
|
@@ -475,18 +522,7 @@ export function TooltipContent({ children, sideOffset = 4 }: TooltipContentProps
|
|
|
475
522
|
style={measureStyle}
|
|
476
523
|
onLayout={(e) => setContentSize(e.nativeEvent.layout)}
|
|
477
524
|
>
|
|
478
|
-
{
|
|
479
|
-
<Text style={textStyle}>{children}</Text>
|
|
480
|
-
) : (
|
|
481
|
-
<View>
|
|
482
|
-
{React.Children.map(children, child => {
|
|
483
|
-
if (React.isValidElement(child) && (child.type === Text || (child.type as any).displayName === 'Text')) {
|
|
484
|
-
return React.cloneElement(child, { style: [textStyle, (child.props as any).style] } as any)
|
|
485
|
-
}
|
|
486
|
-
return child
|
|
487
|
-
})}
|
|
488
|
-
</View>
|
|
489
|
-
)}
|
|
525
|
+
{renderSlotChildren()}
|
|
490
526
|
</View>
|
|
491
527
|
</View>
|
|
492
528
|
</Modal>
|
|
@@ -519,18 +555,7 @@ export function TooltipContent({ children, sideOffset = 4 }: TooltipContentProps
|
|
|
519
555
|
}
|
|
520
556
|
]}
|
|
521
557
|
>
|
|
522
|
-
{
|
|
523
|
-
<Text style={textStyle}>{children}</Text>
|
|
524
|
-
) : (
|
|
525
|
-
<View>
|
|
526
|
-
{React.Children.map(children, child => {
|
|
527
|
-
if (React.isValidElement(child) && (child.type === Text || (child.type as any).displayName === 'Text')) {
|
|
528
|
-
return React.cloneElement(child, { style: [textStyle, (child.props as any).style] } as any)
|
|
529
|
-
}
|
|
530
|
-
return child
|
|
531
|
-
})}
|
|
532
|
-
</View>
|
|
533
|
-
)}
|
|
558
|
+
{renderSlotChildren()}
|
|
534
559
|
|
|
535
560
|
{/* Arrow */}
|
|
536
561
|
{/* Render different arrow based on axis to avoid rotation gap issues */}
|