jfs-components 0.1.36 → 0.1.48

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 (47) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/lib/commonjs/components/Accordion/Accordion.js +16 -3
  3. package/lib/commonjs/components/AppBar/AppBar.js +118 -93
  4. package/lib/commonjs/components/CompareTable/CompareTable.js +98 -33
  5. package/lib/commonjs/components/FormField/FormField.js +9 -1
  6. package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +4 -14
  7. package/lib/commonjs/components/HelloJioInput/HelloJioInput.js +20 -4
  8. package/lib/commonjs/components/PageHero/PageHero.js +3 -0
  9. package/lib/commonjs/components/ProductMerchandisingCard/ProductMerchandisingCard.js +50 -30
  10. package/lib/commonjs/components/ValueBackMetric/ValueBackMetric.js +117 -89
  11. package/lib/commonjs/components/index.js +7 -1
  12. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -42204
  13. package/lib/commonjs/icons/registry.js +1 -1
  14. package/lib/module/components/Accordion/Accordion.js +16 -3
  15. package/lib/module/components/AppBar/AppBar.js +116 -93
  16. package/lib/module/components/CompareTable/CompareTable.js +99 -34
  17. package/lib/module/components/FormField/FormField.js +9 -1
  18. package/lib/module/components/FullscreenModal/FullscreenModal.js +4 -14
  19. package/lib/module/components/HelloJioInput/HelloJioInput.js +20 -4
  20. package/lib/module/components/PageHero/PageHero.js +3 -0
  21. package/lib/module/components/ProductMerchandisingCard/ProductMerchandisingCard.js +50 -30
  22. package/lib/module/components/ValueBackMetric/ValueBackMetric.js +120 -93
  23. package/lib/module/components/index.js +1 -1
  24. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -42204
  25. package/lib/module/icons/registry.js +1 -1
  26. package/lib/typescript/src/components/Accordion/Accordion.d.ts +14 -1
  27. package/lib/typescript/src/components/AppBar/AppBar.d.ts +46 -14
  28. package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +29 -12
  29. package/lib/typescript/src/components/HelloJioInput/HelloJioInput.d.ts +6 -4
  30. package/lib/typescript/src/components/PageHero/PageHero.d.ts +1 -0
  31. package/lib/typescript/src/components/ProductMerchandisingCard/ProductMerchandisingCard.d.ts +8 -1
  32. package/lib/typescript/src/components/ValueBackMetric/ValueBackMetric.d.ts +27 -16
  33. package/lib/typescript/src/components/index.d.ts +1 -1
  34. package/lib/typescript/src/icons/registry.d.ts +1 -1
  35. package/package.json +1 -1
  36. package/src/components/Accordion/Accordion.tsx +61 -33
  37. package/src/components/AppBar/AppBar.tsx +154 -124
  38. package/src/components/CompareTable/CompareTable.tsx +138 -52
  39. package/src/components/FormField/FormField.tsx +7 -0
  40. package/src/components/FullscreenModal/FullscreenModal.tsx +5 -14
  41. package/src/components/HelloJioInput/HelloJioInput.tsx +31 -12
  42. package/src/components/PageHero/PageHero.tsx +5 -0
  43. package/src/components/ProductMerchandisingCard/ProductMerchandisingCard.tsx +63 -30
  44. package/src/components/ValueBackMetric/ValueBackMetric.tsx +178 -104
  45. package/src/components/index.ts +6 -1
  46. package/src/design-tokens/Coin Variables-variables-full.json +1 -42204
  47. package/src/icons/registry.ts +1 -1
@@ -1,74 +1,158 @@
1
- import React, { useState } from 'react'
2
- import { View, type StyleProp, type ViewStyle, Pressable } from 'react-native'
1
+ import React, { useMemo } from 'react'
2
+ import {
3
+ View,
4
+ Pressable,
5
+ type StyleProp,
6
+ type ViewStyle,
7
+ } from 'react-native'
8
+ import Svg, { Path, Defs, LinearGradient, Stop, ClipPath, Rect, G } from 'react-native-svg'
3
9
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
4
10
  import { useTokens } from '../../design-tokens/JFSThemeProvider'
