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.
Files changed (46) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/D2C.md +118 -0
  3. package/lib/commonjs/components/CategoryCard/CategoryCard.js +264 -0
  4. package/lib/commonjs/components/CompareTable/CompareTable.js +140 -84
  5. package/lib/commonjs/components/ContentSheet/ContentSheet.js +28 -5
  6. package/lib/commonjs/components/CounterBadge/CounterBadge.js +78 -0
  7. package/lib/commonjs/components/FavoriteToggle/FavoriteToggle.js +180 -0
  8. package/lib/commonjs/components/HelloJioInput/HelloJioInput.js +287 -0
  9. package/lib/commonjs/components/ValueBackMetric/ValueBackMetric.js +219 -0
  10. package/lib/commonjs/components/index.js +35 -0
  11. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +40095 -1
  12. package/lib/commonjs/design-tokens/figma-modes.generated.js +3 -0
  13. package/lib/commonjs/icons/registry.js +1 -1
  14. package/lib/module/components/CategoryCard/CategoryCard.js +258 -0
  15. package/lib/module/components/CompareTable/CompareTable.js +140 -84
  16. package/lib/module/components/ContentSheet/ContentSheet.js +29 -6
  17. package/lib/module/components/CounterBadge/CounterBadge.js +73 -0
  18. package/lib/module/components/FavoriteToggle/FavoriteToggle.js +174 -0
  19. package/lib/module/components/HelloJioInput/HelloJioInput.js +281 -0
  20. package/lib/module/components/ValueBackMetric/ValueBackMetric.js +214 -0
  21. package/lib/module/components/index.js +6 -1
  22. package/lib/module/design-tokens/Coin Variables-variables-full.json +40095 -1
  23. package/lib/module/design-tokens/figma-modes.generated.js +3 -0
  24. package/lib/module/icons/registry.js +1 -1
  25. package/lib/typescript/src/components/CategoryCard/CategoryCard.d.ts +72 -0
  26. package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +16 -1
  27. package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +7 -1
  28. package/lib/typescript/src/components/CounterBadge/CounterBadge.d.ts +19 -0
  29. package/lib/typescript/src/components/FavoriteToggle/FavoriteToggle.d.ts +66 -0
  30. package/lib/typescript/src/components/HelloJioInput/HelloJioInput.d.ts +111 -0
  31. package/lib/typescript/src/components/ValueBackMetric/ValueBackMetric.d.ts +86 -0
  32. package/lib/typescript/src/components/index.d.ts +5 -0
  33. package/lib/typescript/src/icons/registry.d.ts +1 -1
  34. package/package.json +7 -1
  35. package/scripts/extract-figma-modes.js +359 -0
  36. package/src/components/CategoryCard/CategoryCard.tsx +404 -0
  37. package/src/components/CompareTable/CompareTable.tsx +201 -101
  38. package/src/components/ContentSheet/ContentSheet.tsx +40 -11
  39. package/src/components/CounterBadge/CounterBadge.tsx +95 -0
  40. package/src/components/FavoriteToggle/FavoriteToggle.tsx +243 -0
  41. package/src/components/HelloJioInput/HelloJioInput.tsx +513 -0
  42. package/src/components/ValueBackMetric/ValueBackMetric.tsx +298 -0
  43. package/src/components/index.ts +5 -0
  44. package/src/design-tokens/Coin Variables-variables-full.json +40095 -1
  45. package/src/design-tokens/figma-modes.generated.ts +3 -0
  46. 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)
@@ -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'