jfs-components 0.0.72 → 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 +11 -0
- package/lib/commonjs/components/AccordionCheckbox/AccordionCheckbox.js +239 -0
- package/lib/commonjs/components/BrandChip/BrandChip.js +149 -0
- package/lib/commonjs/components/CardBankAccount/CardBankAccount.js +213 -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 +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/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/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 +171 -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/CardBankAccount/CardBankAccount.js +208 -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 +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/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/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 +21 -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/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/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/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 +22 -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/CardBankAccount/CardBankAccount.tsx +295 -0
- package/src/components/CardInsight/CardInsight.tsx +239 -0
- 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/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/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 +32 -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,503 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
StyleSheet,
|
|
4
|
+
Text,
|
|
5
|
+
View,
|
|
6
|
+
type StyleProp,
|
|
7
|
+
type TextStyle,
|
|
8
|
+
type ViewStyle,
|
|
9
|
+
} from 'react-native'
|
|
10
|
+
import Svg, { Circle } from 'react-native-svg'
|
|
11
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
12
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
13
|
+
import { EMPTY_MODES, flattenChildren } from '../../utils/react-utils'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Per-segment data definition for the data-driven `segments` prop.
|
|
17
|
+
*
|
|
18
|
+
* Each segment renders one arc on the donut ring. `value` controls the
|
|
19
|
+
* arc's angular share (its weight relative to the sum of all values).
|
|
20
|
+
* Use `modes` per segment to override the default `Appearance / DataViz`
|
|
21
|
+
* mode (the parent injects per-index defaults of `Senary`, `Primary`,
|
|
22
|
+
* `Secondary`, `Tertiary`, `Quaternary`, `Quinary`, then cycles).
|
|
23
|
+
*/
|
|
24
|
+
export type DonutChartSegmentData = {
|
|
25
|
+
/** Stable React key. */
|
|
26
|
+
key?: React.Key
|
|
27
|
+
/** Segment's angular weight (relative to the sum of all segment values). */
|
|
28
|
+
value: number
|
|
29
|
+
/** Hard-override the segment color. Bypasses `dataViz/bg` token resolution. */
|
|
30
|
+
color?: string
|
|
31
|
+
/**
|
|
32
|
+
* Per-segment design token mode overrides. Merged on top of parent
|
|
33
|
+
* `modes` and the per-index `Appearance / DataViz` defaults.
|
|
34
|
+
*/
|
|
35
|
+
modes?: Record<string, any>
|
|
36
|
+
/** Per-segment accessibility label. */
|
|
37
|
+
accessibilityLabel?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type DonutChartProps = {
|
|
41
|
+
/**
|
|
42
|
+
* Data-driven segment list. Used when no `children` slot is provided.
|
|
43
|
+
* Defaults to six equal segments themed across the
|
|
44
|
+
* `Appearance / DataViz` palette.
|
|
45
|
+
*/
|
|
46
|
+
segments?: DonutChartSegmentData[]
|
|
47
|
+
/**
|
|
48
|
+
* Optional center value text (large/bold). Pass `null`/`false` to hide.
|
|
49
|
+
* Accepts a string or any `ReactNode` for full control.
|
|
50
|
+
*/
|
|
51
|
+
value?: React.ReactNode
|
|
52
|
+
/**
|
|
53
|
+
* Optional center label text (smaller, beneath the value). Pass
|
|
54
|
+
* `null`/`false` to hide. Accepts a string or any `ReactNode`.
|
|
55
|
+
*/
|
|
56
|
+
label?: React.ReactNode
|
|
57
|
+
/**
|
|
58
|
+
* Slot for custom center content. When provided, it replaces the
|
|
59
|
+
* default `value`/`label` text wrap entirely.
|
|
60
|
+
*/
|
|
61
|
+
children?: React.ReactNode
|
|
62
|
+
/** Outer diameter in px. Defaults to 194 (matching the Figma reference). */
|
|
63
|
+
size?: number
|
|
64
|
+
/**
|
|
65
|
+
* Ring stroke width in px. Defaults to 18% of `size` so the donut keeps
|
|
66
|
+
* the same visual proportions across sizes.
|
|
67
|
+
*/
|
|
68
|
+
strokeWidth?: number
|
|
69
|
+
/**
|
|
70
|
+
* Visual gap between adjacent segments expressed in degrees of arc.
|
|
71
|
+
* Defaults to 0 (segments touch). Use small values (1–4°) for
|
|
72
|
+
* separation when many segments are present.
|
|
73
|
+
*/
|
|
74
|
+
gap?: number
|
|
75
|
+
/** Design token modes for theming (e.g. `{ 'Color Mode': 'Light' }`). */
|
|
76
|
+
modes?: Record<string, any>
|
|
77
|
+
/** Override container styles. */
|
|
78
|
+
style?: StyleProp<ViewStyle>
|
|
79
|
+
/** Override default value text styles. */
|
|
80
|
+
valueStyle?: StyleProp<TextStyle>
|
|
81
|
+
/** Override default label text styles. */
|
|
82
|
+
labelStyle?: StyleProp<TextStyle>
|
|
83
|
+
/** Accessibility label announced for the whole donut chart. */
|
|
84
|
+
accessibilityLabel?: string
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const DEFAULT_APPEARANCE_CYCLE = [
|
|
88
|
+
'Senary',
|
|
89
|
+
'Primary',
|
|
90
|
+
'Secondary',
|
|
91
|
+
'Tertiary',
|
|
92
|
+
'Quaternary',
|
|
93
|
+
'Quinary',
|
|
94
|
+
] as const
|
|
95
|
+
|
|
96
|
+
const DEFAULT_SEGMENTS: DonutChartSegmentData[] = [
|
|
97
|
+
{ value: 1 },
|
|
98
|
+
{ value: 1 },
|
|
99
|
+
{ value: 1 },
|
|
100
|
+
{ value: 1 },
|
|
101
|
+
{ value: 1 },
|
|
102
|
+
{ value: 1 },
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
const STROKE_WIDTH_RATIO = 18 / 194
|
|
106
|
+
|
|
107
|
+
const toNumber = (value: unknown, fallback: number) => {
|
|
108
|
+
if (typeof value === 'number') {
|
|
109
|
+
return Number.isFinite(value) ? value : fallback
|
|
110
|
+
}
|
|
111
|
+
if (typeof value === 'string') {
|
|
112
|
+
const parsed = Number(value)
|
|
113
|
+
return Number.isFinite(parsed) ? parsed : fallback
|
|
114
|
+
}
|
|
115
|
+
return fallback
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']) => {
|
|
119
|
+
if (typeof value === 'number') {
|
|
120
|
+
return String(value) as TextStyle['fontWeight']
|
|
121
|
+
}
|
|
122
|
+
if (typeof value === 'string') {
|
|
123
|
+
return value as TextStyle['fontWeight']
|
|
124
|
+
}
|
|
125
|
+
return fallback
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Compute the default `Appearance / DataViz` mode for a segment at `index`.
|
|
130
|
+
* Cycles through {@link DEFAULT_APPEARANCE_CYCLE} so any number of segments
|
|
131
|
+
* gets a sensible default (Senary, Primary, Secondary, Tertiary,
|
|
132
|
+
* Quaternary, Quinary, then repeats).
|
|
133
|
+
*/
|
|
134
|
+
function defaultAppearanceFor(index: number) {
|
|
135
|
+
return DEFAULT_APPEARANCE_CYCLE[index % DEFAULT_APPEARANCE_CYCLE.length]
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Resolve a single segment's stroke color through the design tokens. Honors
|
|
140
|
+
* any explicit `color` override, then falls back to `dataViz/bg` for the
|
|
141
|
+
* supplied `modes`, then to the Figma reference purple.
|
|
142
|
+
*/
|
|
143
|
+
function resolveSegmentColor(
|
|
144
|
+
color: string | undefined,
|
|
145
|
+
modes: Record<string, any>
|
|
146
|
+
): string {
|
|
147
|
+
if (color) return color
|
|
148
|
+
return ((getVariableByName('dataViz/bg', modes) as string | null) ?? '#5d00b5')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Coerce a slot child into a `DonutChartSegmentData` so the rendering path
|
|
153
|
+
* is uniform. Reads `value`, `color`, `modes` and `accessibilityLabel`
|
|
154
|
+
* from the child's props and ignores anything else.
|
|
155
|
+
*/
|
|
156
|
+
function segmentDataFromChild(
|
|
157
|
+
child: React.ReactElement,
|
|
158
|
+
fallbackKey: React.Key
|
|
159
|
+
): DonutChartSegmentData {
|
|
160
|
+
const props = (child.props as any) ?? {}
|
|
161
|
+
return {
|
|
162
|
+
key: child.key ?? fallbackKey,
|
|
163
|
+
value: toNumber(props.value, 1),
|
|
164
|
+
color: typeof props.color === 'string' ? props.color : undefined,
|
|
165
|
+
modes: props.modes,
|
|
166
|
+
accessibilityLabel: props.accessibilityLabel,
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
type DonutChartSegmentProps = {
|
|
171
|
+
/** Angular weight relative to the sum of sibling segment values. */
|
|
172
|
+
value: number
|
|
173
|
+
/** Hard-override segment color (bypasses tokens). */
|
|
174
|
+
color?: string
|
|
175
|
+
/**
|
|
176
|
+
* Design token modes for the segment. Merged with parent `modes` and
|
|
177
|
+
* the per-index `Appearance / DataViz` defaults.
|
|
178
|
+
*/
|
|
179
|
+
modes?: Record<string, any>
|
|
180
|
+
/** Per-segment accessibility label. */
|
|
181
|
+
accessibilityLabel?: string
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Inert helper used purely to author donut segments declaratively as
|
|
186
|
+
* `DonutChart.Segment` slot children. The parent reads its props and
|
|
187
|
+
* renders the actual SVG arcs — this component never renders anything by
|
|
188
|
+
* itself.
|
|
189
|
+
*/
|
|
190
|
+
function DonutChartSegment(_: DonutChartSegmentProps) {
|
|
191
|
+
return null
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* `DonutChart` renders a circular ring split into categorical segments.
|
|
196
|
+
* It is the segmented counterpart of `CircularProgressBar`: there is **no
|
|
197
|
+
* track background** behind the segments — the colored ring *is* the
|
|
198
|
+
* data, and each slice is a sibling category rather than a directional or
|
|
199
|
+
* temporal value.
|
|
200
|
+
*
|
|
201
|
+
* The default 6-segment layout receives per-index `Appearance / DataViz`
|
|
202
|
+
* defaults (segment 1 → `Senary`, 2 → `Primary`, 3 → `Secondary`, 4 →
|
|
203
|
+
* `Tertiary`, 5 → `Quaternary`, 6 → `Quinary`) so the wheel reads as one
|
|
204
|
+
* concept split into six color-coded categories. Override `modes` per
|
|
205
|
+
* segment to remix appearances, or set `Emphasis / DataViz` on the
|
|
206
|
+
* parent `modes` to dim or brighten the whole wheel at once.
|
|
207
|
+
*
|
|
208
|
+
* @component
|
|
209
|
+
* @param {DonutChartProps} props
|
|
210
|
+
*/
|
|
211
|
+
function DonutChart({
|
|
212
|
+
segments,
|
|
213
|
+
value,
|
|
214
|
+
label,
|
|
215
|
+
children,
|
|
216
|
+
size = 194,
|
|
217
|
+
strokeWidth: strokeWidthProp,
|
|
218
|
+
gap = 0,
|
|
219
|
+
modes: propModes = EMPTY_MODES,
|
|
220
|
+
style,
|
|
221
|
+
valueStyle,
|
|
222
|
+
labelStyle,
|
|
223
|
+
accessibilityLabel,
|
|
224
|
+
}: DonutChartProps) {
|
|
225
|
+
const { modes: globalModes } = useTokens()
|
|
226
|
+
const modes = { ...globalModes, ...propModes }
|
|
227
|
+
|
|
228
|
+
const strokeWidth = Math.max(
|
|
229
|
+
1,
|
|
230
|
+
toNumber(strokeWidthProp, Math.max(1, size * STROKE_WIDTH_RATIO))
|
|
231
|
+
)
|
|
232
|
+
const radius = Math.max(0, (size - strokeWidth) / 2)
|
|
233
|
+
const center = size / 2
|
|
234
|
+
const circumference = 2 * Math.PI * radius
|
|
235
|
+
const gapLength = (Math.max(0, gap) / 360) * circumference
|
|
236
|
+
|
|
237
|
+
const slotChildren = flattenChildren(children).filter((child) =>
|
|
238
|
+
React.isValidElement(child)
|
|
239
|
+
) as React.ReactElement[]
|
|
240
|
+
|
|
241
|
+
const slotSegmentChildren = slotChildren.filter(
|
|
242
|
+
(child) => child.type === DonutChartSegment
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
let resolvedSegments: DonutChartSegmentData[]
|
|
246
|
+
if (slotSegmentChildren.length > 0) {
|
|
247
|
+
resolvedSegments = slotSegmentChildren.map((child, index) =>
|
|
248
|
+
segmentDataFromChild(child, `segment-${index}`)
|
|
249
|
+
)
|
|
250
|
+
} else if (segments && segments.length > 0) {
|
|
251
|
+
resolvedSegments = segments
|
|
252
|
+
} else {
|
|
253
|
+
resolvedSegments = DEFAULT_SEGMENTS
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const totalValue = resolvedSegments.reduce(
|
|
257
|
+
(sum, segment) => sum + Math.max(0, toNumber(segment.value, 0)),
|
|
258
|
+
0
|
|
259
|
+
)
|
|
260
|
+
const hasData = totalValue > 0 && resolvedSegments.length > 0
|
|
261
|
+
|
|
262
|
+
const arcs = hasData
|
|
263
|
+
? buildArcs({
|
|
264
|
+
segments: resolvedSegments,
|
|
265
|
+
totalValue,
|
|
266
|
+
circumference,
|
|
267
|
+
gapLength,
|
|
268
|
+
modes,
|
|
269
|
+
})
|
|
270
|
+
: []
|
|
271
|
+
|
|
272
|
+
const customCenterChildren = slotChildren.filter(
|
|
273
|
+
(child) => child.type !== DonutChartSegment
|
|
274
|
+
)
|
|
275
|
+
const hasChildrenSlot = customCenterChildren.length > 0
|
|
276
|
+
|
|
277
|
+
const showValueLabel = !hasChildrenSlot && shouldRenderText(value)
|
|
278
|
+
const showLabel = !hasChildrenSlot && shouldRenderText(label)
|
|
279
|
+
|
|
280
|
+
const valueColor =
|
|
281
|
+
(getVariableByName('donutChart/value/color', modes) as string | null) ??
|
|
282
|
+
(getVariableByName('value/fg', modes) as string | null) ??
|
|
283
|
+
'#000000'
|
|
284
|
+
const valueFontFamily =
|
|
285
|
+
(getVariableByName('donutChart/value/fontFamily', modes) as string | null) ??
|
|
286
|
+
(getVariableByName('value/fontFamily', modes) as string | null) ??
|
|
287
|
+
'JioType Var'
|
|
288
|
+
const valueFontSize = toNumber(
|
|
289
|
+
getVariableByName('donutChart/value/fontSize', modes) ??
|
|
290
|
+
getVariableByName('value/fontSize', modes),
|
|
291
|
+
29
|
|
292
|
+
)
|
|
293
|
+
const valueLineHeight = toNumber(
|
|
294
|
+
getVariableByName('donutChart/value/lineHeight', modes) ??
|
|
295
|
+
getVariableByName('value/lineHeight', modes),
|
|
296
|
+
29
|
|
297
|
+
)
|
|
298
|
+
const valueFontWeight = toFontWeight(
|
|
299
|
+
getVariableByName('donutChart/value/fontWeight', modes) ??
|
|
300
|
+
getVariableByName('value/fontWeight', modes),
|
|
301
|
+
'900'
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
const labelColor =
|
|
305
|
+
(getVariableByName('donutChart/label/color', modes) as string | null) ??
|
|
306
|
+
(getVariableByName('label/fg', modes) as string | null) ??
|
|
307
|
+
'#000000'
|
|
308
|
+
const labelFontFamily =
|
|
309
|
+
(getVariableByName('donutChart/label/fontFamily', modes) as string | null) ??
|
|
310
|
+
(getVariableByName('label/fontFamily', modes) as string | null) ??
|
|
311
|
+
'JioType Var'
|
|
312
|
+
const labelFontSize = toNumber(
|
|
313
|
+
getVariableByName('donutChart/label/fontSize', modes) ??
|
|
314
|
+
getVariableByName('label/fontSize', modes),
|
|
315
|
+
12
|
|
316
|
+
)
|
|
317
|
+
const labelLineHeight = toNumber(
|
|
318
|
+
getVariableByName('donutChart/label/lineHeight', modes) ??
|
|
319
|
+
getVariableByName('label/lineHeight', modes),
|
|
320
|
+
15.6
|
|
321
|
+
)
|
|
322
|
+
const labelFontWeight = toFontWeight(
|
|
323
|
+
getVariableByName('donutChart/label/fontWeight', modes) ??
|
|
324
|
+
getVariableByName('label/fontWeight', modes),
|
|
325
|
+
'400'
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
const textWrapGap = toNumber(
|
|
329
|
+
getVariableByName('donutChart/textWrap/gap', modes) ??
|
|
330
|
+
getVariableByName('textWrap/gap', modes),
|
|
331
|
+
7
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
const containerStyle: ViewStyle = {
|
|
335
|
+
alignItems: 'center',
|
|
336
|
+
height: size,
|
|
337
|
+
justifyContent: 'center',
|
|
338
|
+
position: 'relative',
|
|
339
|
+
width: size,
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return (
|
|
343
|
+
<View
|
|
344
|
+
accessibilityRole="image"
|
|
345
|
+
accessibilityLabel={accessibilityLabel}
|
|
346
|
+
style={[containerStyle, style]}
|
|
347
|
+
>
|
|
348
|
+
<Svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
|
349
|
+
{arcs.map((arc) => (
|
|
350
|
+
<Circle
|
|
351
|
+
key={arc.key}
|
|
352
|
+
cx={center}
|
|
353
|
+
cy={center}
|
|
354
|
+
r={radius}
|
|
355
|
+
stroke={arc.color}
|
|
356
|
+
strokeWidth={strokeWidth}
|
|
357
|
+
fill="none"
|
|
358
|
+
strokeDasharray={arc.dashArray}
|
|
359
|
+
strokeDashoffset={arc.dashOffset}
|
|
360
|
+
strokeLinecap="butt"
|
|
361
|
+
rotation="-90"
|
|
362
|
+
originX={center}
|
|
363
|
+
originY={center}
|
|
364
|
+
/>
|
|
365
|
+
))}
|
|
366
|
+
</Svg>
|
|
367
|
+
|
|
368
|
+
{hasChildrenSlot ? (
|
|
369
|
+
<View pointerEvents="box-none" style={StyleSheet.absoluteFill as ViewStyle}>
|
|
370
|
+
<View style={styles.centerSlot}>{customCenterChildren}</View>
|
|
371
|
+
</View>
|
|
372
|
+
) : showValueLabel || showLabel ? (
|
|
373
|
+
<View
|
|
374
|
+
pointerEvents="none"
|
|
375
|
+
style={[styles.centerSlot, { gap: textWrapGap }]}
|
|
376
|
+
>
|
|
377
|
+
{showValueLabel ? (
|
|
378
|
+
<Text
|
|
379
|
+
style={[
|
|
380
|
+
{
|
|
381
|
+
color: valueColor,
|
|
382
|
+
fontFamily: valueFontFamily,
|
|
383
|
+
fontSize: valueFontSize,
|
|
384
|
+
lineHeight: valueLineHeight,
|
|
385
|
+
fontWeight: valueFontWeight,
|
|
386
|
+
textAlign: 'center',
|
|
387
|
+
},
|
|
388
|
+
valueStyle,
|
|
389
|
+
]}
|
|
390
|
+
numberOfLines={1}
|
|
391
|
+
>
|
|
392
|
+
{value}
|
|
393
|
+
</Text>
|
|
394
|
+
) : null}
|
|
395
|
+
{showLabel ? (
|
|
396
|
+
<Text
|
|
397
|
+
style={[
|
|
398
|
+
{
|
|
399
|
+
color: labelColor,
|
|
400
|
+
fontFamily: labelFontFamily,
|
|
401
|
+
fontSize: labelFontSize,
|
|
402
|
+
lineHeight: labelLineHeight,
|
|
403
|
+
fontWeight: labelFontWeight,
|
|
404
|
+
textAlign: 'center',
|
|
405
|
+
},
|
|
406
|
+
labelStyle,
|
|
407
|
+
]}
|
|
408
|
+
numberOfLines={1}
|
|
409
|
+
>
|
|
410
|
+
{label}
|
|
411
|
+
</Text>
|
|
412
|
+
) : null}
|
|
413
|
+
</View>
|
|
414
|
+
) : null}
|
|
415
|
+
</View>
|
|
416
|
+
)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function shouldRenderText(node: React.ReactNode): boolean {
|
|
420
|
+
return node !== undefined && node !== null && node !== false && node !== ''
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
type ArcDescriptor = {
|
|
424
|
+
key: React.Key
|
|
425
|
+
color: string
|
|
426
|
+
dashArray: string
|
|
427
|
+
dashOffset: number
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Convert the resolved segment list into a list of stroke-dasharray /
|
|
432
|
+
* stroke-dashoffset descriptors that can be drawn as SVG `Circle`s. Each
|
|
433
|
+
* arc's length is its share of `circumference`, and its rotational offset
|
|
434
|
+
* is the cumulative offset of all preceding segments. Visual gaps are
|
|
435
|
+
* applied symmetrically — half a gap is shaved from each end of the arc
|
|
436
|
+
* so the visible separation between two segments equals one full `gap`.
|
|
437
|
+
*/
|
|
438
|
+
function buildArcs({
|
|
439
|
+
segments,
|
|
440
|
+
totalValue,
|
|
441
|
+
circumference,
|
|
442
|
+
gapLength,
|
|
443
|
+
modes,
|
|
444
|
+
}: {
|
|
445
|
+
segments: DonutChartSegmentData[]
|
|
446
|
+
totalValue: number
|
|
447
|
+
circumference: number
|
|
448
|
+
gapLength: number
|
|
449
|
+
modes: Record<string, any>
|
|
450
|
+
}): ArcDescriptor[] {
|
|
451
|
+
const arcs: ArcDescriptor[] = []
|
|
452
|
+
const halfGap = gapLength / 2
|
|
453
|
+
let cumulativeOffset = 0
|
|
454
|
+
|
|
455
|
+
segments.forEach((segment, index) => {
|
|
456
|
+
const safeValue = Math.max(0, toNumber(segment.value, 0))
|
|
457
|
+
if (safeValue <= 0) {
|
|
458
|
+
return
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const arcLength = (safeValue / totalValue) * circumference
|
|
462
|
+
const drawnLength = Math.max(0, arcLength - gapLength)
|
|
463
|
+
const segmentModes = {
|
|
464
|
+
...modes,
|
|
465
|
+
'Appearance / DataViz': defaultAppearanceFor(index),
|
|
466
|
+
...(segment.modes || {}),
|
|
467
|
+
}
|
|
468
|
+
const color = resolveSegmentColor(segment.color, segmentModes)
|
|
469
|
+
|
|
470
|
+
const dashArray = `${drawnLength} ${circumference - drawnLength}`
|
|
471
|
+
const dashOffset = -cumulativeOffset - halfGap
|
|
472
|
+
|
|
473
|
+
arcs.push({
|
|
474
|
+
key: segment.key ?? `segment-${index}`,
|
|
475
|
+
color,
|
|
476
|
+
dashArray,
|
|
477
|
+
dashOffset,
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
cumulativeOffset += arcLength
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
return arcs
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const styles = StyleSheet.create({
|
|
487
|
+
centerSlot: {
|
|
488
|
+
alignItems: 'center',
|
|
489
|
+
bottom: 0,
|
|
490
|
+
justifyContent: 'center',
|
|
491
|
+
left: 0,
|
|
492
|
+
position: 'absolute',
|
|
493
|
+
right: 0,
|
|
494
|
+
top: 0,
|
|
495
|
+
},
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
DonutChart.Segment = DonutChartSegment
|
|
499
|
+
|
|
500
|
+
export { DonutChartSegment }
|
|
501
|
+
export type { DonutChartSegmentProps }
|
|
502
|
+
|
|
503
|
+
export default DonutChart
|