jfs-components 0.1.25 → 0.1.28

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 (81) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/lib/commonjs/components/AutoplayControl/AutoplayControl.js +66 -0
  3. package/lib/commonjs/components/Carousel/Carousel.js +112 -19
  4. package/lib/commonjs/components/ContentSheet/ContentSheet.js +39 -5
  5. package/lib/commonjs/components/FormUpload/Additem.js +168 -0
  6. package/lib/commonjs/components/FormUpload/FormUpload.js +198 -0
  7. package/lib/commonjs/components/Grid/Grid.js +206 -0
  8. package/lib/commonjs/components/NumberPagination/NumberPagination.js +144 -0
  9. package/lib/commonjs/components/ProfileCard/ProfileCard.js +208 -0
  10. package/lib/commonjs/components/UpiHandle/UpiHandle.js +23 -11
  11. package/lib/commonjs/components/index.js +42 -0
  12. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  13. package/lib/commonjs/design-tokens/figma-modes.generated.js +21 -10
  14. package/lib/commonjs/icons/components/IconBrokenimage.js +19 -0
  15. package/lib/commonjs/icons/components/IconImage.js +19 -0
  16. package/lib/commonjs/icons/components/IconMic.js +19 -0
  17. package/lib/commonjs/icons/components/IconMicoff.js +19 -0
  18. package/lib/commonjs/icons/components/IconPause.js +19 -0
  19. package/lib/commonjs/icons/components/IconPlay.js +19 -0
  20. package/lib/commonjs/icons/components/index.js +66 -0
  21. package/lib/commonjs/icons/registry.js +2 -2
  22. package/lib/module/components/AutoplayControl/AutoplayControl.js +60 -0
  23. package/lib/module/components/Carousel/Carousel.js +111 -19
  24. package/lib/module/components/ContentSheet/ContentSheet.js +41 -7
  25. package/lib/module/components/FormUpload/Additem.js +162 -0
  26. package/lib/module/components/FormUpload/FormUpload.js +192 -0
  27. package/lib/module/components/Grid/Grid.js +201 -0
  28. package/lib/module/components/NumberPagination/NumberPagination.js +138 -0
  29. package/lib/module/components/ProfileCard/ProfileCard.js +202 -0
  30. package/lib/module/components/UpiHandle/UpiHandle.js +23 -11
  31. package/lib/module/components/index.js +7 -1
  32. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  33. package/lib/module/design-tokens/figma-modes.generated.js +21 -10
  34. package/lib/module/icons/components/IconBrokenimage.js +12 -0
  35. package/lib/module/icons/components/IconImage.js +12 -0
  36. package/lib/module/icons/components/IconMic.js +12 -0
  37. package/lib/module/icons/components/IconMicoff.js +12 -0
  38. package/lib/module/icons/components/IconPause.js +12 -0
  39. package/lib/module/icons/components/IconPlay.js +12 -0
  40. package/lib/module/icons/components/index.js +6 -0
  41. package/lib/module/icons/registry.js +2 -2
  42. package/lib/typescript/src/components/AutoplayControl/AutoplayControl.d.ts +15 -0
  43. package/lib/typescript/src/components/Carousel/Carousel.d.ts +8 -1
  44. package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +7 -1
  45. package/lib/typescript/src/components/FormUpload/Additem.d.ts +96 -0
  46. package/lib/typescript/src/components/FormUpload/FormUpload.d.ts +85 -0
  47. package/lib/typescript/src/components/Grid/Grid.d.ts +87 -0
  48. package/lib/typescript/src/components/NumberPagination/NumberPagination.d.ts +21 -0
  49. package/lib/typescript/src/components/ProfileCard/ProfileCard.d.ts +89 -0
  50. package/lib/typescript/src/components/UpiHandle/UpiHandle.d.ts +7 -1
  51. package/lib/typescript/src/components/index.d.ts +34 -28
  52. package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +10 -4
  53. package/lib/typescript/src/icons/components/IconBrokenimage.d.ts +3 -0
  54. package/lib/typescript/src/icons/components/IconImage.d.ts +3 -0
  55. package/lib/typescript/src/icons/components/IconMic.d.ts +3 -0
  56. package/lib/typescript/src/icons/components/IconMicoff.d.ts +3 -0
  57. package/lib/typescript/src/icons/components/IconPause.d.ts +3 -0
  58. package/lib/typescript/src/icons/components/IconPlay.d.ts +3 -0
  59. package/lib/typescript/src/icons/components/index.d.ts +6 -0
  60. package/lib/typescript/src/icons/registry.d.ts +1 -1
  61. package/package.json +1 -1
  62. package/src/components/AutoplayControl/AutoplayControl.tsx +77 -0
  63. package/src/components/Carousel/Carousel.tsx +133 -23
  64. package/src/components/ContentSheet/ContentSheet.tsx +50 -0
  65. package/src/components/FormUpload/Additem.tsx +262 -0
  66. package/src/components/FormUpload/FormUpload.tsx +313 -0
  67. package/src/components/Grid/Grid.tsx +368 -0
  68. package/src/components/NumberPagination/NumberPagination.tsx +170 -0
  69. package/src/components/ProfileCard/ProfileCard.tsx +268 -0
  70. package/src/components/UpiHandle/UpiHandle.tsx +35 -24
  71. package/src/components/index.ts +329 -182
  72. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  73. package/src/design-tokens/figma-modes.generated.ts +21 -10
  74. package/src/icons/components/IconBrokenimage.tsx +11 -0
  75. package/src/icons/components/IconImage.tsx +11 -0
  76. package/src/icons/components/IconMic.tsx +11 -0
  77. package/src/icons/components/IconMicoff.tsx +11 -0
  78. package/src/icons/components/IconPause.tsx +11 -0
  79. package/src/icons/components/IconPlay.tsx +11 -0
  80. package/src/icons/components/index.ts +6 -0
  81. package/src/icons/registry.ts +25 -1
