jfs-components 0.0.69 → 0.0.71
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 +20 -0
- package/lib/commonjs/components/CardAdvisory/CardAdvisory.js +203 -0
- package/lib/commonjs/components/CardCTA/CardCTA.js +198 -16
- package/lib/commonjs/components/CircularProgressBar/CircularProgressBar.js +147 -0
- package/lib/commonjs/components/CircularProgressBarDoted/CircularProgressBarDoted.js +258 -0
- package/lib/commonjs/components/CircularRating/CircularRating.js +161 -0
- package/lib/commonjs/components/Gauge/Gauge.js +223 -0
- package/lib/commonjs/components/ListGroup/ListGroup.js +3 -1
- package/lib/commonjs/components/MediaCard/GlassFill.js +62 -0
- package/lib/commonjs/components/MediaCard/GlassFill.web.js +48 -0
- package/lib/commonjs/components/MediaCard/MediaCard.js +28 -31
- package/lib/commonjs/components/Nudge/Nudge.js +179 -87
- package/lib/commonjs/components/index.js +35 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/CardAdvisory/CardAdvisory.js +197 -0
- package/lib/module/components/CardCTA/CardCTA.js +199 -17
- package/lib/module/components/CircularProgressBar/CircularProgressBar.js +141 -0
- package/lib/module/components/CircularProgressBarDoted/CircularProgressBarDoted.js +253 -0
- package/lib/module/components/CircularRating/CircularRating.js +155 -0
- package/lib/module/components/Gauge/Gauge.js +217 -0
- package/lib/module/components/ListGroup/ListGroup.js +3 -1
- package/lib/module/components/MediaCard/GlassFill.js +57 -0
- package/lib/module/components/MediaCard/GlassFill.web.js +43 -0
- package/lib/module/components/MediaCard/MediaCard.js +29 -32
- package/lib/module/components/Nudge/Nudge.js +178 -87
- package/lib/module/components/index.js +5 -0
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/CardAdvisory/CardAdvisory.d.ts +49 -0
- package/lib/typescript/src/components/CardCTA/CardCTA.d.ts +16 -1
- package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +27 -0
- package/lib/typescript/src/components/CircularProgressBarDoted/CircularProgressBarDoted.d.ts +48 -0
- package/lib/typescript/src/components/CircularRating/CircularRating.d.ts +49 -0
- package/lib/typescript/src/components/Gauge/Gauge.d.ts +53 -0
- package/lib/typescript/src/components/MediaCard/GlassFill.d.ts +47 -0
- package/lib/typescript/src/components/MediaCard/GlassFill.web.d.ts +20 -0
- package/lib/typescript/src/components/MediaCard/MediaCard.d.ts +17 -13
- package/lib/typescript/src/components/Nudge/Nudge.d.ts +14 -11
- package/lib/typescript/src/components/index.d.ts +6 -1
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +3 -2
- package/src/components/CardAdvisory/CardAdvisory.tsx +283 -0
- package/src/components/CardCTA/CardCTA.tsx +236 -13
- package/src/components/CircularProgressBar/CircularProgressBar.tsx +190 -0
- package/src/components/CircularProgressBarDoted/CircularProgressBarDoted.tsx +357 -0
- package/src/components/CircularRating/CircularRating.tsx +241 -0
- package/src/components/Gauge/Gauge.tsx +303 -0
- package/src/components/ListGroup/ListGroup.tsx +3 -1
- package/src/components/MediaCard/GlassFill.tsx +89 -0
- package/src/components/MediaCard/GlassFill.web.tsx +53 -0
- package/src/components/MediaCard/MediaCard.tsx +29 -48
- package/src/components/Nudge/Nudge.tsx +222 -82
- package/src/components/index.ts +6 -1
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/icons/registry.ts +1 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Text,
|
|
4
|
+
View,
|
|
5
|
+
type StyleProp,
|
|
6
|
+
type TextStyle,
|
|
7
|
+
type ViewStyle,
|
|
8
|
+
} from 'react-native'
|
|
9
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
10
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
11
|
+
import Icon from '../../icons/Icon'
|
|
12
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
13
|
+
import CircularProgressBarDoted from '../CircularProgressBarDoted/CircularProgressBarDoted'
|
|
14
|
+
import Nudge from '../Nudge/Nudge'
|
|
15
|
+
|
|
16
|
+
type CircularRatingBaseProps = Omit<React.ComponentProps<typeof View>, 'children' | 'style'>
|
|
17
|
+
|
|
18
|
+
export type CircularRatingProps = CircularRatingBaseProps & {
|
|
19
|
+
/** Rating value. Clamped by CircularProgressBarDoted between 0 and 100. */
|
|
20
|
+
value?: number
|
|
21
|
+
/** Number of dots rendered around the rating ring. */
|
|
22
|
+
dotCount?: number
|
|
23
|
+
/** Small label above the score. */
|
|
24
|
+
label?: string
|
|
25
|
+
/** Text below the score. */
|
|
26
|
+
tierLabel?: string
|
|
27
|
+
/** Footer timestamp/copy shown below the rating. */
|
|
28
|
+
footerText?: string
|
|
29
|
+
/** Show the footer info icon. */
|
|
30
|
+
showFooterIcon?: boolean
|
|
31
|
+
/** Show the bottom inline nudge. */
|
|
32
|
+
showNudge?: boolean
|
|
33
|
+
/** Body text for the default bottom nudge. */
|
|
34
|
+
nudgeBody?: string
|
|
35
|
+
/** Button label for the default bottom nudge. */
|
|
36
|
+
nudgeButtonLabel?: string
|
|
37
|
+
/** Called when the nudge button is pressed. */
|
|
38
|
+
onPressNudgeButton?: () => void
|
|
39
|
+
/** Called when the rating tier row is pressed. */
|
|
40
|
+
onTierPress?: () => void
|
|
41
|
+
/** Optional footer slot. Receives `modes` recursively. */
|
|
42
|
+
footerSlot?: React.ReactNode
|
|
43
|
+
/** Optional nudge slot. Receives `modes` recursively. */
|
|
44
|
+
nudgeSlot?: React.ReactNode
|
|
45
|
+
/** Design token modes forwarded to token lookups and child components. */
|
|
46
|
+
modes?: Record<string, any>
|
|
47
|
+
/** Optional container style overrides. */
|
|
48
|
+
style?: StyleProp<ViewStyle>
|
|
49
|
+
/** Optional rating ring wrapper style overrides. */
|
|
50
|
+
ratingStyle?: StyleProp<ViewStyle>
|
|
51
|
+
/** Optional footer row style overrides. */
|
|
52
|
+
footerStyle?: StyleProp<ViewStyle>
|
|
53
|
+
/** Optional footer text style overrides. */
|
|
54
|
+
footerTextStyle?: StyleProp<TextStyle>
|
|
55
|
+
/** Optional nudge style overrides. */
|
|
56
|
+
nudgeStyle?: StyleProp<ViewStyle>
|
|
57
|
+
/** Accessibility label for the whole component. */
|
|
58
|
+
accessibilityLabel?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface CircularRatingTokens {
|
|
62
|
+
containerStyle: ViewStyle
|
|
63
|
+
ratingStyle: ViewStyle
|
|
64
|
+
footerStyle: ViewStyle
|
|
65
|
+
footerTextStyle: TextStyle
|
|
66
|
+
footerIconColor: string
|
|
67
|
+
footerIconSize: number
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const toNumber = (value: unknown, fallback: number) => {
|
|
71
|
+
if (typeof value === 'number') {
|
|
72
|
+
return Number.isFinite(value) ? value : fallback
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (typeof value === 'string') {
|
|
76
|
+
const parsed = Number(value)
|
|
77
|
+
return Number.isFinite(parsed) ? parsed : fallback
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return fallback
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']) => {
|
|
84
|
+
if (typeof value === 'number') {
|
|
85
|
+
return String(value) as TextStyle['fontWeight']
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof value === 'string') {
|
|
89
|
+
return value as TextStyle['fontWeight']
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return fallback
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function resolveCircularRatingTokens(modes: Record<string, any>): CircularRatingTokens {
|
|
96
|
+
const gap = toNumber(getVariableByName('circularRating/gap', modes), 32)
|
|
97
|
+
const padding = toNumber(getVariableByName('circularRating/padding', modes), 10)
|
|
98
|
+
|
|
99
|
+
const footerGap = toNumber(getVariableByName('circularRating/footer/gap', modes), 4)
|
|
100
|
+
const footerHeight = toNumber(getVariableByName('circularRating/footer/height', modes), 16)
|
|
101
|
+
const footerTextColor = getVariableByName('circularRating/footer/text/color', modes) || '#0d0d0f'
|
|
102
|
+
const footerTextFontSize = toNumber(getVariableByName('circularRating/footer/text/fontSize', modes), 12)
|
|
103
|
+
const footerTextFontFamily = getVariableByName('circularRating/footer/text/fontFamily', modes) || 'JioType Var'
|
|
104
|
+
const footerTextLineHeight = toNumber(getVariableByName('circularRating/footer/text/lineHeight', modes), 16)
|
|
105
|
+
const footerTextFontWeight = toFontWeight(
|
|
106
|
+
getVariableByName('circularRating/footer/text/fontWeight', modes),
|
|
107
|
+
'400'
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
containerStyle: {
|
|
112
|
+
alignItems: 'flex-start',
|
|
113
|
+
gap,
|
|
114
|
+
justifyContent: 'center',
|
|
115
|
+
padding,
|
|
116
|
+
},
|
|
117
|
+
ratingStyle: {
|
|
118
|
+
height: 320,
|
|
119
|
+
width: 320,
|
|
120
|
+
},
|
|
121
|
+
footerStyle: {
|
|
122
|
+
alignItems: 'center',
|
|
123
|
+
flexDirection: 'row',
|
|
124
|
+
gap: footerGap,
|
|
125
|
+
height: footerHeight,
|
|
126
|
+
justifyContent: 'center',
|
|
127
|
+
width: '100%',
|
|
128
|
+
},
|
|
129
|
+
footerTextStyle: {
|
|
130
|
+
color: footerTextColor as string,
|
|
131
|
+
fontFamily: footerTextFontFamily as string,
|
|
132
|
+
fontSize: footerTextFontSize,
|
|
133
|
+
fontWeight: footerTextFontWeight,
|
|
134
|
+
lineHeight: footerTextLineHeight,
|
|
135
|
+
textAlign: 'center',
|
|
136
|
+
},
|
|
137
|
+
footerIconColor: (getVariableByName('circularRating/footer/icon/color', modes) || '#1a1c1f') as string,
|
|
138
|
+
footerIconSize: toNumber(getVariableByName('circularRating/footer/icon/size', modes), 16),
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function CircularRating({
|
|
143
|
+
value = 72,
|
|
144
|
+
dotCount = 24,
|
|
145
|
+
label = 'Rating',
|
|
146
|
+
tierLabel = 'Doing great',
|
|
147
|
+
footerText = 'Updated on 1 March',
|
|
148
|
+
showFooterIcon = true,
|
|
149
|
+
showNudge = true,
|
|
150
|
+
nudgeBody = 'Split this transaction into installments',
|
|
151
|
+
nudgeButtonLabel = 'Button',
|
|
152
|
+
onPressNudgeButton,
|
|
153
|
+
onTierPress,
|
|
154
|
+
footerSlot,
|
|
155
|
+
nudgeSlot,
|
|
156
|
+
modes: propModes = EMPTY_MODES,
|
|
157
|
+
style,
|
|
158
|
+
ratingStyle,
|
|
159
|
+
footerStyle,
|
|
160
|
+
footerTextStyle,
|
|
161
|
+
nudgeStyle,
|
|
162
|
+
accessibilityLabel,
|
|
163
|
+
...rest
|
|
164
|
+
}: CircularRatingProps) {
|
|
165
|
+
const { modes: globalModes } = useTokens()
|
|
166
|
+
const modes = useMemo(
|
|
167
|
+
() => (globalModes === EMPTY_MODES && propModes === EMPTY_MODES
|
|
168
|
+
? EMPTY_MODES
|
|
169
|
+
: { ...globalModes, ...propModes }),
|
|
170
|
+
[globalModes, propModes]
|
|
171
|
+
)
|
|
172
|
+
const tokens = useMemo(() => resolveCircularRatingTokens(modes), [modes])
|
|
173
|
+
|
|
174
|
+
const processedFooterSlot = useMemo(() => {
|
|
175
|
+
if (!footerSlot) return null
|
|
176
|
+
const processed = cloneChildrenWithModes(React.Children.toArray(footerSlot), modes)
|
|
177
|
+
return processed.length === 1 ? processed[0] : processed
|
|
178
|
+
}, [footerSlot, modes])
|
|
179
|
+
|
|
180
|
+
const processedNudgeSlot = useMemo(() => {
|
|
181
|
+
if (!nudgeSlot) return null
|
|
182
|
+
const processed = cloneChildrenWithModes(React.Children.toArray(nudgeSlot), modes)
|
|
183
|
+
return processed.length === 1 ? processed[0] : processed
|
|
184
|
+
}, [nudgeSlot, modes])
|
|
185
|
+
|
|
186
|
+
const defaultAccessibilityLabel =
|
|
187
|
+
accessibilityLabel ?? `${label}. ${Math.round(value)} out of 100. ${tierLabel}. ${footerText}`
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<View
|
|
191
|
+
accessibilityLabel={defaultAccessibilityLabel}
|
|
192
|
+
style={[tokens.containerStyle, style]}
|
|
193
|
+
{...rest}
|
|
194
|
+
>
|
|
195
|
+
<View style={[tokens.ratingStyle, ratingStyle]}>
|
|
196
|
+
<CircularProgressBarDoted
|
|
197
|
+
value={value}
|
|
198
|
+
dotCount={dotCount}
|
|
199
|
+
label={label}
|
|
200
|
+
tierLabel={tierLabel}
|
|
201
|
+
onTierPress={onTierPress}
|
|
202
|
+
modes={modes}
|
|
203
|
+
/>
|
|
204
|
+
</View>
|
|
205
|
+
|
|
206
|
+
<View style={[tokens.footerStyle, footerStyle]}>
|
|
207
|
+
{processedFooterSlot || (
|
|
208
|
+
<>
|
|
209
|
+
<Text numberOfLines={1} style={[tokens.footerTextStyle, footerTextStyle]}>
|
|
210
|
+
{footerText}
|
|
211
|
+
</Text>
|
|
212
|
+
{showFooterIcon ? (
|
|
213
|
+
<Icon
|
|
214
|
+
name="ic_info"
|
|
215
|
+
size={tokens.footerIconSize}
|
|
216
|
+
color={tokens.footerIconColor}
|
|
217
|
+
accessibilityElementsHidden={true}
|
|
218
|
+
importantForAccessibility="no"
|
|
219
|
+
/>
|
|
220
|
+
) : null}
|
|
221
|
+
</>
|
|
222
|
+
)}
|
|
223
|
+
</View>
|
|
224
|
+
|
|
225
|
+
{showNudge ? (
|
|
226
|
+
processedNudgeSlot || (
|
|
227
|
+
<Nudge
|
|
228
|
+
type="inline-compact"
|
|
229
|
+
body={nudgeBody}
|
|
230
|
+
buttonLabel={nudgeButtonLabel}
|
|
231
|
+
onPressButton={onPressNudgeButton}
|
|
232
|
+
modes={modes}
|
|
233
|
+
style={[{ width: 312 }, nudgeStyle]}
|
|
234
|
+
/>
|
|
235
|
+
)
|
|
236
|
+
) : null}
|
|
237
|
+
</View>
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export default React.memo(CircularRating)
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { StyleSheet, View, Text, type StyleProp, type TextStyle, type ViewStyle } from 'react-native'
|
|
3
|
+
import Svg, { Path } from 'react-native-svg'
|
|
4
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
5
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
6
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
7
|
+
import SupportText, { type SupportTextProps } from '../SupportText/SupportText'
|
|
8
|
+
|
|
9
|
+
type GaugeBaseProps = Omit<React.ComponentProps<typeof View>, 'children' | 'style'>
|
|
10
|
+
|
|
11
|
+
export type GaugeProps = GaugeBaseProps & {
|
|
12
|
+
/** Current gauge value. Interpreted against `min` and `max`. */
|
|
13
|
+
value?: number
|
|
14
|
+
/** Lower bound used to normalize progress. */
|
|
15
|
+
min?: number
|
|
16
|
+
/** Upper bound used to normalize progress. */
|
|
17
|
+
max?: number
|
|
18
|
+
/** Optional formatted value shown in the default readout. */
|
|
19
|
+
valueLabel?: string
|
|
20
|
+
/** Heading above the arc. */
|
|
21
|
+
title?: string
|
|
22
|
+
/** Caption below the arc. */
|
|
23
|
+
caption?: string
|
|
24
|
+
/** Support text shown in the default readout. */
|
|
25
|
+
supportText?: string
|
|
26
|
+
/** Status passed to the default SupportText component. */
|
|
27
|
+
supportTextStatus?: SupportTextProps['status']
|
|
28
|
+
/** Hides the heading while keeping the rest of the layout intact. */
|
|
29
|
+
showTitle?: boolean
|
|
30
|
+
/** Hides the caption while keeping the rest of the layout intact. */
|
|
31
|
+
showCaption?: boolean
|
|
32
|
+
/** Hides default support text when no custom readout slot is provided. */
|
|
33
|
+
showSupportText?: boolean
|
|
34
|
+
/** Design token modes forwarded to token lookups and slot children. */
|
|
35
|
+
modes?: Record<string, any>
|
|
36
|
+
/** Slot rendered in the center of the gauge arc. Receives `modes` recursively. */
|
|
37
|
+
children?: React.ReactNode
|
|
38
|
+
/** Container style override. */
|
|
39
|
+
style?: StyleProp<ViewStyle>
|
|
40
|
+
/** Arc wrapper style override. */
|
|
41
|
+
arcStyle?: StyleProp<ViewStyle>
|
|
42
|
+
/** Readout container style override. */
|
|
43
|
+
readoutStyle?: StyleProp<ViewStyle>
|
|
44
|
+
/** Title text style override. */
|
|
45
|
+
titleStyle?: StyleProp<TextStyle>
|
|
46
|
+
/** Value text style override. */
|
|
47
|
+
valueStyle?: StyleProp<TextStyle>
|
|
48
|
+
/** Caption text style override. */
|
|
49
|
+
captionStyle?: StyleProp<TextStyle>
|
|
50
|
+
/** Track stroke style override. */
|
|
51
|
+
trackStyle?: StyleProp<ViewStyle>
|
|
52
|
+
/** Progress stroke style override. */
|
|
53
|
+
progressStyle?: StyleProp<ViewStyle>
|
|
54
|
+
/** Accessibility label for the whole gauge. */
|
|
55
|
+
accessibilityLabel?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const clamp = (value: number, min: number, max: number) => {
|
|
59
|
+
if (max <= min) {
|
|
60
|
+
return 0
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return Math.min(1, Math.max(0, (value - min) / (max - min)))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const toNumber = (value: unknown, fallback: number) => {
|
|
67
|
+
if (typeof value === 'number') {
|
|
68
|
+
return Number.isFinite(value) ? value : fallback
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (typeof value === 'string') {
|
|
72
|
+
const parsed = Number(value)
|
|
73
|
+
return Number.isFinite(parsed) ? parsed : fallback
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return fallback
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']) => {
|
|
80
|
+
if (typeof value === 'number') {
|
|
81
|
+
return String(value) as TextStyle['fontWeight']
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (typeof value === 'string') {
|
|
85
|
+
return value as TextStyle['fontWeight']
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return fallback
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const getStrokeColor = (style: StyleProp<ViewStyle>, fallback: string) => {
|
|
92
|
+
const flattened = StyleSheet.flatten(style)
|
|
93
|
+
return typeof flattened.backgroundColor === 'string'
|
|
94
|
+
? flattened.backgroundColor
|
|
95
|
+
: fallback
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function Gauge({
|
|
99
|
+
value = 84,
|
|
100
|
+
min = 0,
|
|
101
|
+
max = 100,
|
|
102
|
+
valueLabel,
|
|
103
|
+
title = 'Your score is based on strong data',
|
|
104
|
+
caption = 'Add more details for even better accuracy',
|
|
105
|
+
supportText = 'Support Text',
|
|
106
|
+
supportTextStatus = 'Success',
|
|
107
|
+
showTitle = true,
|
|
108
|
+
showCaption = true,
|
|
109
|
+
showSupportText = true,
|
|
110
|
+
modes: propModes = EMPTY_MODES,
|
|
111
|
+
children,
|
|
112
|
+
style,
|
|
113
|
+
arcStyle,
|
|
114
|
+
readoutStyle,
|
|
115
|
+
titleStyle,
|
|
116
|
+
valueStyle,
|
|
117
|
+
captionStyle,
|
|
118
|
+
trackStyle,
|
|
119
|
+
progressStyle,
|
|
120
|
+
accessibilityLabel,
|
|
121
|
+
...rest
|
|
122
|
+
}: GaugeProps) {
|
|
123
|
+
const { modes: globalModes } = useTokens()
|
|
124
|
+
const modes = { ...globalModes, ...propModes }
|
|
125
|
+
const supportTextModes = { ...modes, Status: supportTextStatus }
|
|
126
|
+
|
|
127
|
+
const gap = toNumber(getVariableByName('gauge/gap', modes), 30)
|
|
128
|
+
const padding = toNumber(getVariableByName('gauge/padding', modes), 16)
|
|
129
|
+
const width = toNumber(getVariableByName('gauge/width', modes), 299)
|
|
130
|
+
const arcWidth = toNumber(getVariableByName('gauge/arc/width', modes), 267)
|
|
131
|
+
const arcHeight = arcWidth * (186 / 328)
|
|
132
|
+
|
|
133
|
+
const trackSize = toNumber(getVariableByName('gauge/track/size', modes), 244)
|
|
134
|
+
const trackInnerRadius = toNumber(getVariableByName('gauge/track/radius', modes), 99)
|
|
135
|
+
const progressInnerRadius = toNumber(getVariableByName('gauge/progress/radius', modes), 99)
|
|
136
|
+
const innerRadius = Math.min(trackInnerRadius, progressInnerRadius)
|
|
137
|
+
const strokeWidth = Math.max(1, trackSize / 2 - innerRadius)
|
|
138
|
+
const radius = innerRadius + strokeWidth / 2
|
|
139
|
+
const centerX = arcWidth / 2
|
|
140
|
+
const centerY = Math.min(arcHeight - strokeWidth / 2, radius + strokeWidth / 2)
|
|
141
|
+
const startX = centerX - radius
|
|
142
|
+
const endX = centerX + radius
|
|
143
|
+
const arcPath = `M ${startX} ${centerY} A ${radius} ${radius} 0 0 1 ${endX} ${centerY}`
|
|
144
|
+
const arcLength = Math.PI * radius
|
|
145
|
+
const progress = clamp(value, min, max)
|
|
146
|
+
|
|
147
|
+
const titleColor = getVariableByName('gauge/title/color', modes) || '#0c0d10'
|
|
148
|
+
const titleFontSize = toNumber(getVariableByName('gauge/title/fontSize', modes), 23)
|
|
149
|
+
const titleLineHeight = toNumber(getVariableByName('gauge/title/lineHeight', modes), 23)
|
|
150
|
+
const titleFontFamily = getVariableByName('gauge/title/fontFamily', modes) || 'JioType Var'
|
|
151
|
+
const titleFontWeight = toFontWeight(getVariableByName('gauge/title/fontWeight', modes), '900')
|
|
152
|
+
const titleHeight = toNumber(getVariableByName('gauge/title/height', modes), 46)
|
|
153
|
+
|
|
154
|
+
const readoutGap = toNumber(getVariableByName('gauge/readout/gap', modes), 8)
|
|
155
|
+
const readoutPadding = toNumber(getVariableByName('gauge/readout/padding', modes), 8)
|
|
156
|
+
const valueColor = getVariableByName('gauge/readout/value/color', modes) || '#0c0d10'
|
|
157
|
+
const valueFontSize = toNumber(getVariableByName('gauge/readout/value/fontSize', modes), 36)
|
|
158
|
+
const valueLineHeight = toNumber(getVariableByName('gauge/readout/value/lineHeight', modes), 36)
|
|
159
|
+
const valueFontFamily = getVariableByName('gauge/readout/value/fontFamily', modes) || 'JioType Var'
|
|
160
|
+
const valueFontWeight = toFontWeight(getVariableByName('gauge/readout/value/fontWeight', modes), '800')
|
|
161
|
+
|
|
162
|
+
const captionColor = getVariableByName('gauge/caption/color', modes) || '#0c0d10'
|
|
163
|
+
const captionFontSize = toNumber(getVariableByName('gauge/caption/fontSize', modes), 14)
|
|
164
|
+
const captionLineHeight = toNumber(getVariableByName('gauge/caption/lineHeight', modes), 18)
|
|
165
|
+
const captionFontFamily = getVariableByName('gauge/caption/fontFamily', modes) || 'JioType Var'
|
|
166
|
+
const captionFontWeight = toFontWeight(getVariableByName('gauge/caption/fontWeight', modes), '500')
|
|
167
|
+
|
|
168
|
+
const trackColor = getStrokeColor(
|
|
169
|
+
trackStyle,
|
|
170
|
+
getVariableByName('gauge/track/color', modes) || '#f5f5f6'
|
|
171
|
+
)
|
|
172
|
+
const progressColor = getStrokeColor(
|
|
173
|
+
progressStyle,
|
|
174
|
+
getVariableByName('gauge/progress/color', modes) || '#25ab21'
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
const containerStyle: ViewStyle = {
|
|
178
|
+
alignItems: 'center',
|
|
179
|
+
gap,
|
|
180
|
+
padding,
|
|
181
|
+
width,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const computedTitleStyle: TextStyle = {
|
|
185
|
+
color: titleColor as string,
|
|
186
|
+
fontFamily: titleFontFamily as string,
|
|
187
|
+
fontSize: titleFontSize,
|
|
188
|
+
fontWeight: titleFontWeight,
|
|
189
|
+
lineHeight: titleLineHeight,
|
|
190
|
+
minHeight: titleHeight,
|
|
191
|
+
textAlign: 'center',
|
|
192
|
+
width: '100%',
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const computedArcStyle: ViewStyle = {
|
|
196
|
+
height: arcHeight,
|
|
197
|
+
overflow: 'hidden',
|
|
198
|
+
position: 'relative',
|
|
199
|
+
width: '100%',
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const computedReadoutStyle: ViewStyle = {
|
|
203
|
+
alignItems: 'center',
|
|
204
|
+
gap: readoutGap,
|
|
205
|
+
left: 0,
|
|
206
|
+
padding: readoutPadding,
|
|
207
|
+
position: 'absolute',
|
|
208
|
+
right: 0,
|
|
209
|
+
top: arcHeight * 0.42,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const computedValueStyle: TextStyle = {
|
|
213
|
+
color: valueColor as string,
|
|
214
|
+
fontFamily: valueFontFamily as string,
|
|
215
|
+
fontSize: valueFontSize,
|
|
216
|
+
fontWeight: valueFontWeight,
|
|
217
|
+
lineHeight: valueLineHeight,
|
|
218
|
+
textAlign: 'center',
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const computedCaptionStyle: TextStyle = {
|
|
222
|
+
color: captionColor as string,
|
|
223
|
+
fontFamily: captionFontFamily as string,
|
|
224
|
+
fontSize: captionFontSize,
|
|
225
|
+
fontWeight: captionFontWeight,
|
|
226
|
+
lineHeight: captionLineHeight,
|
|
227
|
+
textAlign: 'center',
|
|
228
|
+
width: '100%',
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const defaultValueLabel = valueLabel ?? String(Math.round(value))
|
|
232
|
+
const defaultAccessibilityLabel =
|
|
233
|
+
accessibilityLabel ??
|
|
234
|
+
`${title}. ${defaultValueLabel} out of ${max}. ${supportText}. ${caption}`
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<View
|
|
238
|
+
accessibilityRole="progressbar"
|
|
239
|
+
accessibilityLabel={defaultAccessibilityLabel}
|
|
240
|
+
accessibilityValue={{ min, max, now: value }}
|
|
241
|
+
style={[containerStyle, style]}
|
|
242
|
+
{...rest}
|
|
243
|
+
>
|
|
244
|
+
{showTitle ? (
|
|
245
|
+
<Text style={[computedTitleStyle, titleStyle]}>
|
|
246
|
+
{title}
|
|
247
|
+
</Text>
|
|
248
|
+
) : null}
|
|
249
|
+
|
|
250
|
+
<View style={[computedArcStyle, arcStyle]}>
|
|
251
|
+
<Svg
|
|
252
|
+
width="100%"
|
|
253
|
+
height={arcHeight}
|
|
254
|
+
viewBox={`0 0 ${arcWidth} ${arcHeight}`}
|
|
255
|
+
>
|
|
256
|
+
<Path
|
|
257
|
+
d={arcPath}
|
|
258
|
+
stroke={trackColor}
|
|
259
|
+
strokeWidth={strokeWidth}
|
|
260
|
+
strokeLinecap="butt"
|
|
261
|
+
fill="none"
|
|
262
|
+
/>
|
|
263
|
+
<Path
|
|
264
|
+
d={arcPath}
|
|
265
|
+
stroke={progressColor}
|
|
266
|
+
strokeWidth={strokeWidth}
|
|
267
|
+
strokeLinecap="butt"
|
|
268
|
+
fill="none"
|
|
269
|
+
strokeDasharray={`${arcLength} ${arcLength}`}
|
|
270
|
+
strokeDashoffset={arcLength * (1 - progress)}
|
|
271
|
+
/>
|
|
272
|
+
</Svg>
|
|
273
|
+
|
|
274
|
+
<View style={[computedReadoutStyle, readoutStyle]}>
|
|
275
|
+
{children ? (
|
|
276
|
+
cloneChildrenWithModes(children, modes)
|
|
277
|
+
) : (
|
|
278
|
+
<>
|
|
279
|
+
<Text style={[computedValueStyle, valueStyle]}>
|
|
280
|
+
{defaultValueLabel}
|
|
281
|
+
</Text>
|
|
282
|
+
{showSupportText ? (
|
|
283
|
+
<SupportText
|
|
284
|
+
label={supportText}
|
|
285
|
+
status={supportTextStatus}
|
|
286
|
+
modes={supportTextModes}
|
|
287
|
+
/>
|
|
288
|
+
) : null}
|
|
289
|
+
</>
|
|
290
|
+
)}
|
|
291
|
+
</View>
|
|
292
|
+
</View>
|
|
293
|
+
|
|
294
|
+
{showCaption ? (
|
|
295
|
+
<Text style={[computedCaptionStyle, captionStyle]}>
|
|
296
|
+
{caption}
|
|
297
|
+
</Text>
|
|
298
|
+
) : null}
|
|
299
|
+
</View>
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export default Gauge
|
|
@@ -55,7 +55,9 @@ function ListGroup({
|
|
|
55
55
|
}: ListGroupProps) {
|
|
56
56
|
// Resolve container tokens
|
|
57
57
|
const backgroundColor = getVariableByName('listGroup/background', modes) || 'rgba(255,255,255,0)'
|
|
58
|
-
|
|
58
|
+
// The current exported token aliases a missing Figma variable. Keep the
|
|
59
|
+
// transparent fallback without logging the missing alias on every render.
|
|
60
|
+
const borderColor = 'rgba(255,255,255,0)'
|
|
59
61
|
const borderWidth = getVariableByName('listGroup/borderWidth', modes) || 0
|
|
60
62
|
const gap = getVariableByName('listGroup/gap', modes) || 12
|
|
61
63
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, StyleSheet, Platform, type ViewStyle, type StyleProp } from 'react-native'
|
|
3
|
+
import { BlurView } from '@react-native-community/blur'
|
|
4
|
+
|
|
5
|
+
export type GlassTint = 'dark' | 'light'
|
|
6
|
+
|
|
7
|
+
export interface GlassFillProps {
|
|
8
|
+
/**
|
|
9
|
+
* Visual tint of the glass surface. Maps to `BlurView`'s `blurType`
|
|
10
|
+
* (`'dark'` | `'light'`). The library also drives the appropriate
|
|
11
|
+
* `reducedTransparencyFallbackColor` from this so iOS gracefully degrades
|
|
12
|
+
* when "Reduce Transparency" is enabled in system accessibility settings.
|
|
13
|
+
*/
|
|
14
|
+
tint?: GlassTint
|
|
15
|
+
/**
|
|
16
|
+
* Blur strength as a 0–100 "intensity" value (kept compatible with the
|
|
17
|
+
* previous `expo-blur` API so consumers don't need to relearn the scale).
|
|
18
|
+
* Internally mapped to `@react-native-community/blur`'s `blurAmount`,
|
|
19
|
+
* which is roughly 0–32 on iOS / Android.
|
|
20
|
+
*/
|
|
21
|
+
intensity?: number
|
|
22
|
+
/**
|
|
23
|
+
* Token-derived color tint laid OVER the live blur (Figma `blur/minimal/background`).
|
|
24
|
+
* Painted as a translucent overlay so the glass keeps the design system
|
|
25
|
+
* color signature even when the platform blur quality varies.
|
|
26
|
+
*/
|
|
27
|
+
overlayColor?: string
|
|
28
|
+
/** Container style overrides. Defaults to `StyleSheet.absoluteFill`. */
|
|
29
|
+
style?: StyleProp<ViewStyle>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const DEFAULT_FALLBACK_DARK = '#1414174a'
|
|
33
|
+
const DEFAULT_FALLBACK_LIGHT = '#ffffff66'
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Glass / frosted surface for native (iOS + Android).
|
|
37
|
+
*
|
|
38
|
+
* Why this lives in its own platform-split file:
|
|
39
|
+
* - `@react-native-community/blur` is a native-only module. Importing it on
|
|
40
|
+
* web throws because the JS shim references native components that aren't
|
|
41
|
+
* registered there. By using Metro's platform-extension resolution
|
|
42
|
+
* (`GlassFill.tsx` for native, `GlassFill.web.tsx` for web), we keep the
|
|
43
|
+
* web bundle free of any native-only imports.
|
|
44
|
+
* - Centralizes the `intensity` (0–100) -> `blurAmount` (0–32) mapping so
|
|
45
|
+
* callers can keep the Figma token semantics they already know.
|
|
46
|
+
*
|
|
47
|
+
* On iOS this is a real `UIVisualEffectView` (true OS-level live blur).
|
|
48
|
+
* On Android this uses the community blur view (RealtimeBlurView). On devices
|
|
49
|
+
* where realtime blur is unavailable, `reducedTransparencyFallbackColor` (and
|
|
50
|
+
* the explicit `overlayColor`) ensure the surface still renders as a
|
|
51
|
+
* translucent tinted scrim instead of disappearing.
|
|
52
|
+
*/
|
|
53
|
+
function GlassFill({
|
|
54
|
+
tint = 'dark',
|
|
55
|
+
intensity = 50,
|
|
56
|
+
overlayColor,
|
|
57
|
+
style,
|
|
58
|
+
}: GlassFillProps) {
|
|
59
|
+
const blurType: 'light' | 'dark' = tint === 'light' ? 'light' : 'dark'
|
|
60
|
+
const blurAmount = Math.max(0, Math.min(32, Math.round(intensity * 0.32)))
|
|
61
|
+
const fallbackColor = overlayColor ?? (tint === 'light' ? DEFAULT_FALLBACK_LIGHT : DEFAULT_FALLBACK_DARK)
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<View style={[StyleSheet.absoluteFill, style]} pointerEvents="none">
|
|
65
|
+
<BlurView
|
|
66
|
+
style={StyleSheet.absoluteFill}
|
|
67
|
+
blurType={blurType}
|
|
68
|
+
blurAmount={blurAmount}
|
|
69
|
+
reducedTransparencyFallbackColor={fallbackColor}
|
|
70
|
+
/>
|
|
71
|
+
{overlayColor != null ? (
|
|
72
|
+
<View style={[StyleSheet.absoluteFill, { backgroundColor: overlayColor }]} />
|
|
73
|
+
) : null}
|
|
74
|
+
{Platform.OS === 'android' ? (
|
|
75
|
+
<View
|
|
76
|
+
style={[
|
|
77
|
+
StyleSheet.absoluteFill,
|
|
78
|
+
{
|
|
79
|
+
backgroundColor: 'rgba(255,255,255,0.03)',
|
|
80
|
+
opacity: 0.6,
|
|
81
|
+
},
|
|
82
|
+
]}
|
|
83
|
+
/>
|
|
84
|
+
) : null}
|
|
85
|
+
</View>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default GlassFill
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, StyleSheet, type ViewStyle, type StyleProp } from 'react-native'
|
|
3
|
+
|
|
4
|
+
export type GlassTint = 'dark' | 'light'
|
|
5
|
+
|
|
6
|
+
export interface GlassFillProps {
|
|
7
|
+
tint?: GlassTint
|
|
8
|
+
intensity?: number
|
|
9
|
+
overlayColor?: string
|
|
10
|
+
style?: StyleProp<ViewStyle>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const DEFAULT_FALLBACK_DARK = '#1414174a'
|
|
14
|
+
const DEFAULT_FALLBACK_LIGHT = '#ffffff66'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Web counterpart of `GlassFill`.
|
|
18
|
+
*
|
|
19
|
+
* `@react-native-community/blur` does not ship a web implementation, so for
|
|
20
|
+
* the web bundle we render a translucent `View` with `backdrop-filter: blur()`
|
|
21
|
+
* — which is exactly how 0.0.67 and earlier shipped the glass effect on web.
|
|
22
|
+
* Native bundles pick up `GlassFill.tsx` instead via Metro's platform
|
|
23
|
+
* resolver; the web bundle picks up this file.
|
|
24
|
+
*/
|
|
25
|
+
function GlassFill({
|
|
26
|
+
tint = 'dark',
|
|
27
|
+
intensity = 50,
|
|
28
|
+
overlayColor,
|
|
29
|
+
style,
|
|
30
|
+
}: GlassFillProps) {
|
|
31
|
+
// Approximate mapping: intensity 0-100 -> ~0-30px CSS blur. Keeps parity
|
|
32
|
+
// with the native blur strength so the component looks roughly the same
|
|
33
|
+
// across platforms.
|
|
34
|
+
const blurPx = Math.max(0, Math.min(30, Math.round(intensity * 0.3)))
|
|
35
|
+
const tintColor = overlayColor ?? (tint === 'light' ? DEFAULT_FALLBACK_LIGHT : DEFAULT_FALLBACK_DARK)
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<View
|
|
39
|
+
style={[
|
|
40
|
+
StyleSheet.absoluteFill,
|
|
41
|
+
{ backgroundColor: tintColor },
|
|
42
|
+
// backdrop-filter is a web-only CSS property; ignored by RN
|
|
43
|
+
// on native (we never bundle this file there anyway).
|
|
44
|
+
// @ts-ignore web-only style
|
|
45
|
+
{ backdropFilter: `blur(${blurPx}px)`, WebkitBackdropFilter: `blur(${blurPx}px)` },
|
|
46
|
+
style,
|
|
47
|
+
]}
|
|
48
|
+
pointerEvents="none"
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default GlassFill
|