5
-
6
11
  import NavArrow from '../NavArrow/NavArrow'
7
12
  import { cloneChildrenWithModes, EMPTY_MODES } from '../../utils/react-utils'
8
13
  import type { Modes } from '../../design-tokens'
9
14
 
10
15
  type AppBarType = 'MainPage' | 'SubPage'
11
16
 
12
- // SubPage "slot wrap" geometry, taken directly from the Figma design
13
- // (node 449:7876). The middle slot is an absolutely-centered box of a fixed
14
- // size; its inner content (node 3991:4125) is a `flex: 1 0 0; min-width: 1px`
15
- // item so it fills / shrinks responsively within that box.
17
+ // SubPage "slot wrap" geometry from Figma (node 449:7876). The middle slot is
18
+ // an absolutely-centered fixed-size box; its inner content (node 3991:4125)
19
+ // fills / shrinks responsively within that box.
16
20
  const SUBPAGE_MIDDLE_DEFAULT_WIDTH = 192
17
21
  const SUBPAGE_MIDDLE_HEIGHT = 32
18
22
  const SUBPAGE_MIDDLE_PADDING_HORIZONTAL = 21
23
+ const JIODOT_FALLBACK_SIZE = 32
24
+
25
+ export type JioDotProps = {
26
+ /** Render size in px. Defaults to token `appBar/mainPage/jiodot/size` (32). */
27
+ size?: number
28
+ /** Modes used to resolve `appBar/mainPage/jiodot/size` when `size` is omitted. */
29
+ modes?: Modes
30
+ }
31
+
32
+ /**
33
+ * Figma "jiodot" mark (node 1117:1908). Opt-in via `leadingSlot` — MainPage
34
+ * does NOT render this by default so existing consumers stay empty-leading.
35
+ */
36
+ export function JioDot({ size, modes = EMPTY_MODES }: JioDotProps) {
37
+ const resolvedSize =
38
+ size ??
39
+ (Number(getVariableByName('appBar/mainPage/jiodot/size', modes)) ||
40
+ JIODOT_FALLBACK_SIZE)
41
+ return (
42
+ <Svg
43
+ width={resolvedSize}
44
+ height={resolvedSize}
45
+ viewBox="0 0 32 32"
46
+ fill="none"
47
+ accessibilityRole="image"
48
+ accessibilityLabel="Jio"
49
+ >
50
+ <G clipPath="url(#jiodotClip)">
51
+ <Path
52
+ d="M0 16C0 7.16344 7.16344 0 16 0C24.8366 0 32 7.16344 32 16C32 24.8366 24.8366 32 16 32C7.16344 32 0 24.8366 0 16Z"
53
+ fill="url(#jiodotGrad)"
54
+ />
55
+ <Path
56
+ d="M11.3039 9.64982H10.7705C9.75837 9.64982 9.20548 10.2205 9.20548 11.3621V16.8679C9.20548 18.2854 8.72667 18.7829 7.60341 18.7829C6.71985 18.7829 6.00192 18.3959 5.43096 17.6963C5.37555 17.6229 4.21615 18.1749 4.21615 19.5378C4.21615 21.0107 5.59629 21.9132 8.15689 21.9132C11.268 21.9132 12.9077 20.3479 12.9077 16.9233V11.3621C12.9059 10.222 12.3536 9.64982 11.3039 9.64982ZM23.7039 12.3381C20.6837 12.3381 18.6769 14.2531 18.6769 17.1085C18.6769 20.0365 20.6096 21.915 23.6482 21.915C26.6674 21.915 28.6556 20.0365 28.6556 17.1274C28.6573 14.2531 26.6867 12.3381 23.7039 12.3381ZM23.6668 19.206C22.4879 19.206 21.6781 18.3408 21.6781 17.107C21.6781 15.8922 22.5077 15.0264 23.6668 15.0264C24.8259 15.0264 25.6553 15.8922 25.6553 17.1257C25.6556 18.3221 24.8084 19.206 23.6668 19.206ZM16.1252 12.43H15.7563C14.8547 12.43 14.1735 12.8531 14.1735 14.1426V20.0347C14.1735 21.3423 14.8366 21.7476 15.7936 21.7476H16.1622C17.0644 21.7476 17.7086 21.3052 17.7086 20.0347V14.1426C17.7086 12.8169 17.0831 12.43 16.1252 12.43ZM15.9228 8.15885C14.7809 8.15885 14.063 8.80359 14.063 9.81634C14.063 10.8469 14.7999 11.4913 15.9779 11.4913C17.1196 11.4913 17.8378 10.8469 17.8378 9.81634C17.8378 8.78582 17.1012 8.15885 15.9228 8.15885Z"
57
+ fill="white"
58
+ />
59
+ </G>
60
+ <Defs>
61
+ <LinearGradient
62
+ id="jiodotGrad"
63
+ x1="11.085"
64
+ y1="9.25442"
65
+ x2="22.3469"
66
+ y2="15.7638"
67
+ gradientUnits="userSpaceOnUse"
68
+ >
69
+ <Stop stopColor="#FBDCAF" />
70
+ <Stop offset="1" stopColor="#AD8444" />
71
+ </LinearGradient>
72
+ <ClipPath id="jiodotClip">
73
+ <Rect width="32" height="32" fill="white" />
74
+ </ClipPath>
75
+ </Defs>
76
+ </Svg>
77
+ )
78
+ }
19
79
 