@@ -0,0 +1,268 @@
1
+ import React, { useMemo } from 'react'
2
+ import {
3
+ View,
4
+ type StyleProp,
5
+ type ViewStyle,
6
+ type ImageSourcePropType,
7
+ } from 'react-native'
8
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
9
+ import { useTokens } from '../../design-tokens/JFSThemeProvider'
10
+ import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
11
+ import Avatar from '../Avatar/Avatar'
12
+ import Attached from '../Attached/Attached'
13
+ import IconCapsule from '../IconCapsule/IconCapsule'
14
+ import ListItem from '../ListItem/ListItem'
15
+ import UpiHandle from '../UpiHandle/UpiHandle'
16
+ import Divider from '../Divider/Divider'
17
+ import type { Modes } from '../../design-tokens'
18
+
19
+ /**
20
+ * A single UPI handle pill rendered under the profile name.
21
+ */
22
+ export interface ProfileCardUpiHandle {
23
+ /** Handle text, e.g. `"username@jio"` or a phone number. */
24
+ label: string
25
+ /** Trailing registry icon (e.g. `'ic_copy'`, `'ic_confirm'`). */
26
+ iconName?: string
27
+ /** Toggles the trailing icon. Defaults to `true`. */
28
+ showIcon?: boolean
29
+ /** Makes the pill pressable (e.g. copy-to-clipboard). */
30
+ onPress?: () => void
31
+ }
32
+
33
+ export interface ProfileCardProps {
34
+ /** User's display name shown in the header. */
35
+ name?: string
36
+ /** Monogram initials for the avatar (used when `avatarSource` is not set). */
37
+ monogram?: string
38
+ /** Avatar image source. When set, the avatar renders the image instead of the monogram. */
39
+ avatarSource?: ImageSourcePropType | string
40
+ /**
41
+ * Registry icon for the small badge attached to the avatar's bottom-right
42
+ * corner. Pass `null` to hide the badge. Defaults to `'ic_qr_code'`.
43
+ */
44
+ badgeIcon?: string | null
45
+ /** UPI handle pills rendered under the name. Pass `[]` to render none. */
46
+ upiHandles?: ProfileCardUpiHandle[]
47
+ /**
48
+ * Full override for the header row (avatar + name + handles). Takes
49
+ * precedence over the declarative header props. `modes` cascade into it.
50
+ */
51
+ header?: React.ReactNode
52
+ /**
53
+ * Card body (the Figma "slot"). Rendered below the header divider; `modes`
54
+ * (including the profile-card context) cascade into every child, so nested
55
+ * `ListItem`s, `Divider`s, `Text`s and `Button`s resolve the on-dark
56
+ * profile-card token values automatically.
57
+ */
58
+ children?: React.ReactNode
59
+ /** Toggles the divider between the header and the body. Defaults to `true`. */
60
+ showDivider?: boolean
61
+ /** Card width. Defaults to the Figma spec (`344`). Pass `'100%'` to fill the parent. */
62
+ width?: number | `${number}%`
63
+ /** Modes object for design-token resolution, cascaded to all children. */
64
+ modes?: Modes
65
+ /** Style overrides for the card container. */
66
+ style?: StyleProp<ViewStyle>
67
+ /** Accessibility label for the card. */
68
+ accessibilityLabel?: string
69
+ }
70
+
71
+ // Forced modes for everything rendered inside the card — the profile card is
72
+ // an on-dark (jioPlus) surface, so child components (Avatar, UpiHandle,
73
+ // ListItem, Divider, Text, Button, …) must resolve their tokens through the
74
+ // `context 10: jioPlus` mode. Consumer modes are spread first so appearance
75
+ // modes (e.g. `Profile Card Appearance: Premium`) still cascade through.
76
+ const CARD_CONTEXT: Modes = Object.freeze({ 'context 10': 'jioPlus' })
77
+
78
+ const DEFAULT_UPI_HANDLES: ProfileCardUpiHandle[] = [
79
+ { label: 'username@jio', iconName: 'ic_copy' },
80
+ { label: '9999999999', iconName: 'ic_confirm' },
81
+ ]
82
+
83
+ interface ProfileCardTokens {
84
+ containerStyle: ViewStyle
85
+ handlesRowStyle: ViewStyle
86
+ badgeBackground: string
87
+ childModes: Modes
88
+ badgeModes: Modes
89
+ }
90
+
91
+ function resolveProfileCardTokens(modes: Modes): ProfileCardTokens {
92
+ const backgroundColor = (getVariableByName('profileCard/bg', modes) ?? '#00065a') as string
93
+ const gap = (getVariableByName('profileCard/gap', modes) ?? 12) as number
94
+ const borderRadius = (getVariableByName('profileCard/radius', modes) ?? 12) as number
95
+ const paddingHorizontal = (getVariableByName('profileCard/padding/horizontal', modes) ?? 20) as number
96
+ const paddingVertical = (getVariableByName('profileCard/padding/vertical', modes) ?? 20) as number
97
+
98
+ const childModes: Modes = { ...modes, ...CARD_CONTEXT }
99
+
100
+ // The UPI handle pills sit in the header ListItem's support slot, whose row
101
+ // gap is the `slot/gap` token in the ListItem context.
102
+ const handlesGap = (getVariableByName('slot/gap', { ...childModes, Context: 'ListItem' }) ?? 2) as number
103
+
104
+ // The avatar badge is a white circle: the fill comes from the Attached
105
+ // slot's background token (the IconCapsule itself resolves transparent).
106
+ const badgeBackground = (getVariableByName('attached/slot/background', childModes) ?? '#ffffff') as string
107
+
108
+ return {
109
+ containerStyle: {
110
+ backgroundColor,
111
+ borderRadius,
112
+ paddingHorizontal,
113
+ paddingVertical,
114
+ gap,
115
+ flexDirection: 'column',
116
+ alignItems: 'stretch',
117
+ },
118
+ handlesRowStyle: {
119
+ flexDirection: 'row',
120
+ flexWrap: 'wrap',
121
+ alignItems: 'center',
122
+ alignSelf: 'flex-start',
123
+ gap: handlesGap,
124
+ },
125
+ badgeBackground,
126
+ childModes,
127
+ // XS capsule per the Figma spec; consumers can still override the size
128
+ // through their own modes.
129
+ badgeModes: { 'Icon Capsule Size': 'XS', ...modes, ...CARD_CONTEXT },
130
+ }
131
+ }
132
+
133
+ /**
134
+ * ProfileCard — Figma node 6914:38498 ("Profile Card").
135
+ *
136
+ * A dark, rounded profile summary card composed from the shared primitives:
137
+ *
138
+ * - **Header** — a {@link ListItem} whose leading slot is an {@link Attached}
139
+ * monogram {@link Avatar} with a small {@link IconCapsule} badge at its
140
+ * bottom-right, the user's name as the title, and a row of
141
+ * {@link UpiHandle} pills in the support slot.
142
+ * - **Divider** — separates the header from the body.
143
+ * - **Body slot** — arbitrary children (typically `ListItem`s separated by
144
+ * `Divider`s). All profile-card context modes cascade into the slot, so
145
+ * nested components automatically resolve their on-dark token values.
146
+ *
147
+ * The card supports the `Profile Card Appearance` collection
148
+ * (`Default` | `Premium`) via the `modes` prop, which recolors the container,
149
+ * avatar, pills and divider in one go.
150
+ *
151
+ * @example
152
+ * ```tsx
153
+ * <ProfileCard
154
+ * name="Shruti Chandarshekhar Rai"
155
+ * monogram="SR"
156
+ * upiHandles={[
157
+ * { label: 'shrutirai-1@jio', iconName: 'ic_copy', onPress: copyHandle },
158
+ * { label: '9184844184', iconName: 'ic_confirm' },
159
+ * ]}
160
+ * >
161
+ * <ListItem layout="Horizontal" title="JioFinance+" navArrow={false} />
162
+ * </ProfileCard>
163
+ * ```
164
+ */
165
+ function ProfileCard({
166
+ name = 'Name',
167
+ monogram = 'MS',
168
+ avatarSource,
169
+ badgeIcon = 'ic_qr_code',
170
+ upiHandles = DEFAULT_UPI_HANDLES,
171
+ header,
172
+ children,
173
+ showDivider = true,
174
+ width = 344,
175
+ modes: propModes = EMPTY_MODES,
176
+ style,
177
+ accessibilityLabel,
178
+ }: ProfileCardProps) {
179
+ const { modes: globalModes } = useTokens()
180
+
181
+ const modes = useMemo(
182
+ () => (globalModes === EMPTY_MODES && propModes === EMPTY_MODES
183
+ ? EMPTY_MODES
184
+ : { ...globalModes, ...propModes }),
185
+ [globalModes, propModes]
186
+ )
187
+
188
+ const tokens = useMemo(() => resolveProfileCardTokens(modes), [modes])
189
+
190
+ // ---- Header -------------------------------------------------------------
191
+ const headerNode = useMemo(() => {
192
+ if (header) return cloneChildrenWithModes(header, tokens.childModes)
193
+
194
+ const avatar = (
195
+ <Attached
196
+ position="bottom-right"
197
+ modes={tokens.childModes}
198
+ badge={
199
+ badgeIcon != null ? (
200
+ <IconCapsule
201
+ iconName={badgeIcon}
202
+ modes={tokens.badgeModes}
203
+ style={{ backgroundColor: tokens.badgeBackground }}
204
+ />
205
+ ) : null
206
+ }
207
+ >
208
+ <Avatar
209
+ monogram={monogram}
210
+ style={avatarSource ? 'Image' : 'Monogram'}
211
+ {...(avatarSource != null ? { imageSource: avatarSource } : {})}
212
+ modes={tokens.childModes}
213
+ />
214
+ </Attached>
215
+ )
216
+
217
+ const handles = upiHandles.length > 0 ? (
218
+ <View style={tokens.handlesRowStyle}>
219
+ {upiHandles.map((handle, index) => (
220
+ <UpiHandle
221
+ key={`${handle.label}-${index}`}
222
+ label={handle.label}
223
+ {...(handle.iconName != null ? { iconName: handle.iconName } : {})}
224
+ showIcon={handle.showIcon ?? true}
225
+ {...(handle.onPress != null ? { onPress: handle.onPress } : {})}
226
+ modes={tokens.childModes}
227
+ />
228
+ ))}
229
+ </View>
230
+ ) : null
231
+
232
+ return (
233
+ <ListItem
234
+ layout="Horizontal"
235
+ title={name}
236
+ navArrow={false}
237
+ showSupportText={handles != null}
238
+ supportSlot={handles}
239
+ leading={avatar}
240
+ modes={tokens.childModes}
241
+ />
242
+ )
243
+ }, [header, name, monogram, avatarSource, badgeIcon, upiHandles, tokens])
244
+
245
+ // ---- Body slot ----------------------------------------------------------
246
+ const bodyChildren = useMemo(
247
+ () => (children != null ? cloneChildrenWithModes(children, tokens.childModes) : null),
248
+ [children, tokens.childModes]
249
+ )
250
+ const hasBody = bodyChildren != null && bodyChildren.length > 0
251
+
252
+ const containerStyle = useMemo<ViewStyle>(
253
+ () => ({ ...tokens.containerStyle, width }),
254
+ [tokens.containerStyle, width]
255
+ )
256
+
257
+ return (
258
+ <View style={[containerStyle, style]} accessibilityLabel={accessibilityLabel}>
259
+ {headerNode}
260
+ {hasBody && showDivider ? (
261
+ <Divider direction="horizontal" modes={tokens.childModes} />
262
+ ) : null}
263
+ {bodyChildren}
264
+ </View>
265
+ )
266
+ }
267
+
268
+ export default React.memo(ProfileCard)
@@ -18,10 +18,6 @@ import MediaSource, { type UnifiedSource } from '../../utils/MediaSource'
18
18
  import Icon from '../../icons/Icon'
