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,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useMemo } from 'react';
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
|
|
7
|
+
import { formatIndianNumber } from '../../utils/number-utils';
|
|
8
|
+
import Title from '../Title/Title';
|
|
9
|
+
import LinearProgress from '../LinearProgress/LinearProgress';
|
|
10
|
+
import MetricLegendItem from '../MetricLegendItem/MetricLegendItem';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A single row in the savings-goal legend (current vs. target).
|
|
14
|
+
*
|
|
15
|
+
* `value` is a **numeric amount** (e.g. `240000`). It serves two purposes:
|
|
16
|
+
*
|
|
17
|
+
* 1. Display: rendered on the right side of the row using Indian numeric
|
|
18
|
+
* notation via {@link formatIndianNumber} and prefixed with `currency`
|
|
19
|
+
* (e.g. `240000` → `"₹2.4L"`).
|
|
20
|
+
* 2. Progress derivation: the bar fills to `current.value / target.value`
|
|
21
|
+
* automatically. There is no separate `progress` prop.
|
|
22
|
+
*
|
|
23
|
+
* Pass `value: undefined` to render a label-only row (the underlying
|
|
24
|
+
* {@link MetricLegendItem} hides the right slot in that case).
|
|
25
|
+
*/
|
|
26
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
27
|
+
const DEFAULT_LEGEND_PADDING = 8;
|
|
28
|
+
const DEFAULT_MODES = Object.freeze({
|
|
29
|
+
'LinearProgress Size': 'L'
|
|
30
|
+
});
|
|
31
|
+
const DEFAULT_CURRENT = {
|
|
32
|
+
label: 'Current (4 months)',
|
|
33
|
+
value: 240000
|
|
34
|
+
};
|
|
35
|
+
const DEFAULT_TARGET = {
|
|
36
|
+
label: 'Recommended (8 months)',
|
|
37
|
+
value: 480000
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* `SavingsGoalSummary` visualises progress toward a savings goal as:
|
|
42
|
+
*
|
|
43
|
+
* 1. A `Title` showing the percentage — **computed automatically** from
|
|
44
|
+
* `current.value / target.value`. There is no `progress` prop; the
|
|
45
|
+
* component owns the calculation so the title, bar and legend are always
|
|
46
|
+
* in sync.
|
|
47
|
+
* 2. A `LinearProgress` bar driven by the same derived ratio.
|
|
48
|
+
* 3. A two-row legend comparing **current** vs. **target**, where each numeric
|
|
49
|
+
* `value` is auto-formatted with Indian notation
|
|
50
|
+
* ({@link formatIndianNumber}) and prefixed with the `currency` symbol.
|
|
51
|
+
*
|
|
52
|
+
* The component is intentionally narrow in scope — it is the body of a savings
|
|
53
|
+
* insight card. Wrap it in `CardInsight` (or any container of your choice) to
|
|
54
|
+
* add a heading, badge or footer.
|
|
55
|
+
*
|
|
56
|
+
* @component
|
|
57
|
+
* @param {SavingsGoalSummaryProps} props
|
|
58
|
+
*/
|
|
59
|
+
function SavingsGoalSummary({
|
|
60
|
+
currency = '₹',
|
|
61
|
+
current = DEFAULT_CURRENT,
|
|
62
|
+
target = DEFAULT_TARGET,
|
|
63
|
+
children,
|
|
64
|
+
modes = EMPTY_MODES,
|
|
65
|
+
style,
|
|
66
|
+
titleStyle,
|
|
67
|
+
legendStyle
|
|
68
|
+
}) {
|
|
69
|
+
// Merge caller modes on top of the defaults so callers can override
|
|
70
|
+
// (e.g. switch to `LinearProgress Size: M`) while still receiving the
|
|
71
|
+
// sensible component-level default.
|
|
72
|
+
const mergedModes = useMemo(() => modes === EMPTY_MODES ? DEFAULT_MODES : {
|
|
73
|
+
...DEFAULT_MODES,
|
|
74
|
+
...modes
|
|
75
|
+
}, [modes]);
|
|
76
|
+
|
|
77
|
+
// Resolve the `LinearProgress` track / indicator colours so the legend
|
|
78
|
+
// dots automatically stay in sync with the progress bar without callers
|
|
79
|
+
// needing to plumb through `indicatorColor`.
|
|
80
|
+
//
|
|
81
|
+
// The token names AND the merge strategy must match `LinearProgress.tsx`
|
|
82
|
+
// exactly, otherwise the dot colours can drift from the bar:
|
|
83
|
+
// • Token aliases live in the `Emphasis` collection (modes High|Medium|Low),
|
|
84
|
+
// NOT `Emphasis / DataViz` — passing the wrong mode key would collapse
|
|
85
|
+
// both dots to the same default-mode colour.
|
|
86
|
+
// • Defaults are placed *before* `mergedModes` is spread, so callers can
|
|
87
|
+
// still override `Emphasis` via the `modes` prop (matches
|
|
88
|
+
// `LinearProgress`'s philosophy).
|
|
89
|
+
const indicatorColorFromTokens = getVariableByName('linearProgress/indicator/background', {
|
|
90
|
+
Emphasis: 'High',
|
|
91
|
+
...mergedModes
|
|
92
|
+
}) ?? '#5d00b5';
|
|
93
|
+
const trackColorFromTokens = getVariableByName('linearProgress/track/background', {
|
|
94
|
+
Emphasis: 'Low',
|
|
95
|
+
...mergedModes
|
|
96
|
+
}) ?? '#ede7ff';
|
|
97
|
+
|
|
98
|
+
// Single source of truth for the bar fill, the title percentage and the
|
|
99
|
+
// formatted legend amounts. There is intentionally no consumer-facing
|
|
100
|
+
// `progress` prop — the only way to change the bar is to change the
|
|
101
|
+
// numeric `current` / `target` values. This keeps the three views (title,
|
|
102
|
+
// bar, legend) impossible to desynchronise.
|
|
103
|
+
const resolvedProgress = useMemo(() => {
|
|
104
|
+
const cv = current?.value;
|
|
105
|
+
const tv = target?.value;
|
|
106
|
+
if (typeof cv !== 'number' || typeof tv !== 'number' || tv <= 0) {
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
return Math.min(Math.max(cv / tv, 0), 1);
|
|
110
|
+
}, [current, target]);
|
|
111
|
+
const percentageLabel = `${Math.round(resolvedProgress * 100)}%`;
|
|
112
|
+
const gap = getVariableByName('savingsGoalSummary/gap', mergedModes) ?? 23;
|
|
113
|
+
const legendGap = getVariableByName('savingsGoalSummary/legend/gap', mergedModes) ?? 16;
|
|
114
|
+
const customLegend = children ? cloneChildrenWithModes(children, mergedModes) : null;
|
|
115
|
+
const defaultLegend = !customLegend && (current || target) ? /*#__PURE__*/_jsxs(_Fragment, {
|
|
116
|
+
children: [current && /*#__PURE__*/_jsx(MetricLegendItem, {
|
|
117
|
+
modes: mergedModes,
|
|
118
|
+
label: current.label,
|
|
119
|
+
value: formatLegendValue(current.value, currency),
|
|
120
|
+
indicatorColor: current.indicatorColor ?? indicatorColorFromTokens
|
|
121
|
+
}), target && /*#__PURE__*/_jsx(MetricLegendItem, {
|
|
122
|
+
modes: mergedModes,
|
|
123
|
+
label: target.label,
|
|
124
|
+
value: formatLegendValue(target.value, currency),
|
|
125
|
+
indicatorColor: target.indicatorColor ?? trackColorFromTokens
|
|
126
|
+
})]
|
|
127
|
+
}) : null;
|
|
128
|
+
const legendNode = customLegend ?? defaultLegend;
|
|
129
|
+
const showLegend = legendNode != null;
|
|
130
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
131
|
+
style: [{
|
|
132
|
+
width: '100%',
|
|
133
|
+
gap,
|
|
134
|
+
alignItems: 'stretch'
|
|
135
|
+
}, style],
|
|
136
|
+
accessibilityLabel: `Savings goal progress, ${percentageLabel}`,
|
|
137
|
+
children: [/*#__PURE__*/_jsx(Title, {
|
|
138
|
+
title: percentageLabel,
|
|
139
|
+
modes: mergedModes,
|
|
140
|
+
style: TITLE_CONTAINER_STYLE,
|
|
141
|
+
textStyle: titleStyle
|
|
142
|
+
}), /*#__PURE__*/_jsx(LinearProgress, {
|
|
143
|
+
value: resolvedProgress,
|
|
144
|
+
modes: mergedModes
|
|
145
|
+
}), showLegend && /*#__PURE__*/_jsx(View, {
|
|
146
|
+
style: [{
|
|
147
|
+
width: '100%',
|
|
148
|
+
padding: DEFAULT_LEGEND_PADDING,
|
|
149
|
+
gap: legendGap,
|
|
150
|
+
alignItems: 'stretch'
|
|
151
|
+
}, legendStyle],
|
|
152
|
+
children: legendNode
|
|
153
|
+
})]
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Format a single legend `value` for display. Returns `undefined` when the
|
|
159
|
+
* value is missing so the underlying {@link MetricLegendItem} hides the right
|
|
160
|
+
* slot (matches the Figma `data` toggle = off).
|
|
161
|
+
*/
|
|
162
|
+
function formatLegendValue(value, currency) {
|
|
163
|
+
if (typeof value !== 'number') return undefined;
|
|
164
|
+
return formatIndianNumber(value, {
|
|
165
|
+
prefix: currency
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Neutralise the `Title` component's default page-level padding so it sits
|
|
170
|
+
// flush inside the summary card (the parent container owns spacing via `gap`).
|
|
171
|
+
const TITLE_CONTAINER_STYLE = {
|
|
172
|
+
paddingHorizontal: 0,
|
|
173
|
+
paddingVertical: 0
|
|
174
|
+
};
|
|
175
|
+
export default SavingsGoalSummary;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider';
|
|
7
|
+
import { EMPTY_MODES, flattenChildren } from '../../utils/react-utils';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Per-segment data definition for the data-driven `segments` prop.
|
|
11
|
+
*
|
|
12
|
+
* Use `value` for proportional widths (segments share width by their value
|
|
13
|
+
* relative to the sum). When `value` is omitted, segments share the row
|
|
14
|
+
* equally (`flex: 1`).
|
|
15
|
+
*
|
|
16
|
+
* Use `modes` to override the per-segment design-token mode — typically the
|
|
17
|
+
* `Appearance / DataViz` mode for color theming, or `Emphasis / DataViz` to
|
|
18
|
+
* change the emphasis level. The parent already injects per-index defaults
|
|
19
|
+
* (segment 0 = High, 1 = Medium, 2 = Low, then cycling) so callers only need
|
|
20
|
+
* to pass `modes` when they want a different result.
|
|
21
|
+
*/
|
|
22
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
23
|
+
/**
|
|
24
|
+
* Default per-index Emphasis modes applied to every segment when the caller
|
|
25
|
+
* does not provide its own `Emphasis / DataViz` override. Cycles for >3
|
|
26
|
+
* segments so additional segments fall back to the same High/Medium/Low
|
|
27
|
+
* rotation.
|
|
28
|
+
*/
|
|
29
|
+
const DEFAULT_EMPHASIS_CYCLE = ['High', 'Medium', 'Low'];
|
|
30
|
+
const DEFAULT_SEGMENTS = [{}, {}, {}];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Compute the default `Emphasis / DataViz` mode for a segment at `index`.
|
|
34
|
+
* Cycles through {@link DEFAULT_EMPHASIS_CYCLE} so any number of segments
|
|
35
|
+
* gets a sensible default.
|
|
36
|
+
*/
|
|
37
|
+
function defaultEmphasisFor(index) {
|
|
38
|
+
return DEFAULT_EMPHASIS_CYCLE[index % DEFAULT_EMPHASIS_CYCLE.length];
|
|
39
|
+
}
|
|
40
|
+
function SegmentedTrackSegment({
|
|
41
|
+
value = 1,
|
|
42
|
+
color,
|
|
43
|
+
modes = EMPTY_MODES,
|
|
44
|
+
style,
|
|
45
|
+
accessibilityLabel
|
|
46
|
+
}) {
|
|
47
|
+
const resolvedColor = color ?? getVariableByName('dataViz/bg', modes) ?? '#5d00b5';
|
|
48
|
+
return /*#__PURE__*/_jsx(View, {
|
|
49
|
+
accessibilityLabel: accessibilityLabel,
|
|
50
|
+
style: [{
|
|
51
|
+
flex: value,
|
|
52
|
+
minWidth: 1,
|
|
53
|
+
height: '100%',
|
|
54
|
+
backgroundColor: resolvedColor
|
|
55
|
+
}, style]
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* `SegmentedTrack` renders a horizontal pill-shaped row of categorical
|
|
61
|
+
* segments. Use it for distribution / share-of breakdowns where each
|
|
62
|
+
* segment is a sibling category (not a directional or temporal progress).
|
|
63
|
+
*
|
|
64
|
+
* Defaults to three equal segments tinted at descending Emphasis levels
|
|
65
|
+
* (`High`, `Medium`, `Low`) so the shape reads as a single concept split
|
|
66
|
+
* three ways. Consumers can either pass the data-driven `segments` prop or
|
|
67
|
+
* a fully custom `children` slot of `SegmentedTrack.Segment`s.
|
|
68
|
+
*
|
|
69
|
+
* Each segment resolves its color through the `dataViz/bg` token, which
|
|
70
|
+
* cascades through `Emphasis / DataViz` and `Appearance / DataViz`. Pass
|
|
71
|
+
* `modes` per segment (or override `Appearance / DataViz` at the parent
|
|
72
|
+
* level via `modes`) to retheme the row without touching colors directly.
|
|
73
|
+
*
|
|
74
|
+
* @component
|
|
75
|
+
* @param {SegmentedTrackProps} props
|
|
76
|
+
*/
|
|
77
|
+
function SegmentedTrack({
|
|
78
|
+
segments,
|
|
79
|
+
children,
|
|
80
|
+
modes: propModes = EMPTY_MODES,
|
|
81
|
+
style,
|
|
82
|
+
segmentStyle,
|
|
83
|
+
accessibilityLabel
|
|
84
|
+
}) {
|
|
85
|
+
const {
|
|
86
|
+
modes: globalModes
|
|
87
|
+
} = useTokens();
|
|
88
|
+
const modes = {
|
|
89
|
+
...globalModes,
|
|
90
|
+
...propModes
|
|
91
|
+
};
|
|
92
|
+
const trackHeight = getVariableByName('segmentedTrack/height', modes) ?? 24;
|
|
93
|
+
const trackRadius = getVariableByName('segmentedTrack/radius', modes) ?? 999;
|
|
94
|
+
const renderedSegments = renderSegments({
|
|
95
|
+
segments,
|
|
96
|
+
children,
|
|
97
|
+
modes,
|
|
98
|
+
segmentStyle
|
|
99
|
+
});
|
|
100
|
+
return /*#__PURE__*/_jsx(View, {
|
|
101
|
+
accessibilityRole: "image",
|
|
102
|
+
accessibilityLabel: accessibilityLabel,
|
|
103
|
+
style: [{
|
|
104
|
+
flexDirection: 'row',
|
|
105
|
+
alignItems: 'stretch',
|
|
106
|
+
height: trackHeight,
|
|
107
|
+
borderRadius: trackRadius,
|
|
108
|
+
overflow: 'hidden',
|
|
109
|
+
width: '100%',
|
|
110
|
+
backgroundColor: 'transparent'
|
|
111
|
+
}, style],
|
|
112
|
+
children: renderedSegments
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build the slot children. When the caller passes JSX `children`, every
|
|
118
|
+
* top-level element is treated as one segment and receives merged `modes`
|
|
119
|
+
* (parent + per-index Emphasis default + the child's own `modes` taking
|
|
120
|
+
* priority). Otherwise the data-driven `segments` array is rendered.
|
|
121
|
+
*/
|
|
122
|
+
function renderSegments({
|
|
123
|
+
segments,
|
|
124
|
+
children,
|
|
125
|
+
modes,
|
|
126
|
+
segmentStyle
|
|
127
|
+
}) {
|
|
128
|
+
if (children !== undefined && children !== null) {
|
|
129
|
+
const flat = flattenChildren(children);
|
|
130
|
+
return flat.map((child, index) => {
|
|
131
|
+
if (! /*#__PURE__*/React.isValidElement(child)) {
|
|
132
|
+
return child;
|
|
133
|
+
}
|
|
134
|
+
const childProps = child.props ?? {};
|
|
135
|
+
const childModes = childProps.modes;
|
|
136
|
+
const mergedModes = {
|
|
137
|
+
...modes,
|
|
138
|
+
'Emphasis / DataViz': defaultEmphasisFor(index),
|
|
139
|
+
...(childModes || {})
|
|
140
|
+
};
|
|
141
|
+
return /*#__PURE__*/React.cloneElement(child, {
|
|
142
|
+
...childProps,
|
|
143
|
+
modes: mergedModes,
|
|
144
|
+
key: child.key ?? `segment-${index}`
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const list = segments && segments.length > 0 ? segments : DEFAULT_SEGMENTS;
|
|
149
|
+
return list.map((segment, index) => {
|
|
150
|
+
const segmentModes = {
|
|
151
|
+
...modes,
|
|
152
|
+
'Emphasis / DataViz': defaultEmphasisFor(index),
|
|
153
|
+
...(segment.modes || {})
|
|
154
|
+
};
|
|
155
|
+
return /*#__PURE__*/_jsx(SegmentedTrackSegment, {
|
|
156
|
+
value: segment.value ?? 1,
|
|
157
|
+
color: segment.color,
|
|
158
|
+
modes: segmentModes,
|
|
159
|
+
style: [segmentStyle, segment.style],
|
|
160
|
+
accessibilityLabel: segment.accessibilityLabel
|
|
161
|
+
}, segment.key ?? `segment-${index}`);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
SegmentedTrack.Segment = SegmentedTrackSegment;
|
|
165
|
+
export { SegmentedTrackSegment };
|
|
166
|
+
export default SegmentedTrack;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
|
+
import { EMPTY_MODES, cloneChildrenWithModes, flattenChildren } from '../../utils/react-utils';
|
|
7
|
+
import StatItem from '../StatItem/StatItem';
|
|
8
|
+
import Divider from '../Divider/Divider';
|
|
9
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
10
|
+
const DEFAULT_ITEMS = [{
|
|
11
|
+
label: 'Updates',
|
|
12
|
+
value: 'Daily'
|
|
13
|
+
}, {
|
|
14
|
+
label: 'Data range',
|
|
15
|
+
value: '24 months'
|
|
16
|
+
}, {
|
|
17
|
+
label: 'Validity',
|
|
18
|
+
value: '24 months'
|
|
19
|
+
}, {
|
|
20
|
+
label: 'Stored for',
|
|
21
|
+
value: '1 month'
|
|
22
|
+
}];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* StatGroup renders a card-style container holding a horizontal row of
|
|
26
|
+
* `StatItem`s separated by vertical dividers. It is typically used to surface
|
|
27
|
+
* 3–5 short metrics (e.g. validity, data range, storage) at a glance.
|
|
28
|
+
*
|
|
29
|
+
* Pass `items` for the simple data-driven case, or use the `children` slot
|
|
30
|
+
* for full control over the row contents (the component still auto-inserts
|
|
31
|
+
* vertical dividers between top-level children).
|
|
32
|
+
*
|
|
33
|
+
* @component
|
|
34
|
+
* @param {StatGroupProps} props
|
|
35
|
+
*/
|
|
36
|
+
function StatGroup({
|
|
37
|
+
items,
|
|
38
|
+
children,
|
|
39
|
+
modes = EMPTY_MODES,
|
|
40
|
+
style
|
|
41
|
+
}) {
|
|
42
|
+
const background = getVariableByName('statGroup/background', modes) ?? '#ffffff';
|
|
43
|
+
const strokeColor = getVariableByName('statGroup/stroke/color', modes) ?? '#ebebed';
|
|
44
|
+
const strokeSize = getVariableByName('statGroup/stroke/size', modes) ?? 1;
|
|
45
|
+
const radius = getVariableByName('statGroup/radius', modes) ?? 12;
|
|
46
|
+
const paddingTop = getVariableByName('statGroup/padding/top', modes) ?? 16;
|
|
47
|
+
const paddingRight = getVariableByName('statGroup/padding/right', modes) ?? 0;
|
|
48
|
+
const paddingBottom = getVariableByName('statGroup/padding/bottom', modes) ?? 12;
|
|
49
|
+
const paddingLeft = getVariableByName('statGroup/padding/left', modes) ?? 0;
|
|
50
|
+
const containerStyle = {
|
|
51
|
+
backgroundColor: background,
|
|
52
|
+
borderColor: strokeColor,
|
|
53
|
+
borderWidth: strokeSize,
|
|
54
|
+
borderStyle: 'solid',
|
|
55
|
+
borderRadius: radius,
|
|
56
|
+
paddingTop,
|
|
57
|
+
paddingRight,
|
|
58
|
+
paddingBottom,
|
|
59
|
+
paddingLeft,
|
|
60
|
+
overflow: 'hidden'
|
|
61
|
+
};
|
|
62
|
+
const slotChildren = renderSlotChildren({
|
|
63
|
+
items,
|
|
64
|
+
children,
|
|
65
|
+
modes
|
|
66
|
+
});
|
|
67
|
+
return /*#__PURE__*/_jsx(View, {
|
|
68
|
+
style: [containerStyle, style],
|
|
69
|
+
accessibilityRole: "summary",
|
|
70
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
71
|
+
style: {
|
|
72
|
+
flexDirection: 'row',
|
|
73
|
+
alignItems: 'stretch',
|
|
74
|
+
width: '100%'
|
|
75
|
+
},
|
|
76
|
+
children: slotChildren
|
|
77
|
+
})
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Build the row of items: render either the supplied children or the
|
|
83
|
+
* `items` array, wrap each entry so it grows equally, and inject a vertical
|
|
84
|
+
* `Divider` between siblings.
|
|
85
|
+
*/
|
|
86
|
+
function renderSlotChildren({
|
|
87
|
+
items,
|
|
88
|
+
children,
|
|
89
|
+
modes
|
|
90
|
+
}) {
|
|
91
|
+
let nodes;
|
|
92
|
+
if (children !== undefined && children !== null) {
|
|
93
|
+
const cloned = cloneChildrenWithModes(children, modes);
|
|
94
|
+
nodes = flattenChildren(cloned);
|
|
95
|
+
} else {
|
|
96
|
+
const list = items && items.length > 0 ? items : DEFAULT_ITEMS;
|
|
97
|
+
nodes = list.map((item, index) => /*#__PURE__*/_jsx(StatItem, {
|
|
98
|
+
label: item.label,
|
|
99
|
+
value: item.value,
|
|
100
|
+
labelPosition: "Bottom",
|
|
101
|
+
modes: modes
|
|
102
|
+
}, item.key ?? `${item.label ?? 'item'}-${index}`));
|
|
103
|
+
}
|
|
104
|
+
const result = [];
|
|
105
|
+
nodes.forEach((node, index) => {
|
|
106
|
+
if (index > 0) {
|
|
107
|
+
result.push(/*#__PURE__*/_jsx(Divider, {
|
|
108
|
+
direction: "vertical",
|
|
109
|
+
modes: modes
|
|
110
|
+
}, `divider-${index}`));
|
|
111
|
+
}
|
|
112
|
+
result.push(/*#__PURE__*/_jsx(View, {
|
|
113
|
+
style: {
|
|
114
|
+
flex: 1,
|
|
115
|
+
minWidth: 0,
|
|
116
|
+
alignSelf: 'center'
|
|
117
|
+
},
|
|
118
|
+
children: node
|
|
119
|
+
}, `slot-${index}`));
|
|
120
|
+
});
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
export default StatGroup;
|
|
@@ -4,10 +4,14 @@ import React from 'react';
|
|
|
4
4
|
import { View, Text } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
6
|
import { EMPTY_MODES } from '../../utils/react-utils';
|
|
7
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
8
|
/**
|
|
9
|
-
* StatItem displays a label/value pair
|
|
10
|
-
*
|
|
9
|
+
* StatItem displays a label/value pair, useful for product stats, metrics
|
|
10
|
+
* or KPI callouts.
|
|
11
|
+
*
|
|
12
|
+
* Supports two layouts via the `labelPosition` variant:
|
|
13
|
+
* - `'Top'` — Small label above a large prominent value (default).
|
|
14
|
+
* - `'Bottom'` — Value above a smaller label, content centered.
|
|
11
15
|
*
|
|
12
16
|
* @component
|
|
13
17
|
* @param {StatItemProps} props
|
|
@@ -15,43 +19,69 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
15
19
|
function StatItem({
|
|
16
20
|
label = 'Purity verified by NABL',
|
|
17
21
|
value = '99.99%',
|
|
22
|
+
labelPosition = 'Top',
|
|
18
23
|
modes = EMPTY_MODES,
|
|
19
|
-
style
|
|
24
|
+
style,
|
|
25
|
+
labelStyle,
|
|
26
|
+
valueStyle
|
|
20
27
|
}) {
|
|
21
|
-
const
|
|
22
|
-
|
|
28
|
+
const isBottom = labelPosition === 'Bottom';
|
|
29
|
+
|
|
30
|
+
// The Figma `Bottom` variant overrides token values locally inside the
|
|
31
|
+
// component (it is not exposed in the design-tokens JSON), so the resolved
|
|
32
|
+
// token values reflect only the `Top` variant. For the `Bottom` variant we
|
|
33
|
+
// therefore use hardcoded literals that match the Figma design, while still
|
|
34
|
+
// allowing the resolver to surface any future mode-based overrides for the
|
|
35
|
+
// `Top` variant.
|
|
36
|
+
const gap = isBottom ? 6 : getVariableByName('statItem/gap', modes) ?? 2;
|
|
37
|
+
const labelForeground = isBottom ? '#24262b' : getVariableByName('statItem/label/foreground', modes) ?? '#1a1c1f';
|
|
23
38
|
const labelFontFamily = getVariableByName('statItem/label/fontFamily', modes) ?? 'JioType Var';
|
|
24
|
-
const labelFontSize = getVariableByName('statItem/label/fontSize', modes) ?? 12;
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
39
|
+
const labelFontSize = isBottom ? 10 : getVariableByName('statItem/label/fontSize', modes) ?? 12;
|
|
40
|
+
const labelFontWeightRaw = isBottom ? 400 : getVariableByName('statItem/label/fontWeight', modes) ?? 500;
|
|
41
|
+
const labelFontWeight = String(labelFontWeightRaw);
|
|
42
|
+
const labelLineHeight = isBottom ? 13 : getVariableByName('statItem/label/lineHeight', modes) ?? 16;
|
|
43
|
+
const valueForeground = isBottom ? '#141414' : getVariableByName('statItem/value/foreground', modes) ?? '#0d0d0f';
|
|
28
44
|
const valueFontFamily = getVariableByName('statItem/value/fontFamily', modes) ?? 'JioType Var';
|
|
29
|
-
const valueFontSize = getVariableByName('statItem/value/fontSize', modes) ?? 26;
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
const valueFontSize = isBottom ? 12 : getVariableByName('statItem/value/fontSize', modes) ?? 26;
|
|
46
|
+
const valueFontWeightRaw = isBottom ? 500 : getVariableByName('statItem/value/fontWeight', modes) ?? 900;
|
|
47
|
+
const valueFontWeight = String(valueFontWeightRaw);
|
|
48
|
+
const valueLineHeight = isBottom ? 16 : getVariableByName('statItem/value/lineHeight', modes) ?? 26;
|
|
49
|
+
const containerStyle = {
|
|
50
|
+
gap,
|
|
51
|
+
alignItems: isBottom ? 'center' : 'flex-start',
|
|
52
|
+
justifyContent: isBottom ? 'center' : 'flex-start'
|
|
53
|
+
};
|
|
54
|
+
const labelTextStyle = {
|
|
55
|
+
color: labelForeground,
|
|
56
|
+
fontFamily: labelFontFamily,
|
|
57
|
+
fontSize: labelFontSize,
|
|
58
|
+
fontWeight: labelFontWeight,
|
|
59
|
+
lineHeight: labelLineHeight,
|
|
60
|
+
textAlign: isBottom ? 'center' : 'left'
|
|
61
|
+
};
|
|
62
|
+
const valueTextStyle = {
|
|
63
|
+
color: valueForeground,
|
|
64
|
+
fontFamily: valueFontFamily,
|
|
65
|
+
fontSize: valueFontSize,
|
|
66
|
+
fontWeight: valueFontWeight,
|
|
67
|
+
lineHeight: valueLineHeight,
|
|
68
|
+
textAlign: isBottom ? 'center' : 'left'
|
|
69
|
+
};
|
|
70
|
+
const labelNode = /*#__PURE__*/_jsx(Text, {
|
|
71
|
+
style: [labelTextStyle, labelStyle],
|
|
72
|
+
children: label
|
|
73
|
+
});
|
|
74
|
+
const valueNode = /*#__PURE__*/_jsx(Text, {
|
|
75
|
+
style: [valueTextStyle, valueStyle],
|
|
76
|
+
children: value
|
|
77
|
+
});
|
|
78
|
+
return /*#__PURE__*/_jsx(View, {
|
|
79
|
+
style: [containerStyle, style],
|
|
80
|
+
children: isBottom ? /*#__PURE__*/_jsxs(_Fragment, {
|
|
81
|
+
children: [valueNode, labelNode]
|
|
82
|
+
}) : /*#__PURE__*/_jsxs(_Fragment, {
|
|
83
|
+
children: [labelNode, valueNode]
|
|
84
|
+
})
|
|
55
85
|
});
|
|
56
86
|
}
|
|
57
87
|
export default StatItem;
|