20
80
  export type AppBarProps = {
21
81
  /**
22
82
  * Type of the App Bar.
23
- * - `MainPage`: Taller, usually has Jio logo (leading) and Profile/Notif (actions).
24
- * - `SubPage`: Shorter, usually has Back arrow (leading) and Page Title (middle).
83
+ * - `MainPage`: Taller bar; no default leading (pass `JioDot` or your logo via `leadingSlot`).
84
+ * - `SubPage`: Shorter bar with default back arrow and centered middle slot.
25
85
  * @default "MainPage"
26
86
  */
27
- type?: AppBarType;
87
+ type?: AppBarType
28
88
  /**
29
89
  * Slot for the leading element.
30
90
  * Defaults:
31
- * - MainPage: Jio Logo (placeholder circle for now if no asset)
32
- * - SubPage: Back Icon Button
91
+ * - MainPage: none (opt in with `<JioDot />` or any custom node)
92
+ * - SubPage: Back `NavArrow`
33
93
  */
34
- leadingSlot?: React.ReactNode;
94
+ leadingSlot?: React.ReactNode
35
95
  /**
36
- * Slot for the middle content.
37
- * Often used for "Page Title" in SubPage.
96
+ * Slot for the middle content. Often used for a page title on SubPage.
38
97
  *
39
- * On `SubPage` this is rendered as an absolutely-centered box (matching the
40
- * Figma "slot wrap"): it stays centered in the bar regardless of how wide
41
- * the leading/actions slots are, and its content fills/shrinks responsively
42
- * within {@link middleSlotWidth}.
98
+ * On `SubPage` this is rendered as an absolutely-centered box (Figma
99
+ * "slot wrap"): it stays centered regardless of leading/actions width, and
100
+ * content fills/shrinks within {@link middleSlotWidth}.
43
101
  */
44
- middleSlot?: React.ReactNode;
102
+ middleSlot?: React.ReactNode
45
103
  /**
46
104
  * Width of the centered `SubPage` middle slot, in px.
47
105
  * Defaults to the Figma value (192). Has no effect on `MainPage`.
48
106
  * @default 192
49
107
  */
50
- middleSlotWidth?: number;
108
+ middleSlotWidth?: number
51
109
  /**
52
- * Slot for the actions on the right.
110
+ * Slot for the actions on the right (IconButtons, Avatar, etc.).
53
111
  */
54
- actionsSlot?: React.ReactNode;
112
+ actionsSlot?: React.ReactNode
55
113
  /**
56
114
  * Token modes to override.
57
115
  */
58
- modes?: Modes;
116
+ modes?: Modes
59
117
  /**
60
- * Callback for the default leading slot press (e.g. back button).
118
+ * Callback for the default SubPage leading back arrow.
61
119
  */
62
- onLeadingPress?: () => void;
120
+ onLeadingPress?: () => void
63
121
  /**
64
122
  * Container style override.
65
123
  */
66
- style?: StyleProp<ViewStyle>;
67
- accessibilityLabel?: string;
68
- accessibilityHint?: string;
69
- } & Omit<React.ComponentProps<typeof View>, 'style' | 'accessibilityRole' | 'accessibilityLabel' | 'accessibilityHint'>;
124
+ style?: StyleProp<ViewStyle>
125
+ accessibilityLabel?: string
126
+ accessibilityHint?: string
127
+ } & Omit<
128
+ React.ComponentProps<typeof View>,
129
+ 'style' | 'accessibilityRole' | 'accessibilityLabel' | 'accessibilityHint'
130
+ >
70
131
 
