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,268 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
type StyleProp,
|
|
5
|
+
type ViewStyle,
|
|
6
|
+
} from 'react-native'
|
|
7
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
8
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
9
|
+
import { EMPTY_MODES, flattenChildren } from '../../utils/react-utils'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Per-segment data definition for the data-driven `segments` prop.
|
|
13
|
+
*
|
|
14
|
+
* Use `value` for proportional widths (segments share width by their value
|
|
15
|
+
* relative to the sum). When `value` is omitted, segments share the row
|
|
16
|
+
* equally (`flex: 1`).
|
|
17
|
+
*
|
|
18
|
+
* Use `modes` to override the per-segment design-token mode — typically the
|
|
19
|
+
* `Appearance / DataViz` mode for color theming, or `Emphasis / DataViz` to
|
|
20
|
+
* change the emphasis level. The parent already injects per-index defaults
|
|
21
|
+
* (segment 0 = High, 1 = Medium, 2 = Low, then cycling) so callers only need
|
|
22
|
+
* to pass `modes` when they want a different result.
|
|
23
|
+
*/
|
|
24
|
+
export type SegmentedTrackSegmentData = {
|
|
25
|
+
/** Stable React key. */
|
|
26
|
+
key?: React.Key
|
|
27
|
+
/**
|
|
28
|
+
* Optional explicit weight used for proportional width (`flex: value`).
|
|
29
|
+
* When omitted, the segment uses an equal share of the available row.
|
|
30
|
+
* Mixing values and undefined is supported — undefined falls back to 1.
|
|
31
|
+
*/
|
|
32
|
+
value?: number
|
|
33
|
+
/**
|
|
34
|
+
* Hard-override the segment color. Bypasses `dataViz/bg` token resolution
|
|
35
|
+
* entirely.
|
|
36
|
+
*/
|
|
37
|
+
color?: string
|
|
38
|
+
/**
|
|
39
|
+
* Per-segment design token mode overrides. Merged on top of the parent
|
|
40
|
+
* `modes` and the per-index Emphasis defaults.
|
|
41
|
+
*/
|
|
42
|
+
modes?: Record<string, any>
|
|
43
|
+
/** Override individual segment styles. */
|
|
44
|
+
style?: StyleProp<ViewStyle>
|
|
45
|
+
/** Per-segment accessibility label. */
|
|
46
|
+
accessibilityLabel?: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type SegmentedTrackProps = {
|
|
50
|
+
/**
|
|
51
|
+
* Data-driven segment list. Used when no `children` slot is provided.
|
|
52
|
+
* Defaults to three equal segments which receive per-index Emphasis
|
|
53
|
+
* defaults (`High`, `Medium`, `Low`).
|
|
54
|
+
*/
|
|
55
|
+
segments?: SegmentedTrackSegmentData[]
|
|
56
|
+
/**
|
|
57
|
+
* Slot for fully custom segment children. Each top-level child is treated
|
|
58
|
+
* as one segment. The parent injects per-index Emphasis defaults and the
|
|
59
|
+
* shared `modes`, while child-level `modes` overrides win.
|
|
60
|
+
*/
|
|
61
|
+
children?: React.ReactNode
|
|
62
|
+
/** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
|
|
63
|
+
modes?: Record<string, any>
|
|
64
|
+
/** Override the container styles. */
|
|
65
|
+
style?: StyleProp<ViewStyle>
|
|
66
|
+
/** Style applied to every segment (data-driven mode only). */
|
|
67
|
+
segmentStyle?: StyleProp<ViewStyle>
|
|
68
|
+
/** Accessibility label announced for the whole distribution row. */
|
|
69
|
+
accessibilityLabel?: string
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type SegmentedTrackSegmentProps = {
|
|
73
|
+
/**
|
|
74
|
+
* Optional explicit weight used for proportional width (`flex: value`).
|
|
75
|
+
* Defaults to 1 (equal share within the parent row).
|
|
76
|
+
*/
|
|
77
|
+
value?: number | undefined
|
|
78
|
+
/**
|
|
79
|
+
* Hard-override the segment color. Bypasses `dataViz/bg` token resolution.
|
|
80
|
+
*/
|
|
81
|
+
color?: string | undefined
|
|
82
|
+
/**
|
|
83
|
+
* Design token modes for the segment. Merged with parent `modes` and the
|
|
84
|
+
* per-index Emphasis defaults injected by the parent `SegmentedTrack`.
|
|
85
|
+
*/
|
|
86
|
+
modes?: Record<string, any> | undefined
|
|
87
|
+
/** Override the segment styles. */
|
|
88
|
+
style?: StyleProp<ViewStyle> | undefined
|
|
89
|
+
/** Per-segment accessibility label. */
|
|
90
|
+
accessibilityLabel?: string | undefined
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Default per-index Emphasis modes applied to every segment when the caller
|
|
95
|
+
* does not provide its own `Emphasis / DataViz` override. Cycles for >3
|
|
96
|
+
* segments so additional segments fall back to the same High/Medium/Low
|
|
97
|
+
* rotation.
|
|
98
|
+
*/
|
|
99
|
+
const DEFAULT_EMPHASIS_CYCLE = ['High', 'Medium', 'Low'] as const
|
|
100
|
+
|
|
101
|
+
const DEFAULT_SEGMENTS: SegmentedTrackSegmentData[] = [
|
|
102
|
+
{},
|
|
103
|
+
{},
|
|
104
|
+
{},
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Compute the default `Emphasis / DataViz` mode for a segment at `index`.
|
|
109
|
+
* Cycles through {@link DEFAULT_EMPHASIS_CYCLE} so any number of segments
|
|
110
|
+
* gets a sensible default.
|
|
111
|
+
*/
|
|
112
|
+
function defaultEmphasisFor(index: number) {
|
|
113
|
+
return DEFAULT_EMPHASIS_CYCLE[index % DEFAULT_EMPHASIS_CYCLE.length]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function SegmentedTrackSegment({
|
|
117
|
+
value = 1,
|
|
118
|
+
color,
|
|
119
|
+
modes = EMPTY_MODES,
|
|
120
|
+
style,
|
|
121
|
+
accessibilityLabel,
|
|
122
|
+
}: SegmentedTrackSegmentProps) {
|
|
123
|
+
const resolvedColor =
|
|
124
|
+
color ?? ((getVariableByName('dataViz/bg', modes) as string | null) ?? '#5d00b5')
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<View
|
|
128
|
+
accessibilityLabel={accessibilityLabel}
|
|
129
|
+
style={[
|
|
130
|
+
{
|
|
131
|
+
flex: value,
|
|
132
|
+
minWidth: 1,
|
|
133
|
+
height: '100%',
|
|
134
|
+
backgroundColor: resolvedColor,
|
|
135
|
+
},
|
|
136
|
+
style,
|
|
137
|
+
]}
|
|
138
|
+
/>
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* `SegmentedTrack` renders a horizontal pill-shaped row of categorical
|
|
144
|
+
* segments. Use it for distribution / share-of breakdowns where each
|
|
145
|
+
* segment is a sibling category (not a directional or temporal progress).
|
|
146
|
+
*
|
|
147
|
+
* Defaults to three equal segments tinted at descending Emphasis levels
|
|
148
|
+
* (`High`, `Medium`, `Low`) so the shape reads as a single concept split
|
|
149
|
+
* three ways. Consumers can either pass the data-driven `segments` prop or
|
|
150
|
+
* a fully custom `children` slot of `SegmentedTrack.Segment`s.
|
|
151
|
+
*
|
|
152
|
+
* Each segment resolves its color through the `dataViz/bg` token, which
|
|
153
|
+
* cascades through `Emphasis / DataViz` and `Appearance / DataViz`. Pass
|
|
154
|
+
* `modes` per segment (or override `Appearance / DataViz` at the parent
|
|
155
|
+
* level via `modes`) to retheme the row without touching colors directly.
|
|
156
|
+
*
|
|
157
|
+
* @component
|
|
158
|
+
* @param {SegmentedTrackProps} props
|
|
159
|
+
*/
|
|
160
|
+
function SegmentedTrack({
|
|
161
|
+
segments,
|
|
162
|
+
children,
|
|
163
|
+
modes: propModes = EMPTY_MODES,
|
|
164
|
+
style,
|
|
165
|
+
segmentStyle,
|
|
166
|
+
accessibilityLabel,
|
|
167
|
+
}: SegmentedTrackProps) {
|
|
168
|
+
const { modes: globalModes } = useTokens()
|
|
169
|
+
const modes = { ...globalModes, ...propModes }
|
|
170
|
+
|
|
171
|
+
const trackHeight =
|
|
172
|
+
(getVariableByName('segmentedTrack/height', modes) as number | null) ?? 24
|
|
173
|
+
const trackRadius =
|
|
174
|
+
(getVariableByName('segmentedTrack/radius', modes) as number | null) ?? 999
|
|
175
|
+
|
|
176
|
+
const renderedSegments = renderSegments({
|
|
177
|
+
segments,
|
|
178
|
+
children,
|
|
179
|
+
modes,
|
|
180
|
+
segmentStyle,
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<View
|
|
185
|
+
accessibilityRole="image"
|
|
186
|
+
accessibilityLabel={accessibilityLabel}
|
|
187
|
+
style={[
|
|
188
|
+
{
|
|
189
|
+
flexDirection: 'row',
|
|
190
|
+
alignItems: 'stretch',
|
|
191
|
+
height: trackHeight,
|
|
192
|
+
borderRadius: trackRadius,
|
|
193
|
+
overflow: 'hidden',
|
|
194
|
+
width: '100%',
|
|
195
|
+
backgroundColor: 'transparent',
|
|
196
|
+
},
|
|
197
|
+
style,
|
|
198
|
+
]}
|
|
199
|
+
>
|
|
200
|
+
{renderedSegments}
|
|
201
|
+
</View>
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Build the slot children. When the caller passes JSX `children`, every
|
|
207
|
+
* top-level element is treated as one segment and receives merged `modes`
|
|
208
|
+
* (parent + per-index Emphasis default + the child's own `modes` taking
|
|
209
|
+
* priority). Otherwise the data-driven `segments` array is rendered.
|
|
210
|
+
*/
|
|
211
|
+
function renderSegments({
|
|
212
|
+
segments,
|
|
213
|
+
children,
|
|
214
|
+
modes,
|
|
215
|
+
segmentStyle,
|
|
216
|
+
}: {
|
|
217
|
+
segments: SegmentedTrackSegmentData[] | undefined
|
|
218
|
+
children: React.ReactNode | undefined
|
|
219
|
+
modes: Record<string, any>
|
|
220
|
+
segmentStyle: StyleProp<ViewStyle> | undefined
|
|
221
|
+
}): React.ReactNode[] {
|
|
222
|
+
if (children !== undefined && children !== null) {
|
|
223
|
+
const flat = flattenChildren(children)
|
|
224
|
+
return flat.map((child, index) => {
|
|
225
|
+
if (!React.isValidElement(child)) {
|
|
226
|
+
return child
|
|
227
|
+
}
|
|
228
|
+
const childProps = (child.props as any) ?? {}
|
|
229
|
+
const childModes = childProps.modes
|
|
230
|
+
const mergedModes = {
|
|
231
|
+
...modes,
|
|
232
|
+
'Emphasis / DataViz': defaultEmphasisFor(index),
|
|
233
|
+
...(childModes || {}),
|
|
234
|
+
}
|
|
235
|
+
return React.cloneElement(child, {
|
|
236
|
+
...childProps,
|
|
237
|
+
modes: mergedModes,
|
|
238
|
+
key: child.key ?? `segment-${index}`,
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const list = segments && segments.length > 0 ? segments : DEFAULT_SEGMENTS
|
|
244
|
+
return list.map((segment, index) => {
|
|
245
|
+
const segmentModes = {
|
|
246
|
+
...modes,
|
|
247
|
+
'Emphasis / DataViz': defaultEmphasisFor(index),
|
|
248
|
+
...(segment.modes || {}),
|
|
249
|
+
}
|
|
250
|
+
return (
|
|
251
|
+
<SegmentedTrackSegment
|
|
252
|
+
key={segment.key ?? `segment-${index}`}
|
|
253
|
+
value={segment.value ?? 1}
|
|
254
|
+
color={segment.color}
|
|
255
|
+
modes={segmentModes}
|
|
256
|
+
style={[segmentStyle, segment.style]}
|
|
257
|
+
accessibilityLabel={segment.accessibilityLabel}
|
|
258
|
+
/>
|
|
259
|
+
)
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
SegmentedTrack.Segment = SegmentedTrackSegment
|
|
264
|
+
|
|
265
|
+
export { SegmentedTrackSegment }
|
|
266
|
+
export type { SegmentedTrackSegmentProps }
|
|
267
|
+
|
|
268
|
+
export default SegmentedTrack
|
|
@@ -0,0 +1,169 @@
|
|
|
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 {
|
|
5
|
+
EMPTY_MODES,
|
|
6
|
+
cloneChildrenWithModes,
|
|
7
|
+
flattenChildren,
|
|
8
|
+
} from '../../utils/react-utils'
|
|
9
|
+
import StatItem from '../StatItem/StatItem'
|
|
10
|
+
import Divider from '../Divider/Divider'
|
|
11
|
+
|
|
12
|
+
export type StatGroupItem = {
|
|
13
|
+
/** Stable key for the item. Falls back to the label / index. */
|
|
14
|
+
key?: React.Key
|
|
15
|
+
/** The descriptive label shown beneath the value. */
|
|
16
|
+
label?: string
|
|
17
|
+
/** The prominent value shown above the label. */
|
|
18
|
+
value?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type StatGroupProps = {
|
|
22
|
+
/**
|
|
23
|
+
* Array of stat items. Each item is rendered as a centered `StatItem`
|
|
24
|
+
* (`labelPosition="Bottom"`) with vertical dividers between them.
|
|
25
|
+
*
|
|
26
|
+
* Ignored when a `children` slot is provided.
|
|
27
|
+
*/
|
|
28
|
+
items?: StatGroupItem[]
|
|
29
|
+
/**
|
|
30
|
+
* Custom slot content. When provided, the `items` prop is ignored. Each
|
|
31
|
+
* top-level child is wrapped to take an equal share of the available width
|
|
32
|
+
* and a vertical `Divider` is auto-inserted between siblings — matching the
|
|
33
|
+
* Figma `slot` behaviour. `modes` are propagated to all children.
|
|
34
|
+
*/
|
|
35
|
+
children?: React.ReactNode
|
|
36
|
+
/** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
|
|
37
|
+
modes?: Record<string, any>
|
|
38
|
+
/** Override container styles. */
|
|
39
|
+
style?: StyleProp<ViewStyle>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const DEFAULT_ITEMS: StatGroupItem[] = [
|
|
43
|
+
{ label: 'Updates', value: 'Daily' },
|
|
44
|
+
{ label: 'Data range', value: '24 months' },
|
|
45
|
+
{ label: 'Validity', value: '24 months' },
|
|
46
|
+
{ label: 'Stored for', value: '1 month' },
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* StatGroup renders a card-style container holding a horizontal row of
|
|
51
|
+
* `StatItem`s separated by vertical dividers. It is typically used to surface
|
|
52
|
+
* 3–5 short metrics (e.g. validity, data range, storage) at a glance.
|
|
53
|
+
*
|
|
54
|
+
* Pass `items` for the simple data-driven case, or use the `children` slot
|
|
55
|
+
* for full control over the row contents (the component still auto-inserts
|
|
56
|
+
* vertical dividers between top-level children).
|
|
57
|
+
*
|
|
58
|
+
* @component
|
|
59
|
+
* @param {StatGroupProps} props
|
|
60
|
+
*/
|
|
61
|
+
function StatGroup({
|
|
62
|
+
items,
|
|
63
|
+
children,
|
|
64
|
+
modes = EMPTY_MODES,
|
|
65
|
+
style,
|
|
66
|
+
}: StatGroupProps) {
|
|
67
|
+
const background =
|
|
68
|
+
(getVariableByName('statGroup/background', modes) as string | null) ?? '#ffffff'
|
|
69
|
+
const strokeColor =
|
|
70
|
+
(getVariableByName('statGroup/stroke/color', modes) as string | null) ?? '#ebebed'
|
|
71
|
+
const strokeSize =
|
|
72
|
+
(getVariableByName('statGroup/stroke/size', modes) as number | null) ?? 1
|
|
73
|
+
const radius =
|
|
74
|
+
(getVariableByName('statGroup/radius', modes) as number | null) ?? 12
|
|
75
|
+
const paddingTop =
|
|
76
|
+
(getVariableByName('statGroup/padding/top', modes) as number | null) ?? 16
|
|
77
|
+
const paddingRight =
|
|
78
|
+
(getVariableByName('statGroup/padding/right', modes) as number | null) ?? 0
|
|
79
|
+
const paddingBottom =
|
|
80
|
+
(getVariableByName('statGroup/padding/bottom', modes) as number | null) ?? 12
|
|
81
|
+
const paddingLeft =
|
|
82
|
+
(getVariableByName('statGroup/padding/left', modes) as number | null) ?? 0
|
|
83
|
+
|
|
84
|
+
const containerStyle: ViewStyle = {
|
|
85
|
+
backgroundColor: background,
|
|
86
|
+
borderColor: strokeColor,
|
|
87
|
+
borderWidth: strokeSize,
|
|
88
|
+
borderStyle: 'solid',
|
|
89
|
+
borderRadius: radius,
|
|
90
|
+
paddingTop,
|
|
91
|
+
paddingRight,
|
|
92
|
+
paddingBottom,
|
|
93
|
+
paddingLeft,
|
|
94
|
+
overflow: 'hidden',
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const slotChildren = renderSlotChildren({ items, children, modes })
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<View style={[containerStyle, style]} accessibilityRole="summary">
|
|
101
|
+
<View
|
|
102
|
+
style={{
|
|
103
|
+
flexDirection: 'row',
|
|
104
|
+
alignItems: 'stretch',
|
|
105
|
+
width: '100%',
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
{slotChildren}
|
|
109
|
+
</View>
|
|
110
|
+
</View>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Build the row of items: render either the supplied children or the
|
|
116
|
+
* `items` array, wrap each entry so it grows equally, and inject a vertical
|
|
117
|
+
* `Divider` between siblings.
|
|
118
|
+
*/
|
|
119
|
+
function renderSlotChildren({
|
|
120
|
+
items,
|
|
121
|
+
children,
|
|
122
|
+
modes,
|
|
123
|
+
}: {
|
|
124
|
+
items?: StatGroupItem[]
|
|
125
|
+
children?: React.ReactNode
|
|
126
|
+
modes: Record<string, any>
|
|
127
|
+
}): React.ReactNode[] {
|
|
128
|
+
let nodes: React.ReactNode[]
|
|
129
|
+
|
|
130
|
+
if (children !== undefined && children !== null) {
|
|
131
|
+
const cloned = cloneChildrenWithModes(children, modes)
|
|
132
|
+
nodes = flattenChildren(cloned)
|
|
133
|
+
} else {
|
|
134
|
+
const list = items && items.length > 0 ? items : DEFAULT_ITEMS
|
|
135
|
+
nodes = list.map((item, index) => (
|
|
136
|
+
<StatItem
|
|
137
|
+
key={item.key ?? `${item.label ?? 'item'}-${index}`}
|
|
138
|
+
label={item.label}
|
|
139
|
+
value={item.value}
|
|
140
|
+
labelPosition="Bottom"
|
|
141
|
+
modes={modes}
|
|
142
|
+
/>
|
|
143
|
+
))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const result: React.ReactNode[] = []
|
|
147
|
+
nodes.forEach((node, index) => {
|
|
148
|
+
if (index > 0) {
|
|
149
|
+
result.push(
|
|
150
|
+
<Divider
|
|
151
|
+
key={`divider-${index}`}
|
|
152
|
+
direction="vertical"
|
|
153
|
+
modes={modes}
|
|
154
|
+
/>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
result.push(
|
|
158
|
+
<View
|
|
159
|
+
key={`slot-${index}`}
|
|
160
|
+
style={{ flex: 1, minWidth: 0, alignSelf: 'center' }}
|
|
161
|
+
>
|
|
162
|
+
{node}
|
|
163
|
+
</View>
|
|
164
|
+
)
|
|
165
|
+
})
|
|
166
|
+
return result
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export default StatGroup
|
|
@@ -1,22 +1,46 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
type StyleProp,
|
|
6
|
+
type ViewStyle,
|
|
7
|
+
type TextStyle,
|
|
8
|
+
} from 'react-native'
|
|
3
9
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
10
|
import { EMPTY_MODES } from '../../utils/react-utils'
|
|
5
11
|
|
|
12
|
+
export type StatItemLabelPosition = 'Top' | 'Bottom'
|
|
13
|
+
|
|
6
14
|
export type StatItemProps = {
|
|
7
|
-
/** The small descriptive label
|
|
15
|
+
/** The small descriptive label. */
|
|
8
16
|
label?: string
|
|
9
|
-
/** The
|
|
17
|
+
/** The prominent value to display. */
|
|
10
18
|
value?: string
|
|
11
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Whether the label is rendered above or below the value. Matches the
|
|
21
|
+
* Figma `labelPosition` variant. Defaults to `'Top'`.
|
|
22
|
+
*
|
|
23
|
+
* - `'Top'` — Label (12px medium) above large value (26px black). Left-aligned.
|
|
24
|
+
* - `'Bottom'` — Value (12px medium) above smaller label (10px regular). Center-aligned.
|
|
25
|
+
*/
|
|
26
|
+
labelPosition?: StatItemLabelPosition
|
|
27
|
+
/** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
|
|
12
28
|
modes?: Record<string, any>
|
|
13
29
|
/** Override container styles. */
|
|
14
30
|
style?: StyleProp<ViewStyle>
|
|
31
|
+
/** Override the label text styles. */
|
|
32
|
+
labelStyle?: StyleProp<TextStyle>
|
|
33
|
+
/** Override the value text styles. */
|
|
34
|
+
valueStyle?: StyleProp<TextStyle>
|
|
15
35
|
}
|
|
16
36
|
|
|
17
37
|
/**
|
|
18
|
-
* StatItem displays a label/value pair
|
|
19
|
-
*
|
|
38
|
+
* StatItem displays a label/value pair, useful for product stats, metrics
|
|
39
|
+
* or KPI callouts.
|
|
40
|
+
*
|
|
41
|
+
* Supports two layouts via the `labelPosition` variant:
|
|
42
|
+
* - `'Top'` — Small label above a large prominent value (default).
|
|
43
|
+
* - `'Bottom'` — Value above a smaller label, content centered.
|
|
20
44
|
*
|
|
21
45
|
* @component
|
|
22
46
|
* @param {StatItemProps} props
|
|
@@ -24,47 +48,100 @@ export type StatItemProps = {
|
|
|
24
48
|
function StatItem({
|
|
25
49
|
label = 'Purity verified by NABL',
|
|
26
50
|
value = '99.99%',
|
|
51
|
+
labelPosition = 'Top',
|
|
27
52
|
modes = EMPTY_MODES,
|
|
28
53
|
style,
|
|
54
|
+
labelStyle,
|
|
55
|
+
valueStyle,
|
|
29
56
|
}: StatItemProps) {
|
|
30
|
-
const
|
|
57
|
+
const isBottom = labelPosition === 'Bottom'
|
|
58
|
+
|
|
59
|
+
// The Figma `Bottom` variant overrides token values locally inside the
|
|
60
|
+
// component (it is not exposed in the design-tokens JSON), so the resolved
|
|
61
|
+
// token values reflect only the `Top` variant. For the `Bottom` variant we
|
|
62
|
+
// therefore use hardcoded literals that match the Figma design, while still
|
|
63
|
+
// allowing the resolver to surface any future mode-based overrides for the
|
|
64
|
+
// `Top` variant.
|
|
65
|
+
const gap = isBottom
|
|
66
|
+
? 6
|
|
67
|
+
: ((getVariableByName('statItem/gap', modes) as number | null) ?? 2)
|
|
68
|
+
|
|
69
|
+
const labelForeground = isBottom
|
|
70
|
+
? '#24262b'
|
|
71
|
+
: ((getVariableByName('statItem/label/foreground', modes) as string | null) ?? '#1a1c1f')
|
|
72
|
+
const labelFontFamily =
|
|
73
|
+
(getVariableByName('statItem/label/fontFamily', modes) as string | null) ?? 'JioType Var'
|
|
74
|
+
const labelFontSize = isBottom
|
|
75
|
+
? 10
|
|
76
|
+
: ((getVariableByName('statItem/label/fontSize', modes) as number | null) ?? 12)
|
|
77
|
+
const labelFontWeightRaw = isBottom
|
|
78
|
+
? 400
|
|
79
|
+
: (getVariableByName('statItem/label/fontWeight', modes) ?? 500)
|
|
80
|
+
const labelFontWeight = String(labelFontWeightRaw) as TextStyle['fontWeight']
|
|
81
|
+
const labelLineHeight = isBottom
|
|
82
|
+
? 13
|
|
83
|
+
: ((getVariableByName('statItem/label/lineHeight', modes) as number | null) ?? 16)
|
|
84
|
+
|
|
85
|
+
const valueForeground = isBottom
|
|
86
|
+
? '#141414'
|
|
87
|
+
: ((getVariableByName('statItem/value/foreground', modes) as string | null) ?? '#0d0d0f')
|
|
88
|
+
const valueFontFamily =
|
|
89
|
+
(getVariableByName('statItem/value/fontFamily', modes) as string | null) ?? 'JioType Var'
|
|
90
|
+
const valueFontSize = isBottom
|
|
91
|
+
? 12
|
|
92
|
+
: ((getVariableByName('statItem/value/fontSize', modes) as number | null) ?? 26)
|
|
93
|
+
const valueFontWeightRaw = isBottom
|
|
94
|
+
? 500
|
|
95
|
+
: (getVariableByName('statItem/value/fontWeight', modes) ?? 900)
|
|
96
|
+
const valueFontWeight = String(valueFontWeightRaw) as TextStyle['fontWeight']
|
|
97
|
+
const valueLineHeight = isBottom
|
|
98
|
+
? 16
|
|
99
|
+
: ((getVariableByName('statItem/value/lineHeight', modes) as number | null) ?? 26)
|
|
31
100
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
101
|
+
const containerStyle: ViewStyle = {
|
|
102
|
+
gap,
|
|
103
|
+
alignItems: isBottom ? 'center' : 'flex-start',
|
|
104
|
+
justifyContent: isBottom ? 'center' : 'flex-start',
|
|
105
|
+
}
|
|
37
106
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
107
|
+
const labelTextStyle: TextStyle = {
|
|
108
|
+
color: labelForeground,
|
|
109
|
+
fontFamily: labelFontFamily,
|
|
110
|
+
fontSize: labelFontSize,
|
|
111
|
+
fontWeight: labelFontWeight,
|
|
112
|
+
lineHeight: labelLineHeight,
|
|
113
|
+
textAlign: isBottom ? 'center' : 'left',
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const valueTextStyle: TextStyle = {
|
|
117
|
+
color: valueForeground,
|
|
118
|
+
fontFamily: valueFontFamily,
|
|
119
|
+
fontSize: valueFontSize,
|
|
120
|
+
fontWeight: valueFontWeight,
|
|
121
|
+
lineHeight: valueLineHeight,
|
|
122
|
+
textAlign: isBottom ? 'center' : 'left',
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const labelNode = (
|
|
126
|
+
<Text style={[labelTextStyle, labelStyle]}>{label}</Text>
|
|
127
|
+
)
|
|
128
|
+
const valueNode = (
|
|
129
|
+
<Text style={[valueTextStyle, valueStyle]}>{value}</Text>
|
|
130
|
+
)
|
|
43
131
|
|
|
44
132
|
return (
|
|
45
|
-
<View style={[
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
<Text
|
|
58
|
-
style={{
|
|
59
|
-
color: valueForeground as string,
|
|
60
|
-
fontFamily: valueFontFamily as string,
|
|
61
|
-
fontSize: valueFontSize as number,
|
|
62
|
-
fontWeight: String(valueFontWeight) as any,
|
|
63
|
-
lineHeight: valueLineHeight as number,
|
|
64
|
-
}}
|
|
65
|
-
>
|
|
66
|
-
{value}
|
|
67
|
-
</Text>
|
|
133
|
+
<View style={[containerStyle, style]}>
|
|
134
|
+
{isBottom ? (
|
|
135
|
+
<>
|
|
136
|
+
{valueNode}
|
|
137
|
+
{labelNode}
|
|
138
|
+
</>
|
|
139
|
+
) : (
|
|
140
|
+
<>
|
|
141
|
+
{labelNode}
|
|
142
|
+
{valueNode}
|
|
143
|
+
</>
|
|
144
|
+
)}
|
|
68
145
|
</View>
|
|
69
146
|
)
|
|
70
147
|
}
|