jfs-components 0.1.30 → 0.1.33
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 +29 -0
- package/D2C.md +118 -0
- package/lib/commonjs/components/CategoryCard/CategoryCard.js +264 -0
- package/lib/commonjs/components/CompareTable/CompareTable.js +140 -84
- package/lib/commonjs/components/ContentSheet/ContentSheet.js +28 -5
- package/lib/commonjs/components/CounterBadge/CounterBadge.js +78 -0
- package/lib/commonjs/components/FavoriteToggle/FavoriteToggle.js +180 -0
- package/lib/commonjs/components/HelloJioInput/HelloJioInput.js +287 -0
- package/lib/commonjs/components/ValueBackMetric/ValueBackMetric.js +219 -0
- package/lib/commonjs/components/index.js +35 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +40095 -1
- package/lib/commonjs/design-tokens/figma-modes.generated.js +3 -0
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/CategoryCard/CategoryCard.js +258 -0
- package/lib/module/components/CompareTable/CompareTable.js +140 -84
- package/lib/module/components/ContentSheet/ContentSheet.js +29 -6
- package/lib/module/components/CounterBadge/CounterBadge.js +73 -0
- package/lib/module/components/FavoriteToggle/FavoriteToggle.js +174 -0
- package/lib/module/components/HelloJioInput/HelloJioInput.js +281 -0
- package/lib/module/components/ValueBackMetric/ValueBackMetric.js +214 -0
- package/lib/module/components/index.js +6 -1
- package/lib/module/design-tokens/Coin Variables-variables-full.json +40095 -1
- package/lib/module/design-tokens/figma-modes.generated.js +3 -0
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/CategoryCard/CategoryCard.d.ts +72 -0
- package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +16 -1
- package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +7 -1
- package/lib/typescript/src/components/CounterBadge/CounterBadge.d.ts +19 -0
- package/lib/typescript/src/components/FavoriteToggle/FavoriteToggle.d.ts +66 -0
- package/lib/typescript/src/components/HelloJioInput/HelloJioInput.d.ts +111 -0
- package/lib/typescript/src/components/ValueBackMetric/ValueBackMetric.d.ts +86 -0
- package/lib/typescript/src/components/index.d.ts +5 -0
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +7 -1
- package/scripts/extract-figma-modes.js +359 -0
- package/src/components/CategoryCard/CategoryCard.tsx +404 -0
- package/src/components/CompareTable/CompareTable.tsx +201 -101
- package/src/components/ContentSheet/ContentSheet.tsx +40 -11
- package/src/components/CounterBadge/CounterBadge.tsx +95 -0
- package/src/components/FavoriteToggle/FavoriteToggle.tsx +243 -0
- package/src/components/HelloJioInput/HelloJioInput.tsx +513 -0
- package/src/components/ValueBackMetric/ValueBackMetric.tsx +298 -0
- package/src/components/index.ts +5 -0
- package/src/design-tokens/Coin Variables-variables-full.json +40095 -1
- package/src/design-tokens/figma-modes.generated.ts +3 -0
- package/src/icons/registry.ts +1 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
Pressable,
|
|
6
|
+
type StyleProp,
|
|
7
|
+
type ViewStyle,
|
|
8
|
+
type TextStyle,
|
|
9
|
+
type GestureResponderEvent,
|
|
10
|
+
} from 'react-native'
|
|
11
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
12
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
13
|
+
import Icon from '../Icon/Icon'
|
|
14
|
+
import Link from '../Link/Link'
|
|
15
|
+
import type { Modes } from '../../design-tokens'
|
|
16
|
+
|
|
17
|
+
const TITLE_LINE_HEIGHT_RATIO = 1.2
|
|
18
|
+
const VALUE_LINE_HEIGHT_RATIO = 1.2
|
|
19
|
+
const CAPTION_LINE_HEIGHT_RATIO = 1.5
|
|
20
|
+
|
|
21
|
+
// The Figma node binds the header-icon size and the per-part font sizes/weights
|
|
22
|
+
// to `valueBackMetric/*` variables, but those tokens are NOT in the committed
|
|
23
|
+
// variables snapshot (`npm run tokens -- find valueBackMetric` → no matches).
|
|
24
|
+
// They also intentionally differ from the `metricdata/*` typography defaults
|
|
25
|
+
// (e.g. `metricdata/value` resolves to 20px/700, whereas this component's value
|
|
26
|
+
// is 16px/500). So the sizes/weights below are encoded as literal constants
|
|
27
|
+
// matching the Figma design exactly. Colours, gaps and padding DO exist as real
|
|
28
|
+
// `metricdata/*` tokens (and match the design), so those resolve through
|
|
29
|
+
// `getVariableByName` and stay themeable.
|
|
30
|
+
const TITLE_FONT_SIZE = 12
|
|
31
|
+
const TITLE_FONT_WEIGHT: TextStyle['fontWeight'] = '500'
|
|
32
|
+
const VALUE_FONT_SIZE = 16
|
|
33
|
+
const VALUE_FONT_WEIGHT: TextStyle['fontWeight'] = '500'
|
|
34
|
+
const CAPTION_FONT_SIZE = 12
|
|
35
|
+
const CAPTION_FONT_WEIGHT: TextStyle['fontWeight'] = '400'
|
|
36
|
+
const FONT_FAMILY = 'JioType Var'
|
|
37
|
+
const CARD_BACKGROUND = '#ffffff'
|
|
38
|
+
const CARD_MIN_HEIGHT = 82
|
|
39
|
+
const ICON_SIZE = 18
|
|
40
|
+
// Figma renders the header glyph flush (all `icon/padding/*` tokens are 0); the
|
|
41
|
+
// shared Icon pulls padding from those tokens, so mirror MetricData and zero it
|
|
42
|
+
// explicitly to keep the icon flush against the title.
|
|
43
|
+
const ICON_PADDING_RESET: ViewStyle = {
|
|
44
|
+
paddingLeft: 0,
|
|
45
|
+
paddingRight: 0,
|
|
46
|
+
paddingTop: 0,
|
|
47
|
+
paddingBottom: 0,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type ValueBackMetricProps = {
|
|
51
|
+
/**
|
|
52
|
+
* Header glyph slot, shown to the left of the title. Mirrors the Figma swap
|
|
53
|
+
* slot whose preferred instances are `Icon` and `Image`:
|
|
54
|
+
* - a registry icon name (e.g. `'ic_rupee_coin'`) renders the shared
|
|
55
|
+
* {@link Icon}, colour/size from the `icon/*` tokens (default 18px gold —
|
|
56
|
+
* retint via `modes`, e.g. `{ AppearanceBrand: 'Secondary' }`);
|
|
57
|
+
* - a custom node (an `<Icon/>`, an `<Image/>`, your brand logo) renders
|
|
58
|
+
* as-is with `modes` cascaded into it.
|
|
59
|
+
* The Figma design uses a dedicated JioPoints brand mark that is not in the
|
|
60
|
+
* icon registry, so `ic_rupee_coin` stands in by default.
|
|
61
|
+
*/
|
|
62
|
+
icon?: string | React.ReactNode
|
|
63
|
+
/** Header title next to the icon. Defaults to `'JioPoints'`. */
|
|
64
|
+
title?: string
|
|
65
|
+
/**
|
|
66
|
+
* The prominent value line (16px/500). Pass a string, or any node to compose
|
|
67
|
+
* a richer value. Hidden when omitted.
|
|
68
|
+
*/
|
|
69
|
+
value?: string | React.ReactNode
|
|
70
|
+
/** Muted caption below the value (12px, `#777`). Hidden when omitted. */
|
|
71
|
+
caption?: string
|
|
72
|
+
/** Bottom link label (underlined, brand accent). Hidden when omitted. */
|
|
73
|
+
linkLabel?: string
|
|
74
|
+
/** Fired when the link is pressed. Navigation is the consumer's job. */
|
|
75
|
+
onLinkPress?: (event: GestureResponderEvent) => void
|
|
76
|
+
/**
|
|
77
|
+
* Press handler for the whole card. When set, the card becomes pressable
|
|
78
|
+
* (rendered through a `Pressable` with `accessibilityRole="button"`).
|
|
79
|
+
* Independent of {@link onLinkPress} — the bottom link keeps its own handler.
|
|
80
|
+
*/
|
|
81
|
+
onPress?: (event: GestureResponderEvent) => void
|
|
82
|
+
/** Disable interaction when `onPress` is set. */
|
|
83
|
+
disabled?: boolean
|
|
84
|
+
/** Design-token modes for theming. */
|
|
85
|
+
modes?: Modes
|
|
86
|
+
/** Override the container styles. */
|
|
87
|
+
style?: StyleProp<ViewStyle>
|
|
88
|
+
/** Override the title text styles. */
|
|
89
|
+
titleStyle?: StyleProp<TextStyle>
|
|
90
|
+
/** Override the value text styles. */
|
|
91
|
+
valueStyle?: StyleProp<TextStyle>
|
|
92
|
+
/** Override the caption text styles. */
|
|
93
|
+
captionStyle?: StyleProp<TextStyle>
|
|
94
|
+
/**
|
|
95
|
+
* Accessibility label. Defaults to the resolved `title`, `value`, `caption`
|
|
96
|
+
* and `linkLabel` joined together.
|
|
97
|
+
*/
|
|
98
|
+
accessibilityLabel?: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* ValueBackMetric — a compact, left-aligned "value back" card.
|
|
103
|
+
*
|
|
104
|
+
* Stacks a branded header row (icon + `title`, e.g. "JioPoints"), a prominent
|
|
105
|
+
* `value`, a muted `caption`, and an optional bottom `linkLabel` (e.g. "Earn").
|
|
106
|
+
* `value`, `caption` and `linkLabel` each render only when provided; the header
|
|
107
|
+
* (icon + title) always shows.
|
|
108
|
+
*
|
|
109
|
+
* Colours, gaps and padding resolve from the `metricdata/*` tokens; the header
|
|
110
|
+
* glyph from the `icon/*` tokens (default 18px gold); the link entirely from
|
|
111
|
+
* the `link/*` + `text/foreground` tokens via the shared {@link Link}, with
|
|
112
|
+
* `Text Appearance: Secondary` and `Text Sizes: Small` seeded so it renders
|
|
113
|
+
* purple at 12px like the design (caller `modes` win). The per-part font
|
|
114
|
+
* sizes/weights come from the Figma `valueBackMetric/*` variables, which are
|
|
115
|
+
* not yet in the committed token snapshot, so they are encoded as constants
|
|
116
|
+
* matching the design.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```tsx
|
|
120
|
+
* <ValueBackMetric
|
|
121
|
+
* title="JioPoints"
|
|
122
|
+
* value="Value"
|
|
123
|
+
* caption="Earn 10 point with UPI"
|
|
124
|
+
* linkLabel="Earn"
|
|
125
|
+
* onPress={() => navigation.navigate('ValueBack')}
|
|
126
|
+
* onLinkPress={() => navigation.navigate('EarnPoints')}
|
|
127
|
+
* />
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
function ValueBackMetric({
|
|
131
|
+
icon = 'ic_rupee_coin',
|
|
132
|
+
title = 'JioPoints',
|
|
133
|
+
value,
|
|
134
|
+
caption,
|
|
135
|
+
linkLabel,
|
|
136
|
+
onLinkPress,
|
|
137
|
+
onPress,
|
|
138
|
+
disabled,
|
|
139
|
+
modes = EMPTY_MODES,
|
|
140
|
+
style,
|
|
141
|
+
titleStyle,
|
|
142
|
+
valueStyle,
|
|
143
|
+
captionStyle,
|
|
144
|
+
accessibilityLabel,
|
|
145
|
+
}: ValueBackMetricProps) {
|
|
146
|
+
const titleColor = (getVariableByName('metricdata/title/color', modes) as string | null) ?? '#000000'
|
|
147
|
+
const valueColor = (getVariableByName('metricdata/value/color', modes) as string | null) ?? '#000000'
|
|
148
|
+
const captionColor = (getVariableByName('metricdata/caption/color', modes) as string | null) ?? '#777777'
|
|
149
|
+
// The design's link is purple (`text/foreground` under `Text Appearance:
|
|
150
|
+
// Secondary`) at 12px (`link/fontSize` under `Text Sizes: Small`), while the
|
|
151
|
+
// header icon keeps the default gold `icon/color`. Seed only the link's
|
|
152
|
+
// modes; caller-supplied modes win.
|
|
153
|
+
const linkModes = React.useMemo<Modes>(
|
|
154
|
+
() => ({ 'Text Appearance': 'Secondary', 'Text Sizes': 'Small', ...modes }),
|
|
155
|
+
[modes]
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
// The header glyph is token-driven: `icon/color` (gold by default, retinted
|
|
159
|
+
// via `AppearanceBrand`) and `icon/size` (18px). Passing them explicitly — as
|
|
160
|
+
// MetricData does — makes the glyph deterministic instead of relying on the
|
|
161
|
+
// shared Icon's internal token lookups.
|
|
162
|
+
const iconColor = (getVariableByName('icon/color', modes) as string | null) ?? '#ad8444'
|
|
163
|
+
const iconSize = (getVariableByName('icon/size', modes) as number | null) ?? ICON_SIZE
|
|
164
|
+
|
|
165
|
+
const gap = (getVariableByName('metricdata/gap', modes) as number | null) ?? 4
|
|
166
|
+
const valueWrapGap = (getVariableByName('metricdata/valueWrap/gap', modes) as number | null) ?? 4
|
|
167
|
+
const paddingHorizontal = (getVariableByName('metricdata/padding/horizontal', modes) as number | null) ?? 10
|
|
168
|
+
const paddingVertical = (getVariableByName('metricdata/padding/vertical', modes) as number | null) ?? 10
|
|
169
|
+
|
|
170
|
+
const containerStyle: ViewStyle = {
|
|
171
|
+
flexDirection: 'column',
|
|
172
|
+
alignItems: 'flex-start',
|
|
173
|
+
justifyContent: 'center',
|
|
174
|
+
gap,
|
|
175
|
+
paddingHorizontal,
|
|
176
|
+
paddingVertical,
|
|
177
|
+
minHeight: CARD_MIN_HEIGHT,
|
|
178
|
+
backgroundColor: CARD_BACKGROUND,
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const titleTextStyle: TextStyle = {
|
|
182
|
+
color: titleColor,
|
|
183
|
+
fontFamily: FONT_FAMILY,
|
|
184
|
+
fontSize: TITLE_FONT_SIZE,
|
|
185
|
+
fontWeight: TITLE_FONT_WEIGHT,
|
|
186
|
+
lineHeight: TITLE_FONT_SIZE * TITLE_LINE_HEIGHT_RATIO,
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const valueTextStyle: TextStyle = {
|
|
190
|
+
color: valueColor,
|
|
191
|
+
fontFamily: FONT_FAMILY,
|
|
192
|
+
fontSize: VALUE_FONT_SIZE,
|
|
193
|
+
fontWeight: VALUE_FONT_WEIGHT,
|
|
194
|
+
lineHeight: VALUE_FONT_SIZE * VALUE_LINE_HEIGHT_RATIO,
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const captionTextStyle: TextStyle = {
|
|
198
|
+
color: captionColor,
|
|
199
|
+
fontFamily: FONT_FAMILY,
|
|
200
|
+
fontSize: CAPTION_FONT_SIZE,
|
|
201
|
+
fontWeight: CAPTION_FONT_WEIGHT,
|
|
202
|
+
lineHeight: CAPTION_FONT_SIZE * CAPTION_LINE_HEIGHT_RATIO,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Mirrors the Figma swap slot, whose preferred instances are `Icon` and
|
|
206
|
+
// `Image`. A registry name renders the token-driven `Icon` (18px, gold,
|
|
207
|
+
// flush — all from the `icon/*` tokens); a custom node (`<Icon/>`,
|
|
208
|
+
// `<Image/>`, anything) renders as-is with `modes` cascaded into it, so an
|
|
209
|
+
// Icon instance follows the icon tokens while an Image keeps its own sizing.
|
|
210
|
+
const iconNode =
|
|
211
|
+
icon == null ? null : typeof icon === 'string' ? (
|
|
212
|
+
<Icon
|
|
213
|
+
iconName={icon}
|
|
214
|
+
size={iconSize}
|
|
215
|
+
color={iconColor}
|
|
216
|
+
modes={modes}
|
|
217
|
+
style={ICON_PADDING_RESET}
|
|
218
|
+
/>
|
|
219
|
+
) : (
|
|
220
|
+
cloneChildrenWithModes(icon, modes)
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
const hasValue = value !== undefined && value !== null && value !== ''
|
|
224
|
+
const hasCaption = caption !== undefined && caption !== ''
|
|
225
|
+
|
|
226
|
+
const resolvedLabel =
|
|
227
|
+
accessibilityLabel ??
|
|
228
|
+
([title, typeof value === 'string' ? value : undefined, caption, linkLabel]
|
|
229
|
+
.filter(Boolean)
|
|
230
|
+
.join(', ') || undefined)
|
|
231
|
+
|
|
232
|
+
const content = (
|
|
233
|
+
<>
|
|
234
|
+
{/* Header row: brand glyph + title (always shown). */}
|
|
235
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', gap: valueWrapGap }}>
|
|
236
|
+
{iconNode}
|
|
237
|
+
{title !== undefined && title !== '' ? (
|
|
238
|
+
<Text style={[titleTextStyle, titleStyle]}>{title}</Text>
|
|
239
|
+
) : null}
|
|
240
|
+
</View>
|
|
241
|
+
|
|
242
|
+
{/* Value + caption block (each optional). */}
|
|
243
|
+
{hasValue || hasCaption ? (
|
|
244
|
+
<View style={{ flexDirection: 'column', alignItems: 'flex-start', gap: 4 }}>
|
|
245
|
+
{hasValue ? (
|
|
246
|
+
typeof value === 'string' ? (
|
|
247
|
+
<Text style={[valueTextStyle, valueStyle]}>{value}</Text>
|
|
248
|
+
) : (
|
|
249
|
+
value
|
|
250
|
+
)
|
|
251
|
+
) : null}
|
|
252
|
+
{hasCaption ? <Text style={[captionTextStyle, captionStyle]}>{caption}</Text> : null}
|
|
253
|
+
</View>
|
|
254
|
+
) : null}
|
|
255
|
+
|
|
256
|
+
{/* Bottom link (optional). Fully token-driven via the seeded `linkModes`
|
|
257
|
+
(purple 12px, see above). `linkLabel` is checked inline (narrows it to
|
|
258
|
+
a non-undefined `string`) and `onPress` is spread conditionally, since
|
|
259
|
+
`exactOptionalPropertyTypes` forbids passing `undefined` to Link's
|
|
260
|
+
non-`undefined` optional props. */}
|
|
261
|
+
{linkLabel ? (
|
|
262
|
+
<Link
|
|
263
|
+
text={linkLabel}
|
|
264
|
+
autolayout="Hug"
|
|
265
|
+
modes={linkModes}
|
|
266
|
+
{...(onLinkPress ? { onPress: onLinkPress } : null)}
|
|
267
|
+
/>
|
|
268
|
+
) : null}
|
|
269
|
+
</>
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
if (onPress) {
|
|
273
|
+
return (
|
|
274
|
+
<Pressable
|
|
275
|
+
style={[containerStyle, style]}
|
|
276
|
+
onPress={onPress}
|
|
277
|
+
disabled={disabled}
|
|
278
|
+
accessibilityRole="button"
|
|
279
|
+
{...(resolvedLabel !== undefined ? { accessibilityLabel: resolvedLabel } : null)}
|
|
280
|
+
accessibilityState={{ disabled: !!disabled }}
|
|
281
|
+
>
|
|
282
|
+
{content}
|
|
283
|
+
</Pressable>
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return (
|
|
288
|
+
<View
|
|
289
|
+
style={[containerStyle, style]}
|
|
290
|
+
accessible
|
|
291
|
+
{...(resolvedLabel !== undefined ? { accessibilityLabel: resolvedLabel } : null)}
|
|
292
|
+
>
|
|
293
|
+
{content}
|
|
294
|
+
</View>
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export default React.memo(ValueBackMetric)
|
package/src/components/index.ts
CHANGED
|
@@ -33,6 +33,7 @@ export {
|
|
|
33
33
|
} from './CardFinancialCondition/CardFinancialCondition'
|
|
34
34
|
export { default as CardInsight, type CardInsightProps } from './CardInsight/CardInsight'
|
|
35
35
|
export { default as CcCard, type CcCardProps, type CcCardBadge, type CcCardListItem } from './CcCard/CcCard'
|
|
36
|
+
export { default as CategoryCard, type CategoryCardProps } from './CategoryCard/CategoryCard'
|
|
36
37
|
export { default as Disclaimer } from './Disclaimer/Disclaimer'
|
|
37
38
|
export { default as Divider, type DividerProps, type DividerDirection } from './Divider/Divider'
|
|
38
39
|
export { default as Drawer, type DrawerProps, type DrawerHandle } from './Drawer/Drawer'
|
|
@@ -108,6 +109,7 @@ export {
|
|
|
108
109
|
} from './MonthlyStatusGrid/MonthlyStatusGrid'
|
|
109
110
|
export { default as Gauge, type GaugeProps } from './Gauge/Gauge'
|
|
110
111
|
export { default as HeroSection, type HeroSectionProps } from './HeroSection/HeroSection'
|
|
112
|
+
export { default as HelloJioInput, type HelloJioInputProps } from './HelloJioInput/HelloJioInput'
|
|
111
113
|
export { default as HoldingsCard, type HoldingsCardProps } from './HoldingsCard/HoldingsCard'
|
|
112
114
|
export { default as HStack, type HStackProps } from './HStack/HStack'
|
|
113
115
|
export { default as Icon, type IconProps } from './Icon/Icon'
|
|
@@ -336,3 +338,6 @@ export {
|
|
|
336
338
|
export { default as Toggle, type ToggleProps } from './Toggle/Toggle'
|
|
337
339
|
export { default as AutoplayControl, type AutoplayControlProps } from './AutoplayControl/AutoplayControl'
|
|
338
340
|
export { default as NumberPagination, type NumberPaginationProps } from './NumberPagination/NumberPagination'
|
|
341
|
+
export { default as CounterBadge, type CounterBadgeProps } from './CounterBadge/CounterBadge'
|
|
342
|
+
export { default as FavoriteToggle, type FavoriteToggleProps } from './FavoriteToggle/FavoriteToggle'
|
|
343
|
+
export { default as ValueBackMetric, type ValueBackMetricProps } from './ValueBackMetric/ValueBackMetric'
|