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,394 @@
|
|
|
1
|
+
import React, { useCallback, useState } from 'react'
|
|
2
|
+
import { View, type StyleProp, type ViewStyle } from 'react-native'
|
|
3
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
5
|
+
import { EMPTY_MODES } from '../../utils/react-utils'
|
|
6
|
+
import MetricLegendItem from '../MetricLegendItem/MetricLegendItem'
|
|
7
|
+
import SegmentedTrack, {
|
|
8
|
+
type SegmentedTrackSegmentData,
|
|
9
|
+
} from '../SegmentedTrack/SegmentedTrack'
|
|
10
|
+
import Tabs from '../Tabs/Tabs'
|
|
11
|
+
import TabItem from '../Tabs/TabItem'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* One row of data inside a `RangeTrack` tab.
|
|
15
|
+
*
|
|
16
|
+
* Each item drives BOTH a `SegmentedTrack` segment and the matching
|
|
17
|
+
* `MetricLegendItem` row, so the segment color and legend indicator
|
|
18
|
+
* always share the same color by construction (same `Emphasis / DataViz`
|
|
19
|
+
* cascade as the standalone `SegmentedTrack`).
|
|
20
|
+
*/
|
|
21
|
+
export type RangeTrackItem = {
|
|
22
|
+
/** Stable React key. Falls back to the item label / index. */
|
|
23
|
+
key?: React.Key
|
|
24
|
+
/** The descriptive label rendered next to the indicator dot. */
|
|
25
|
+
label?: React.ReactNode
|
|
26
|
+
/**
|
|
27
|
+
* The data point — drives the segment's proportional width. Same
|
|
28
|
+
* semantics as `SegmentedTrackSegmentData.value`. When the legend's
|
|
29
|
+
* right-side text is shown, the resolution order is:
|
|
30
|
+
* `displayValue` → `formatValue(value)` → hide.
|
|
31
|
+
*/
|
|
32
|
+
value: number
|
|
33
|
+
/**
|
|
34
|
+
* Optional override for the legend row's right-side text. Falls
|
|
35
|
+
* back to `formatValue(value)` if a parent-level `formatValue` is
|
|
36
|
+
* provided; otherwise the value slot is hidden (matches the
|
|
37
|
+
* `data` boolean toggle on `MetricLegendItem`). Pass `null` or
|
|
38
|
+
* `false` to explicitly hide on a per-item basis.
|
|
39
|
+
*/
|
|
40
|
+
displayValue?: React.ReactNode
|
|
41
|
+
/**
|
|
42
|
+
* Hard-override the shared color used for both the segment and the
|
|
43
|
+
* legend indicator. Bypasses `dataViz/bg` token resolution.
|
|
44
|
+
*/
|
|
45
|
+
color?: string
|
|
46
|
+
/**
|
|
47
|
+
* Per-item design token mode overrides. Merged on top of parent
|
|
48
|
+
* `modes` and the per-index `Emphasis / DataViz` defaults
|
|
49
|
+
* (`High`, `Medium`, `Low`, then cycles).
|
|
50
|
+
*/
|
|
51
|
+
modes?: Record<string, any>
|
|
52
|
+
/** Accessibility label for the segment + legend row pairing. */
|
|
53
|
+
accessibilityLabel?: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* One tab inside a `RangeTrack`. Each tab carries its own `items`
|
|
58
|
+
* array, which is the single source of truth for both the
|
|
59
|
+
* `SegmentedTrack` segments AND the `MetricLegendItem` legend rows
|
|
60
|
+
* rendered for that tab.
|
|
61
|
+
*/
|
|
62
|
+
export type RangeTrackTab = {
|
|
63
|
+
/** Stable React key. Falls back to the tab label / index. */
|
|
64
|
+
key?: React.Key
|
|
65
|
+
/** The label shown inside the `TabItem` at the top of the component. */
|
|
66
|
+
label: string
|
|
67
|
+
/**
|
|
68
|
+
* Data-driven rows. Each entry produces one `SegmentedTrack`
|
|
69
|
+
* segment AND one matching `MetricLegendItem` legend row — the
|
|
70
|
+
* count of legend rows is locked in step with the count of segments
|
|
71
|
+
* by construction.
|
|
72
|
+
*/
|
|
73
|
+
items: RangeTrackItem[]
|
|
74
|
+
/**
|
|
75
|
+
* Per-tab accessibility label for the tab item button. Falls back
|
|
76
|
+
* to `label`.
|
|
77
|
+
*/
|
|
78
|
+
accessibilityLabel?: string
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type RangeTrackProps = {
|
|
82
|
+
/**
|
|
83
|
+
* Tabs to render across the top. Each tab carries its own `items`
|
|
84
|
+
* array, which is the single source of truth for that tab's
|
|
85
|
+
* segments and legend rows. Switching tabs swaps both the segments
|
|
86
|
+
* and the legend together.
|
|
87
|
+
*/
|
|
88
|
+
tabs: RangeTrackTab[]
|
|
89
|
+
/**
|
|
90
|
+
* Optional formatter applied to each item's numeric `value` when
|
|
91
|
+
* the item does not provide an explicit `displayValue`. Useful for
|
|
92
|
+
* the common "format every weight the same way" case
|
|
93
|
+
* (e.g. `(v) => `${v}%``). Applies to the legend rows of the
|
|
94
|
+
* currently active tab.
|
|
95
|
+
*/
|
|
96
|
+
formatValue?: (value: number) => React.ReactNode
|
|
97
|
+
/**
|
|
98
|
+
* Controlled active tab index. When provided, the component is
|
|
99
|
+
* fully controlled and `onTabChange` should be wired up. When
|
|
100
|
+
* `undefined`, the component manages the active tab internally
|
|
101
|
+
* starting from `defaultActiveTabIndex`.
|
|
102
|
+
*/
|
|
103
|
+
activeTabIndex?: number
|
|
104
|
+
/**
|
|
105
|
+
* Initial active tab index when the component is uncontrolled.
|
|
106
|
+
* Ignored if `activeTabIndex` is provided.
|
|
107
|
+
* @default 0
|
|
108
|
+
*/
|
|
109
|
+
defaultActiveTabIndex?: number
|
|
110
|
+
/**
|
|
111
|
+
* Fired when the user taps a tab. Receives the new index and the
|
|
112
|
+
* matching `RangeTrackTab` from the `tabs` array.
|
|
113
|
+
*/
|
|
114
|
+
onTabChange?: (index: number, tab: RangeTrackTab) => void
|
|
115
|
+
/**
|
|
116
|
+
* When true, the tab row scrolls horizontally — useful when there
|
|
117
|
+
* are many tabs.
|
|
118
|
+
* @default false
|
|
119
|
+
*/
|
|
120
|
+
scrollableTabs?: boolean
|
|
121
|
+
/** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
|
|
122
|
+
modes?: Record<string, any>
|
|
123
|
+
/** Override the outer container styles. */
|
|
124
|
+
style?: StyleProp<ViewStyle>
|
|
125
|
+
/** Override the tab row styles. */
|
|
126
|
+
tabsStyle?: StyleProp<ViewStyle>
|
|
127
|
+
/** Override the `SegmentedTrack` styles. */
|
|
128
|
+
trackStyle?: StyleProp<ViewStyle>
|
|
129
|
+
/** Override the legend list container styles. */
|
|
130
|
+
legendStyle?: StyleProp<ViewStyle>
|
|
131
|
+
/** Accessibility label announced for the whole component. */
|
|
132
|
+
accessibilityLabel?: string
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const DEFAULT_EMPHASIS_CYCLE = ['High', 'Medium', 'Low'] as const
|
|
136
|
+
|
|
137
|
+
const DEFAULT_TABS: RangeTrackTab[] = [
|
|
138
|
+
{
|
|
139
|
+
label: 'Tab item',
|
|
140
|
+
items: [
|
|
141
|
+
{ label: 'Large cap', value: 1, displayValue: '25%' },
|
|
142
|
+
{ label: 'Mid cap', value: 1, displayValue: '25%' },
|
|
143
|
+
{ label: 'Small cap', value: 1, displayValue: '25%' },
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
label: 'Tab item',
|
|
148
|
+
items: [
|
|
149
|
+
{ label: 'Large cap', value: 1, displayValue: '25%' },
|
|
150
|
+
{ label: 'Mid cap', value: 1, displayValue: '25%' },
|
|
151
|
+
{ label: 'Small cap', value: 1, displayValue: '25%' },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
label: 'Tab item',
|
|
156
|
+
items: [
|
|
157
|
+
{ label: 'Large cap', value: 1, displayValue: '25%' },
|
|
158
|
+
{ label: 'Mid cap', value: 1, displayValue: '25%' },
|
|
159
|
+
{ label: 'Small cap', value: 1, displayValue: '25%' },
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
const defaultEmphasisFor = (index: number) =>
|
|
165
|
+
DEFAULT_EMPHASIS_CYCLE[index % DEFAULT_EMPHASIS_CYCLE.length]
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Resolve the shared color for an item. Honors any explicit `color`
|
|
169
|
+
* override, then falls back to `dataViz/bg` for the merged mode set,
|
|
170
|
+
* then to the Figma reference value.
|
|
171
|
+
*
|
|
172
|
+
* Mirrors the cascade used by `SegmentedTrack` (per-index `Emphasis /
|
|
173
|
+
* DataViz` defaults of `High` → `Medium` → `Low`, cycling) so the
|
|
174
|
+
* pre-computed color we pass to both the segment and the legend
|
|
175
|
+
* indicator matches what the `SegmentedTrack` would have computed on
|
|
176
|
+
* its own. This is what keeps segments and legend rows in sync by
|
|
177
|
+
* construction.
|
|
178
|
+
*/
|
|
179
|
+
function resolveItemColor(
|
|
180
|
+
parentModes: Record<string, any>,
|
|
181
|
+
item: RangeTrackItem,
|
|
182
|
+
index: number
|
|
183
|
+
): string {
|
|
184
|
+
if (item.color) return item.color
|
|
185
|
+
const itemModes = {
|
|
186
|
+
...parentModes,
|
|
187
|
+
'Emphasis / DataViz': defaultEmphasisFor(index),
|
|
188
|
+
...(item.modes || {}),
|
|
189
|
+
}
|
|
190
|
+
return (
|
|
191
|
+
(getVariableByName('dataViz/bg', itemModes) as string | null) ?? '#cea15a'
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Resolve what to render in the legend row's right-side slot. The
|
|
197
|
+
* order of precedence is:
|
|
198
|
+
* 1. `item.displayValue` if explicitly provided (including `null` /
|
|
199
|
+
* `false`, which the underlying `MetricLegendItem` treats as
|
|
200
|
+
* "hide the value slot").
|
|
201
|
+
* 2. `formatValue(item.value)` when a parent-level formatter exists.
|
|
202
|
+
* 3. `undefined` — the value slot is hidden.
|
|
203
|
+
*/
|
|
204
|
+
function resolveLegendValue(
|
|
205
|
+
item: RangeTrackItem,
|
|
206
|
+
formatValue: ((value: number) => React.ReactNode) | undefined
|
|
207
|
+
): React.ReactNode {
|
|
208
|
+
if (item.displayValue !== undefined) return item.displayValue
|
|
209
|
+
if (formatValue) return formatValue(item.value)
|
|
210
|
+
return undefined
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* `RangeTrack` pairs a tab row with a `SegmentedTrack` and a vertical
|
|
215
|
+
* stack of `MetricLegendItem` rows. Each tab carries its own `items`
|
|
216
|
+
* array which is the **single source of truth** for both the segments
|
|
217
|
+
* and the legend rows of that tab — every segment has exactly one
|
|
218
|
+
* legend row and they share the same color through the same
|
|
219
|
+
* `Emphasis / DataViz` cascade as the standalone `SegmentedTrack`.
|
|
220
|
+
*
|
|
221
|
+
* Switching tabs swaps the segments and the legend together so the
|
|
222
|
+
* two visualizations can never drift out of sync.
|
|
223
|
+
*
|
|
224
|
+
* The default 3-item layout per tab receives per-index
|
|
225
|
+
* `Emphasis / DataViz` defaults (item 1 → `High`, 2 → `Medium`, 3 →
|
|
226
|
+
* `Low`, then cycles). Override `Appearance / DataViz` on the parent
|
|
227
|
+
* `modes` to retheme the whole component, or set `modes` per item to
|
|
228
|
+
* remix.
|
|
229
|
+
*
|
|
230
|
+
* The component supports both **controlled** and **uncontrolled**
|
|
231
|
+
* tab selection — pass `activeTabIndex` + `onTabChange` for the
|
|
232
|
+
* controlled mode, or omit them and the component will manage the
|
|
233
|
+
* selection internally starting from `defaultActiveTabIndex`.
|
|
234
|
+
*
|
|
235
|
+
* @component
|
|
236
|
+
* @param {RangeTrackProps} props
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```tsx
|
|
240
|
+
* <RangeTrack
|
|
241
|
+
* tabs={[
|
|
242
|
+
* {
|
|
243
|
+
* label: 'Sectoral',
|
|
244
|
+
* items: [
|
|
245
|
+
* { label: 'Large cap', value: 50, displayValue: '50%' },
|
|
246
|
+
* { label: 'Mid cap', value: 30, displayValue: '30%' },
|
|
247
|
+
* { label: 'Small cap', value: 20, displayValue: '20%' },
|
|
248
|
+
* ],
|
|
249
|
+
* },
|
|
250
|
+
* {
|
|
251
|
+
* label: 'Geography',
|
|
252
|
+
* items: [
|
|
253
|
+
* { label: 'India', value: 70, displayValue: '70%' },
|
|
254
|
+
* { label: 'US', value: 20, displayValue: '20%' },
|
|
255
|
+
* { label: 'Other', value: 10, displayValue: '10%' },
|
|
256
|
+
* ],
|
|
257
|
+
* },
|
|
258
|
+
* ]}
|
|
259
|
+
* />
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
function RangeTrack({
|
|
263
|
+
tabs,
|
|
264
|
+
formatValue,
|
|
265
|
+
activeTabIndex,
|
|
266
|
+
defaultActiveTabIndex = 0,
|
|
267
|
+
onTabChange,
|
|
268
|
+
scrollableTabs = false,
|
|
269
|
+
modes: propModes = EMPTY_MODES,
|
|
270
|
+
style,
|
|
271
|
+
tabsStyle,
|
|
272
|
+
trackStyle,
|
|
273
|
+
legendStyle,
|
|
274
|
+
accessibilityLabel,
|
|
275
|
+
}: RangeTrackProps) {
|
|
276
|
+
const { modes: globalModes } = useTokens()
|
|
277
|
+
const modes = { ...globalModes, ...propModes }
|
|
278
|
+
|
|
279
|
+
const resolvedTabs = tabs && tabs.length > 0 ? tabs : DEFAULT_TABS
|
|
280
|
+
|
|
281
|
+
const [internalIndex, setInternalIndex] = useState(() =>
|
|
282
|
+
clampIndex(defaultActiveTabIndex, resolvedTabs.length)
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
const isControlled = activeTabIndex !== undefined
|
|
286
|
+
const rawIndex = isControlled ? activeTabIndex : internalIndex
|
|
287
|
+
const safeIndex = clampIndex(rawIndex, resolvedTabs.length)
|
|
288
|
+
|
|
289
|
+
const handleTabPress = useCallback(
|
|
290
|
+
(index: number) => {
|
|
291
|
+
const nextTab = resolvedTabs[index]
|
|
292
|
+
if (!nextTab) return
|
|
293
|
+
if (!isControlled) setInternalIndex(index)
|
|
294
|
+
onTabChange?.(index, nextTab)
|
|
295
|
+
},
|
|
296
|
+
[isControlled, onTabChange, resolvedTabs]
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
const containerGap =
|
|
300
|
+
(getVariableByName('rangeTrack/gap', modes) as number | null) ?? 28
|
|
301
|
+
// Vertical gap between legend rows is not exposed as its own token —
|
|
302
|
+
// the Figma design uses 8px between rows, mirroring the
|
|
303
|
+
// `donutChartSummary/legend/gap` default. Keep the value in step
|
|
304
|
+
// with `DonutChartSummary` so the two summary components feel
|
|
305
|
+
// cohesive when stacked.
|
|
306
|
+
const legendRowGap = 8
|
|
307
|
+
|
|
308
|
+
const activeTab = resolvedTabs[safeIndex]
|
|
309
|
+
const activeItems = activeTab?.items ?? []
|
|
310
|
+
|
|
311
|
+
const segments: SegmentedTrackSegmentData[] = activeItems.map(
|
|
312
|
+
(item, index) => ({
|
|
313
|
+
key: item.key ?? `segment-${index}`,
|
|
314
|
+
value: item.value,
|
|
315
|
+
color: resolveItemColor(modes, item, index),
|
|
316
|
+
accessibilityLabel: item.accessibilityLabel,
|
|
317
|
+
})
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
return (
|
|
321
|
+
<View
|
|
322
|
+
accessibilityRole="summary"
|
|
323
|
+
accessibilityLabel={accessibilityLabel}
|
|
324
|
+
style={[
|
|
325
|
+
{
|
|
326
|
+
width: '100%',
|
|
327
|
+
flexDirection: 'column',
|
|
328
|
+
alignItems: 'flex-start',
|
|
329
|
+
gap: containerGap,
|
|
330
|
+
},
|
|
331
|
+
style,
|
|
332
|
+
]}
|
|
333
|
+
>
|
|
334
|
+
<Tabs
|
|
335
|
+
modes={modes}
|
|
336
|
+
scrollable={scrollableTabs}
|
|
337
|
+
style={tabsStyle}
|
|
338
|
+
>
|
|
339
|
+
{resolvedTabs.map((tab, index) => (
|
|
340
|
+
<TabItem
|
|
341
|
+
key={tab.key ?? tab.label ?? `tab-${index}`}
|
|
342
|
+
label={tab.label}
|
|
343
|
+
active={index === safeIndex}
|
|
344
|
+
onPress={() => handleTabPress(index)}
|
|
345
|
+
accessibilityLabel={tab.accessibilityLabel ?? tab.label}
|
|
346
|
+
/>
|
|
347
|
+
))}
|
|
348
|
+
</Tabs>
|
|
349
|
+
|
|
350
|
+
<SegmentedTrack
|
|
351
|
+
modes={modes}
|
|
352
|
+
segments={segments}
|
|
353
|
+
style={trackStyle}
|
|
354
|
+
accessibilityLabel={activeTab?.accessibilityLabel ?? activeTab?.label}
|
|
355
|
+
/>
|
|
356
|
+
|
|
357
|
+
<View
|
|
358
|
+
style={[
|
|
359
|
+
{
|
|
360
|
+
width: '100%',
|
|
361
|
+
flexDirection: 'column',
|
|
362
|
+
alignItems: 'flex-start',
|
|
363
|
+
gap: legendRowGap,
|
|
364
|
+
},
|
|
365
|
+
legendStyle,
|
|
366
|
+
]}
|
|
367
|
+
>
|
|
368
|
+
{activeItems.map((item, index) => (
|
|
369
|
+
<MetricLegendItem
|
|
370
|
+
key={item.key ?? `legend-${index}`}
|
|
371
|
+
label={item.label}
|
|
372
|
+
value={resolveLegendValue(item, formatValue)}
|
|
373
|
+
indicatorColor={resolveItemColor(modes, item, index)}
|
|
374
|
+
modes={modes}
|
|
375
|
+
/>
|
|
376
|
+
))}
|
|
377
|
+
</View>
|
|
378
|
+
</View>
|
|
379
|
+
)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Clamp a tab index into `[0, length)`. Negative or out-of-bounds
|
|
384
|
+
* values fall back to `0` to avoid rendering an undefined tab.
|
|
385
|
+
*/
|
|
386
|
+
function clampIndex(index: number, length: number): number {
|
|
387
|
+
if (length <= 0) return 0
|
|
388
|
+
if (!Number.isFinite(index)) return 0
|
|
389
|
+
if (index < 0) return 0
|
|
390
|
+
if (index >= length) return length - 1
|
|
391
|
+
return Math.floor(index)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export default RangeTrack
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
type StyleProp,
|
|
5
|
+
type ViewStyle,
|
|
6
|
+
type TextStyle,
|
|
7
|
+
} from 'react-native'
|
|
8
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
9
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
10
|
+
import { formatIndianNumber } from '../../utils/number-utils'
|
|
11
|
+
import Title from '../Title/Title'
|
|
12
|
+
import LinearProgress from '../LinearProgress/LinearProgress'
|
|
13
|
+
import MetricLegendItem from '../MetricLegendItem/MetricLegendItem'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A single row in the savings-goal legend (current vs. target).
|
|
17
|
+
*
|
|
18
|
+
* `value` is a **numeric amount** (e.g. `240000`). It serves two purposes:
|
|
19
|
+
*
|
|
20
|
+
* 1. Display: rendered on the right side of the row using Indian numeric
|
|
21
|
+
* notation via {@link formatIndianNumber} and prefixed with `currency`
|
|
22
|
+
* (e.g. `240000` → `"₹2.4L"`).
|
|
23
|
+
* 2. Progress derivation: the bar fills to `current.value / target.value`
|
|
24
|
+
* automatically. There is no separate `progress` prop.
|
|
25
|
+
*
|
|
26
|
+
* Pass `value: undefined` to render a label-only row (the underlying
|
|
27
|
+
* {@link MetricLegendItem} hides the right slot in that case).
|
|
28
|
+
*/
|
|
29
|
+
export type SavingsGoalSummaryItem = {
|
|
30
|
+
/** Label shown next to the indicator dot (e.g. "Current (4 months)"). */
|
|
31
|
+
label?: React.ReactNode
|
|
32
|
+
/**
|
|
33
|
+
* Numeric amount used for both display formatting and progress derivation.
|
|
34
|
+
* Use `undefined` to render a label-only row.
|
|
35
|
+
*/
|
|
36
|
+
value?: number
|
|
37
|
+
/** Override the indicator dot colour. */
|
|
38
|
+
indicatorColor?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type SavingsGoalSummaryProps = {
|
|
42
|
+
/**
|
|
43
|
+
* Currency symbol (e.g. `'₹'`, `'$'`) prefixed to every legend value.
|
|
44
|
+
* Defaults to `'₹'`. The symbol is rendered as part of the formatted
|
|
45
|
+
* string — this component does not use `MoneyValue`.
|
|
46
|
+
*/
|
|
47
|
+
currency?: string
|
|
48
|
+
/**
|
|
49
|
+
* "Current" row in the legend. The indicator dot defaults to the
|
|
50
|
+
* `LinearProgress` indicator colour. Pass `null` to hide the row.
|
|
51
|
+
*
|
|
52
|
+
* `current.value` (numeric) drives both the displayed amount and — together
|
|
53
|
+
* with `target.value` — the progress bar fill.
|
|
54
|
+
*/
|
|
55
|
+
current?: SavingsGoalSummaryItem | null
|
|
56
|
+
/**
|
|
57
|
+
* "Target" / recommended row in the legend. The indicator dot defaults to
|
|
58
|
+
* the `LinearProgress` track colour. Pass `null` to hide the row.
|
|
59
|
+
*/
|
|
60
|
+
target?: SavingsGoalSummaryItem | null
|
|
61
|
+
/**
|
|
62
|
+
* Custom legend slot. When provided, replaces the default `current`/`target`
|
|
63
|
+
* rows entirely — pass any number of `MetricLegendItem`s (or any other
|
|
64
|
+
* nodes) to fully control the legend.
|
|
65
|
+
*
|
|
66
|
+
* Note: the progress bar is still derived from `current.value` / `target.value`,
|
|
67
|
+
* so pass them even when overriding the legend if you want a non-zero bar.
|
|
68
|
+
*/
|
|
69
|
+
children?: React.ReactNode
|
|
70
|
+
/**
|
|
71
|
+
* Design token modes for theming. Defaults to `{ 'LinearProgress Size': 'L' }`
|
|
72
|
+
* which renders the thicker progress bar from the Figma reference. Caller
|
|
73
|
+
* modes are merged on top and can override every default key.
|
|
74
|
+
*/
|
|
75
|
+
modes?: Record<string, any>
|
|
76
|
+
/** Override container styles. */
|
|
77
|
+
style?: StyleProp<ViewStyle>
|
|
78
|
+
/**
|
|
79
|
+
* Override styles for the auto-generated percentage text. Useful for
|
|
80
|
+
* tweaking colour or alignment; the value (e.g. `"50%"`) is always
|
|
81
|
+
* computed from `current` / `target` and cannot be replaced.
|
|
82
|
+
*/
|
|
83
|
+
titleStyle?: StyleProp<TextStyle>
|
|
84
|
+
/** Override the inner legend container styles. */
|
|
85
|
+
legendStyle?: StyleProp<ViewStyle>
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const DEFAULT_LEGEND_PADDING = 8
|
|
89
|
+
|
|
90
|
+
const DEFAULT_MODES: Readonly<Record<string, any>> = Object.freeze({
|
|
91
|
+
'LinearProgress Size': 'L',
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const DEFAULT_CURRENT: SavingsGoalSummaryItem = {
|
|
95
|
+
label: 'Current (4 months)',
|
|
96
|
+
value: 240000,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const DEFAULT_TARGET: SavingsGoalSummaryItem = {
|
|
100
|
+
label: 'Recommended (8 months)',
|
|
101
|
+
value: 480000,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* `SavingsGoalSummary` visualises progress toward a savings goal as:
|
|
106
|
+
*
|
|
107
|
+
* 1. A `Title` showing the percentage — **computed automatically** from
|
|
108
|
+
* `current.value / target.value`. There is no `progress` prop; the
|
|
109
|
+
* component owns the calculation so the title, bar and legend are always
|
|
110
|
+
* in sync.
|
|
111
|
+
* 2. A `LinearProgress` bar driven by the same derived ratio.
|
|
112
|
+
* 3. A two-row legend comparing **current** vs. **target**, where each numeric
|
|
113
|
+
* `value` is auto-formatted with Indian notation
|
|
114
|
+
* ({@link formatIndianNumber}) and prefixed with the `currency` symbol.
|
|
115
|
+
*
|
|
116
|
+
* The component is intentionally narrow in scope — it is the body of a savings
|
|
117
|
+
* insight card. Wrap it in `CardInsight` (or any container of your choice) to
|
|
118
|
+
* add a heading, badge or footer.
|
|
119
|
+
*
|
|
120
|
+
* @component
|
|
121
|
+
* @param {SavingsGoalSummaryProps} props
|
|
122
|
+
*/
|
|
123
|
+
function SavingsGoalSummary({
|
|
124
|
+
currency = '₹',
|
|
125
|
+
current = DEFAULT_CURRENT,
|
|
126
|
+
target = DEFAULT_TARGET,
|
|
127
|
+
children,
|
|
128
|
+
modes = EMPTY_MODES,
|
|
129
|
+
style,
|
|
130
|
+
titleStyle,
|
|
131
|
+
legendStyle,
|
|
132
|
+
}: SavingsGoalSummaryProps) {
|
|
133
|
+
// Merge caller modes on top of the defaults so callers can override
|
|
134
|
+
// (e.g. switch to `LinearProgress Size: M`) while still receiving the
|
|
135
|
+
// sensible component-level default.
|
|
136
|
+
const mergedModes = useMemo(
|
|
137
|
+
() => (modes === EMPTY_MODES ? DEFAULT_MODES : { ...DEFAULT_MODES, ...modes }),
|
|
138
|
+
[modes],
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
// Resolve the `LinearProgress` track / indicator colours so the legend
|
|
142
|
+
// dots automatically stay in sync with the progress bar without callers
|
|
143
|
+
// needing to plumb through `indicatorColor`.
|
|
144
|
+
//
|
|
145
|
+
// The token names AND the merge strategy must match `LinearProgress.tsx`
|
|
146
|
+
// exactly, otherwise the dot colours can drift from the bar:
|
|
147
|
+
// • Token aliases live in the `Emphasis` collection (modes High|Medium|Low),
|
|
148
|
+
// NOT `Emphasis / DataViz` — passing the wrong mode key would collapse
|
|
149
|
+
// both dots to the same default-mode colour.
|
|
150
|
+
// • Defaults are placed *before* `mergedModes` is spread, so callers can
|
|
151
|
+
// still override `Emphasis` via the `modes` prop (matches
|
|
152
|
+
// `LinearProgress`'s philosophy).
|
|
153
|
+
const indicatorColorFromTokens =
|
|
154
|
+
(getVariableByName('linearProgress/indicator/background', {
|
|
155
|
+
Emphasis: 'High',
|
|
156
|
+
...mergedModes,
|
|
157
|
+
}) as string | null) ?? '#5d00b5'
|
|
158
|
+
const trackColorFromTokens =
|
|
159
|
+
(getVariableByName('linearProgress/track/background', {
|
|
160
|
+
Emphasis: 'Low',
|
|
161
|
+
...mergedModes,
|
|
162
|
+
}) as string | null) ?? '#ede7ff'
|
|
163
|
+
|
|
164
|
+
// Single source of truth for the bar fill, the title percentage and the
|
|
165
|
+
// formatted legend amounts. There is intentionally no consumer-facing
|
|
166
|
+
// `progress` prop — the only way to change the bar is to change the
|
|
167
|
+
// numeric `current` / `target` values. This keeps the three views (title,
|
|
168
|
+
// bar, legend) impossible to desynchronise.
|
|
169
|
+
const resolvedProgress = useMemo(() => {
|
|
170
|
+
const cv = current?.value
|
|
171
|
+
const tv = target?.value
|
|
172
|
+
if (typeof cv !== 'number' || typeof tv !== 'number' || tv <= 0) {
|
|
173
|
+
return 0
|
|
174
|
+
}
|
|
175
|
+
return Math.min(Math.max(cv / tv, 0), 1)
|
|
176
|
+
}, [current, target])
|
|
177
|
+
|
|
178
|
+
const percentageLabel = `${Math.round(resolvedProgress * 100)}%`
|
|
179
|
+
|
|
180
|
+
const gap = (getVariableByName('savingsGoalSummary/gap', mergedModes) as number | null) ?? 23
|
|
181
|
+
const legendGap =
|
|
182
|
+
(getVariableByName('savingsGoalSummary/legend/gap', mergedModes) as number | null) ?? 16
|
|
183
|
+
|
|
184
|
+
const customLegend = children
|
|
185
|
+
? cloneChildrenWithModes(children, mergedModes)
|
|
186
|
+
: null
|
|
187
|
+
|
|
188
|
+
const defaultLegend = !customLegend && (current || target) ? (
|
|
189
|
+
<>
|
|
190
|
+
{current && (
|
|
191
|
+
<MetricLegendItem
|
|
192
|
+
modes={mergedModes}
|
|
193
|
+
label={current.label}
|
|
194
|
+
value={formatLegendValue(current.value, currency)}
|
|
195
|
+
indicatorColor={current.indicatorColor ?? indicatorColorFromTokens}
|
|
196
|
+
/>
|
|
197
|
+
)}
|
|
198
|
+
{target && (
|
|
199
|
+
<MetricLegendItem
|
|
200
|
+
modes={mergedModes}
|
|
201
|
+
label={target.label}
|
|
202
|
+
value={formatLegendValue(target.value, currency)}
|
|
203
|
+
indicatorColor={target.indicatorColor ?? trackColorFromTokens}
|
|
204
|
+
/>
|
|
205
|
+
)}
|
|
206
|
+
</>
|
|
207
|
+
) : null
|
|
208
|
+
|
|
209
|
+
const legendNode = customLegend ?? defaultLegend
|
|
210
|
+
const showLegend = legendNode != null
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<View
|
|
214
|
+
style={[
|
|
215
|
+
{
|
|
216
|
+
width: '100%',
|
|
217
|
+
gap,
|
|
218
|
+
alignItems: 'stretch',
|
|
219
|
+
},
|
|
220
|
+
style,
|
|
221
|
+
]}
|
|
222
|
+
accessibilityLabel={`Savings goal progress, ${percentageLabel}`}
|
|
223
|
+
>
|
|
224
|
+
<Title
|
|
225
|
+
title={percentageLabel}
|
|
226
|
+
modes={mergedModes}
|
|
227
|
+
style={TITLE_CONTAINER_STYLE}
|
|
228
|
+
textStyle={titleStyle}
|
|
229
|
+
/>
|
|
230
|
+
|
|
231
|
+
<LinearProgress value={resolvedProgress} modes={mergedModes} />
|
|
232
|
+
|
|
233
|
+
{showLegend && (
|
|
234
|
+
<View
|
|
235
|
+
style={[
|
|
236
|
+
{
|
|
237
|
+
width: '100%',
|
|
238
|
+
padding: DEFAULT_LEGEND_PADDING,
|
|
239
|
+
gap: legendGap,
|
|
240
|
+
alignItems: 'stretch',
|
|
241
|
+
},
|
|
242
|
+
legendStyle,
|
|
243
|
+
]}
|
|
244
|
+
>
|
|
245
|
+
{legendNode}
|
|
246
|
+
</View>
|
|
247
|
+
)}
|
|
248
|
+
</View>
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Format a single legend `value` for display. Returns `undefined` when the
|
|
254
|
+
* value is missing so the underlying {@link MetricLegendItem} hides the right
|
|
255
|
+
* slot (matches the Figma `data` toggle = off).
|
|
256
|
+
*/
|
|
257
|
+
function formatLegendValue(value: number | undefined, currency: string): string | undefined {
|
|
258
|
+
if (typeof value !== 'number') return undefined
|
|
259
|
+
return formatIndianNumber(value, { prefix: currency })
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Neutralise the `Title` component's default page-level padding so it sits
|
|
263
|
+
// flush inside the summary card (the parent container owns spacing via `gap`).
|
|
264
|
+
const TITLE_CONTAINER_STYLE: ViewStyle = {
|
|
265
|
+
paddingHorizontal: 0,
|
|
266
|
+
paddingVertical: 0,
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export default SavingsGoalSummary
|