71
- export default function AppBar({
132
+ /**
133
+ * AppBar — top navigation bar for MainPage and SubPage layouts
134
+ * (Figma node 1070:18571).
135
+ *
136
+ * Visual values resolve from design tokens via `getVariableByName`. Slots
137
+ * cascade `modes` to children through `cloneChildrenWithModes`.
138
+ *
139
+ * @component
140
+ * @example
141
+ * ```tsx
142
+ * <AppBar
143
+ * type="MainPage"
144
+ * leadingSlot={<JioDot />}
145
+ * actionsSlot={
146
+ * <>
147
+ * <IconButton iconName="ic_add" />
148
+ * <IconButton iconName="ic_add" />
149
+ * <Avatar />
150
+ * </>
151
+ * }
152
+ * />
153
+ * ```
154
+ */
155
+ function AppBar({
72
156
  type = 'MainPage',
73
157
  leadingSlot,
74
158
  middleSlot,
@@ -82,14 +166,14 @@ export default function AppBar({
82
166
  ...rest
83
167
  }: AppBarProps) {
84
168
  const { modes: globalModes } = useTokens()
85
- const modes = { Context2: 'AppBar', ...globalModes, ...propModes }
169
+ const modes = useMemo(
170
+ () => ({ Context2: 'AppBar', ...globalModes, ...propModes }),
171
+ [globalModes, propModes]
172
+ )
86
173
 
87
174
  const isMain = type === 'MainPage'
88
175
  const isSub = type === 'SubPage'
89
176
 
90
- // --- Tokens ---
91
- // Construct token keys based on type. keys must be camelCase as per instruction.
92
- // We use explicit string literals so that the extraction script can find them.
93
177
  const paddingHorizontal = isMain
94
178
  ? getVariableByName('appBar/mainPage/padding/horizontal', modes)
95
179
  : getVariableByName('appBar/subPage/padding/horizontal', modes)
@@ -106,61 +190,35 @@ export default function AppBar({
106
190
  ? getVariableByName('appBar/mainPage/actions/gap', modes)
107
191
  : getVariableByName('appBar/subPage/actions/gap', modes)
108
192
 
109
- // Layout styles
110
193
  const containerStyle: ViewStyle = {
111
194
  flexDirection: 'row',
112
195
  alignItems: 'center',
113
- // No `justifyContent` here: with the inline middle slot using `flex: 1`
114
- // the three sections lay out naturally (leading | middle | actions).
115
- // When middleSlot is absent we fall back to `space-between` at the wrapper
116
- // level so leading & actions still anchor to the edges.
117
- paddingHorizontal: paddingHorizontal ?? 16,
118
- paddingVertical: paddingVertical ?? (isMain ? 16 : 10),
119
- backgroundColor: backgroundColor ?? '#FFFFFF',
120
- // MainPage: h=68 (16 top/bot padding? 36 height content?)
121
- // SubPage: h=52
196
+ paddingHorizontal: (paddingHorizontal as number) ?? 16,
197
+ paddingVertical: (paddingVertical as number) ?? (isMain ? 16 : 10),
198
+ backgroundColor: (backgroundColor as string) ?? '#FFFFFF',
122
199
  }
123
200
 
124
- // --- Leading Slot Default ---
125
- // SubPage default: Back Arrow
126
- // MainPage default: Jio Logo (We'll use a placeholder or basic text if no asset,
127
- // but looking at "AppBarHome" before, it had "ic_hellojio". Let's stick to generic defaults or empty if not provided,
128
- // but usually a component library provides sensible defaults.)
129
-
130
- // Actually, let's implement the specific defaults mentioned in the plan/analysis
131
- // SubPage: NavArrow props (icon, etc).
132
-
201
+ // MainPage: no default leading (backward compatible — consumers opt in via
202
+ // leadingSlot, e.g. <JioDot />). SubPage: default back NavArrow.
133
203
  let defaultLeading: React.ReactNode = null
134
- if (leadingSlot === undefined) {
135
- if (isSub) {
136
- defaultLeading = (
137
- <Pressable
138
- {...(onLeadingPress ? { onPress: onLeadingPress } : {})}
139
- hitSlop={8}
140
- accessibilityRole="button"
141
- accessibilityLabel={undefined}
142
- style={({ pressed }) => ({
143
- opacity: pressed ? 0.7 : 1,
144
- alignItems: 'center',
145
- justifyContent: 'center',
146
- })}
147
- >
148
- <NavArrow direction="Back" modes={modes} />
149
- </Pressable>
150
- )
151
- } else {
152
- // MainPage default leading: often the Brand Logo.
153
- // We'll leave it empty or render a placeholder if desired, but standard AppBar often lets consumer pass logo.
154
- // Previous code had `leadingIconName='ic_hellojio'` for Home.
155
- // We will render nothing by default for MainPage leading to keep it flexible, OR a specialized Logo component if we had one.
156
- // For now, let's assume consumer passes it for MainPage, or we leave empty.
157
- // Wait, Figma screenshot shows "Jio" logo.
158
- // The user said: "mostly token namings are 'camelCase'. Maximize existing component usage."
159
- // I'll leave defaultLeading as null for MainPage unless I have a "BrandLogo" component.
160
- }
204
+ if (leadingSlot === undefined && isSub) {
205
+ defaultLeading = (
206
+ <Pressable
207
+ {...(onLeadingPress ? { onPress: onLeadingPress } : {})}
208
+ hitSlop={8}
209
+ accessibilityRole="button"
210
+ accessibilityLabel={undefined}
211
+ style={({ pressed }) => ({
212
+ opacity: pressed ? 0.7 : 1,
213
+ alignItems: 'center',
214
+ justifyContent: 'center',
215
+ })}
216
+ >
217
+ <NavArrow direction="Back" modes={modes} />
218
+ </Pressable>
219
+ )
161
220
  }
162
221
 
163
- // --- Process Slots ---
164
222
  const processedLeading = leadingSlot
165
223
  ? cloneChildrenWithModes([leadingSlot], modes)[0]
166
224
  : defaultLeading
@@ -169,40 +227,28 @@ export default function AppBar({
169
227
  ? cloneChildrenWithModes(React.Children.toArray(middleSlot), modes)
170
228
  : null
171
229
 
172
- // Actions Gap wrapper
173
- // The Figma has "Actions" slot with a gap.
174
230
  const actionsStyle: ViewStyle = {
175
231
  flexDirection: 'row',
176
232
  alignItems: 'center',
177
- gap: actionsGap ?? 0,
233
+ gap: (actionsGap as number) ?? 0,
178
234
  }
179
235
 
180
236
  const processedActions = actionsSlot
181
- ? <View style={actionsStyle}>{cloneChildrenWithModes(React.Children.toArray(actionsSlot), modes)}</View>
237
+ ? cloneChildrenWithModes(React.Children.toArray(actionsSlot), modes)
182
238
  : null
183
239
 
184
- // SubPage centers its middle slot via absolute positioning (see Figma
185
- // "slot wrap"), so it never participates in the row flow. Only MainPage
186
- // keeps the legacy in-flow middle slot.
240
+ // SubPage middle is absolute (Figma "slot wrap"); MainPage middle stays in-flow.
187
241
  const hasInFlowMiddle = isMain && !!processedMiddle
188
242
 
189
- // With an in-flow middle (MainPage) the middle (flex: 1) absorbs the
190
- // remaining space, so leading & actions sit at the edges naturally. In all
191
- // other cases we pin leading & actions to the outer edges with
192
- // `space-between`; the SubPage middle floats above, centered.
243
+ // With an in-flow middle the flex:1 middle absorbs leftover space. Otherwise
244
+ // pin leading & actions to the edges; SubPage middle floats centered above.
193
245
  const wrapperStyle: ViewStyle = {
194
246
  ...containerStyle,
195
247
  justifyContent: hasInFlowMiddle ? 'flex-start' : 'space-between',
196
248
  }
197
249
 
198
- // Absolutely-positioned overlay for the SubPage middle slot. Instead of
199
- // centering a fixed-height box with `top/left: 50%` + transform (which on
200
- // Android resolves the percentage against an intermediate, content-driven
201
- // parent height and lands a few px too high), the overlay STRETCHES to fill
202
- // the whole bar (`top/bottom/left/right: 0`) and centers its content with
203
- // flexbox. This uses the exact same full-height vertical reference as the
204
- // leading/actions row (`alignItems: 'center'`), so the middle content always
205
- // sits on the same line as the back arrow and end slot on every platform.
250
+ // Overlay stretches to the full bar and centers content with flexbox so the
251
+ // middle aligns with leading/actions on every platform.
206
252
  const subPageMiddleOverlayStyle: ViewStyle = {
207
253
  position: 'absolute',
208
254
  top: 0,
@@ -214,9 +260,6 @@ export default function AppBar({
214
260
  justifyContent: 'center',
215
261
  }
216
262
 
217
- // Fixed-width clipping box (mirrors the Figma "slot wrap" geometry): keeps the
218
- // middle content from bleeding under the leading/actions slots while staying
219
- // centered within the overlay above.
220
263
  const subPageMiddleBoxStyle: ViewStyle = {
221
264
  width: middleSlotWidth,
222
265
  height: SUBPAGE_MIDDLE_HEIGHT,
@@ -235,18 +278,11 @@ export default function AppBar({
235
278
  {...(accessibilityHint ? { accessibilityHint } : {})}
236
279
  {...rest}
237
280
  >
238
- {/* Leading Section */}
239
281
  <View style={{ flexDirection: 'row', alignItems: 'center' }}>
240
282
  {processedLeading}
241
283
  </View>
242
284
 
243
- {/*
244
- * MainPage in-flow middle — occupies the space between leading and
245
- * actions (`flex: 1`) without overflowing. `minWidth: 0` lets the flex
246
- * item shrink below its content's intrinsic width on platforms that
247
- * respect it (web).
248
- */}
249
- {hasInFlowMiddle && (
285
+ {hasInFlowMiddle ? (
250
286
  <View
251
287
  style={{
252
288
  flex: 1,
@@ -259,19 +295,11 @@ export default function AppBar({
259
295
  >
260
296
  {processedMiddle}
261
297
  </View>
262
- )}
298
+ ) : null}
263
299
 
264
- {/* Actions Section */}
265
- <View style={actionsStyle}>
266
- {processedActions}
267
- </View>
300
+ <View style={actionsStyle}>{processedActions}</View>
268
301
 
269
- {/*
270
- * SubPage middle — absolutely centered "slot wrap". The inner wrapper is
271
- * a responsive `flex: 1` item (matching Figma's `flex-[1_0_0] min-w-px`)
272
- * so its content fills / shrinks within the fixed-width box.
273
- */}
274
- {isSub && processedMiddle && (
302
+ {isSub && processedMiddle ? (
275
303
  <View style={subPageMiddleOverlayStyle} pointerEvents="box-none">
276
304
  <View style={subPageMiddleBoxStyle} pointerEvents="box-none">
277
305
  <View
@@ -289,7 +317,9 @@ export default function AppBar({
289
317
  </View>
290
318
  </View>
291
319
  </View>
292
- )}
320
+ ) : null}
293
321
  </View>
294
322
  )
295
323
  }
324
+
325
+ export default AppBar