19
19
  import type { Modes } from '../../design-tokens'
20
20
 
21
- // Default static asset from the component folder.
22
- // Consumers can override the image via the `source` prop if needed.
23
- const DEFAULT_AVATAR_IMAGE = require('./Image.png')
24
-
25
21
  const IS_WEB = Platform.OS === 'web'
26
22
  const IS_IOS = Platform.OS === 'ios'
27
23
  const PRESS_DELAY = IS_IOS ? 130 : 0
@@ -39,6 +35,12 @@ type UpiHandleProps = {
39
35
  * inline SVG XML string, a `require()` asset, an SVG React component, or
40
36
  * an already-rendered element. See {@link UnifiedSource}. Avatars are
41
37
  * intentionally **not** tinted — the source renders as-is.
38
+ *
39
+ * The leading avatar is driven entirely by this prop: providing a `source`
40
+ * (or the deprecated `avatarSource`) force-sets the Figma "UPI Handle Image"
41
+ * collection to `True` and renders the avatar; omitting it force-sets the
42
+ * mode to `False` and hides it. This override wins over any `UPI Handle
43
+ * Image` value passed via `modes`.
42
44
  */
43
45
  source?: UnifiedSource;
44
46
  /**
@@ -134,7 +136,7 @@ function resolveUpiHandleTokens(modes: Modes): UpiHandleTokens {
134
136
  * @param {Object} [props.modes={}] - Modes object passed directly to `getVariableByName`.
135
137
  * @param {boolean} [props.showIcon=true] - Toggles the trailing icon visibility.
136
138
  * @param {string} [props.iconName='ic_scan_qr_code'] - Icon name from the actions set.
137
- * @param {UnifiedSource} [props.source] - Unified avatar source (URI, inline SVG XML, `require()` asset, SVG React component, or React element). Smart-detects raster vs SVG so the same prop works on iOS, Android and web.
139
+ * @param {UnifiedSource} [props.source] - Unified avatar source (URI, inline SVG XML, `require()` asset, SVG React component, or React element). Smart-detects raster vs SVG so the same prop works on iOS, Android and web. Providing a source force-sets `UPI Handle Image: True` (avatar shown); omitting it force-sets `False` (avatar hidden), overriding any `UPI Handle Image` value passed via `modes`.
138
140
  * @param {ImageSourcePropType|UnifiedSource} [props.avatarSource] - Deprecated alias for `source`; kept for back-compat.
139
141
  * @param {Function} [props.onClick] - Click/tap handler. Works as an alias for `onPress`.
140
142
  * @param {string} [props.accessibilityLabel] - Accessibility label for screen readers
@@ -168,12 +170,21 @@ function UpiHandle({
168
170
  }: UpiHandleProps) {
169
171
  const { modes: globalModes } = useTokens()
170
172
 
171
- const modes = useMemo(
172
- () => (globalModes === EMPTY_MODES && propModes === EMPTY_MODES
173
+ // The avatar is fully driven by the presence of a `source`. It force-sets the
174
+ // Figma "UPI Handle Image" collection to `True` when a source is provided and
175
+ // `False` otherwise. This is applied last so it overrides any consumer-passed
176
+ // `UPI Handle Image` mode, keeping the avatar visibility and the token layout
177
+ // (symmetric padding in the hidden-image state) in lockstep with the source.
178
+ const hasSource = source != null || avatarSource != null
179
+
180
+ const modes = useMemo(() => {
181
+ const base = globalModes === EMPTY_MODES && propModes === EMPTY_MODES
173
182
  ? EMPTY_MODES
174
- : { ...globalModes, ...propModes }),
175
- [globalModes, propModes]
176
- )
183
+ : { ...globalModes, ...propModes }
184
+ return { ...base, 'UPI Handle Image': hasSource ? 'True' : 'False' }
185
+ }, [globalModes, propModes, hasSource])
186
+
187
+ const showImageResolved = hasSource
177
188
 
178
189
  const tokens = useMemo(() => resolveUpiHandleTokens(modes), [modes])
179
190
 
@@ -226,25 +237,25 @@ function UpiHandle({
226
237
 
227
238
  // `source` wins; `avatarSource` is the legacy fallback. Both are accepted
228
239
  // as a UnifiedSource (string / number / {uri} / component / element), and
229
- // the legacy `ImageSourcePropType` shapes naturally fit that union too.
230
- const resolvedAvatarSource: UnifiedSource =
231
- (source as UnifiedSource | undefined) ??
232
- (avatarSource as UnifiedSource | undefined) ??
233
- (DEFAULT_AVATAR_IMAGE as UnifiedSource)
240
+ // the legacy `ImageSourcePropType` shapes naturally fit that union too. This
241
+ // is only read when `showImageResolved` is true, which requires a source.
242
+ const resolvedAvatarSource = (source ?? avatarSource) as UnifiedSource
234
243
 
235
244
  const avatarSize = (tokens.avatarStyle.width as number | undefined) ?? 23
236
245
 
237
246
  const innerContent = (
238
247
  <>
239
- <View style={tokens.avatarStyle}>
240
- <MediaSource
241
- source={resolvedAvatarSource}
242
- size={avatarSize}
243
- resizeMode="cover"
244
- accessibilityElementsHidden={true}
245
- importantForAccessibility="no"
246
- />
247
- </View>
248
+ {showImageResolved && (
249
+ <View style={tokens.avatarStyle}>
250
+ <MediaSource
251
+ source={resolvedAvatarSource}
252
+ size={avatarSize}
253
+ resizeMode="cover"
254
+ accessibilityElementsHidden={true}
255
+ importantForAccessibility="no"
256
+ />
257
+ </View>
258
+ )}
248
259
  <Text
249
260
  style={tokens.labelStyle}
250
261
  {...resolveTruncation(disableTruncation, 1, 'tail')}