jfs-components 0.1.30 → 0.1.32

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 (41) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/lib/commonjs/components/CategoryCard/CategoryCard.js +264 -0
  3. package/lib/commonjs/components/CompareTable/CompareTable.js +140 -84
  4. package/lib/commonjs/components/ContentSheet/ContentSheet.js +28 -5
  5. package/lib/commonjs/components/CounterBadge/CounterBadge.js +78 -0
  6. package/lib/commonjs/components/FavoriteToggle/FavoriteToggle.js +180 -0
  7. package/lib/commonjs/components/HelloJioInput/HelloJioInput.js +287 -0
  8. package/lib/commonjs/components/ValueBackMetric/ValueBackMetric.js +219 -0
  9. package/lib/commonjs/components/index.js +35 -0
  10. package/lib/commonjs/design-tokens/figma-modes.generated.js +3 -0
  11. package/lib/commonjs/icons/registry.js +1 -1
  12. package/lib/module/components/CategoryCard/CategoryCard.js +258 -0
  13. package/lib/module/components/CompareTable/CompareTable.js +140 -84
  14. package/lib/module/components/ContentSheet/ContentSheet.js +29 -6
  15. package/lib/module/components/CounterBadge/CounterBadge.js +73 -0
  16. package/lib/module/components/FavoriteToggle/FavoriteToggle.js +174 -0
  17. package/lib/module/components/HelloJioInput/HelloJioInput.js +281 -0
  18. package/lib/module/components/ValueBackMetric/ValueBackMetric.js +214 -0
  19. package/lib/module/components/index.js +6 -1
  20. package/lib/module/design-tokens/figma-modes.generated.js +3 -0
  21. package/lib/module/icons/registry.js +1 -1
  22. package/lib/typescript/src/components/CategoryCard/CategoryCard.d.ts +72 -0
  23. package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +16 -1
  24. package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +7 -1
  25. package/lib/typescript/src/components/CounterBadge/CounterBadge.d.ts +19 -0
  26. package/lib/typescript/src/components/FavoriteToggle/FavoriteToggle.d.ts +66 -0
  27. package/lib/typescript/src/components/HelloJioInput/HelloJioInput.d.ts +111 -0
  28. package/lib/typescript/src/components/ValueBackMetric/ValueBackMetric.d.ts +86 -0
  29. package/lib/typescript/src/components/index.d.ts +5 -0
  30. package/lib/typescript/src/icons/registry.d.ts +1 -1
  31. package/package.json +1 -1
  32. package/src/components/CategoryCard/CategoryCard.tsx +404 -0
  33. package/src/components/CompareTable/CompareTable.tsx +201 -101
  34. package/src/components/ContentSheet/ContentSheet.tsx +40 -11
  35. package/src/components/CounterBadge/CounterBadge.tsx +95 -0
  36. package/src/components/FavoriteToggle/FavoriteToggle.tsx +243 -0
  37. package/src/components/HelloJioInput/HelloJioInput.tsx +513 -0
  38. package/src/components/ValueBackMetric/ValueBackMetric.tsx +298 -0
  39. package/src/components/index.ts +5 -0
  40. package/src/design-tokens/figma-modes.generated.ts +3 -0
  41. 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'
@@ -319,6 +319,7 @@ export const FIGMA_COMPONENT_MODES: Record<string, readonly string[]> = {
319
319
  "CardProviderInfo": ["AppearanceBrand", "Avatar Size", "Badge Size", "Color Mode", "context 10", "Context4", "Profile Card Appearance"],
320
320
  "Carousel": ["peekOffset"],
321
321
  "CarouselCardAccounts": ["peekOffset"],
322
+ "CategoryCard": ["AppearanceBrand", "Badge Size", "Color Mode", "Emphasis", "Radius"],
322
323
  "CcCard": ["AppearanceBrand", "AppearanceSystem", "Avatar Size", "Badge Size", "Button / Size", "Button / State", "Color Mode", "Context", "context 10", "context 8", "Context2", "Context4", "context5", "context7", "Emphasis", "List Item Style", "ListItem State", "NavArrow Direction", "Page type", "Profile Card Appearance", "Radius", "Selectable", "Semantic Intent", "Text Appearance", "Text Sizes", "Weight"],
323
324
  "Checkbox": ["Color Mode"],
324
325
  "CheckboxGroup": ["Color Mode"],
@@ -348,6 +349,7 @@ export const FIGMA_COMPONENT_MODES: Record<string, readonly string[]> = {
348
349
  "FullscreenModal": ["AppearanceBrand", "AppearanceSystem", "Button / Size", "Button / State", "Color Mode", "Context", "context 10", "context5", "Emphasis", "Profile Card Appearance", "Semantic Intent", "Slot gap"],
349
350
  "Gauge": ["AppearanceBrand", "AppearanceSystem", "Color Mode", "FormField States", "Semantic Intent", "Status"],
350
351
  "Grid": ["Background", "Color Mode", "Context", "Padding", "Page type", "Slot gap"],
352
+ "HelloJioInput": ["AppearanceBrand", "Button / Size", "Button / State", "Color Mode", "Emphasis"],
351
353
  "HeroSection": ["AppearanceBrand", "Button / Size", "Button / State", "Color Mode", "context 9", "context7", "Emphasis", "FormField States", "InputState", "Page type", "Status"],
352
354
  "HoldingsCard": ["AppearanceBrand", "Color Mode"],
353
355
  "HStack": ["Context", "Padding", "Page type", "Slot gap"],
@@ -416,6 +418,7 @@ export const FIGMA_COMPONENT_MODES: Record<string, readonly string[]> = {
416
418
  "TransactionBubble": ["Color Mode", "context 10", "Context2", "Context3", "context5", "List Item Style", "NavArrow Direction", "Page type", "Profile Card Appearance", "Transaction Status"],
417
419
  "TransactionStatus": ["Transaction Status"],
418
420
  "UpiHandle": ["Color Mode", "context 10", "Profile Card Appearance", "UPI Handle Image"],
421
+ "ValueBackMetric": ["AppearanceBrand", "Badge Size", "Color Mode", "Context", "context 10", "Context4", "Emphasis", "Profile Card Appearance", "Text Appearance", "Text Sizes", "Weight"],
419
422
  "VStack": ["Context", "Padding", "Page type", "Slot gap"],
420
423
  };
421
424
 
@@ -4,7 +4,7 @@
4
4
  * Auto-generated from SVG files in src/icons/
5
5
  * DO NOT EDIT MANUALLY - Run "npm run icons:generate" to regenerate
6
6
  *
7
- * Generated: 2026-07-10T20:55:36.505Z
7
+ * Generated: 2026-07-15T20:21:39.992Z
8
8
  */
9
9
 
10
10
  // Icon name to SVG data mapping