jfs-components 0.1.25 → 0.1.30
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 +53 -0
- package/lib/commonjs/components/AutoplayControl/AutoplayControl.js +66 -0
- package/lib/commonjs/components/Carousel/Carousel.js +112 -19
- package/lib/commonjs/components/CompareTable/CompareTable.js +191 -25
- package/lib/commonjs/components/ContentSheet/ContentSheet.js +104 -10
- package/lib/commonjs/components/FormUpload/Additem.js +168 -0
- package/lib/commonjs/components/FormUpload/FormUpload.js +198 -0
- package/lib/commonjs/components/Grid/Grid.js +206 -0
- package/lib/commonjs/components/MoneyValue/MoneyValue.js +179 -54
- package/lib/commonjs/components/NumberPagination/NumberPagination.js +144 -0
- package/lib/commonjs/components/PdpCcCard/PdpCcCard.js +7 -1
- package/lib/commonjs/components/ProfileCard/ProfileCard.js +208 -0
- package/lib/commonjs/components/Table/Table.js +27 -7
- package/lib/commonjs/components/UpiHandle/UpiHandle.js +23 -11
- package/lib/commonjs/components/index.js +42 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/design-tokens/figma-modes.generated.js +21 -10
- package/lib/commonjs/icons/components/IconBrokenimage.js +19 -0
- package/lib/commonjs/icons/components/IconImage.js +19 -0
- package/lib/commonjs/icons/components/IconMic.js +19 -0
- package/lib/commonjs/icons/components/IconMicoff.js +19 -0
- package/lib/commonjs/icons/components/IconPause.js +19 -0
- package/lib/commonjs/icons/components/IconPlay.js +19 -0
- package/lib/commonjs/icons/components/index.js +66 -0
- package/lib/commonjs/icons/registry.js +2 -2
- package/lib/module/components/AutoplayControl/AutoplayControl.js +60 -0
- package/lib/module/components/Carousel/Carousel.js +111 -19
- package/lib/module/components/CompareTable/CompareTable.js +191 -26
- package/lib/module/components/ContentSheet/ContentSheet.js +108 -14
- package/lib/module/components/FormUpload/Additem.js +162 -0
- package/lib/module/components/FormUpload/FormUpload.js +192 -0
- package/lib/module/components/Grid/Grid.js +201 -0
- package/lib/module/components/MoneyValue/MoneyValue.js +182 -57
- package/lib/module/components/NumberPagination/NumberPagination.js +138 -0
- package/lib/module/components/PdpCcCard/PdpCcCard.js +7 -1
- package/lib/module/components/ProfileCard/ProfileCard.js +202 -0
- package/lib/module/components/Table/Table.js +27 -7
- package/lib/module/components/UpiHandle/UpiHandle.js +23 -11
- package/lib/module/components/index.js +7 -1
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/design-tokens/figma-modes.generated.js +21 -10
- package/lib/module/icons/components/IconBrokenimage.js +12 -0
- package/lib/module/icons/components/IconImage.js +12 -0
- package/lib/module/icons/components/IconMic.js +12 -0
- package/lib/module/icons/components/IconMicoff.js +12 -0
- package/lib/module/icons/components/IconPause.js +12 -0
- package/lib/module/icons/components/IconPlay.js +12 -0
- package/lib/module/icons/components/index.js +6 -0
- package/lib/module/icons/registry.js +2 -2
- package/lib/typescript/src/components/AutoplayControl/AutoplayControl.d.ts +15 -0
- package/lib/typescript/src/components/Carousel/Carousel.d.ts +8 -1
- package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +21 -1
- package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +20 -1
- package/lib/typescript/src/components/FormUpload/Additem.d.ts +96 -0
- package/lib/typescript/src/components/FormUpload/FormUpload.d.ts +85 -0
- package/lib/typescript/src/components/Grid/Grid.d.ts +87 -0
- package/lib/typescript/src/components/MoneyValue/MoneyValue.d.ts +10 -1
- package/lib/typescript/src/components/NumberPagination/NumberPagination.d.ts +21 -0
- package/lib/typescript/src/components/PdpCcCard/PdpCcCard.d.ts +25 -1
- package/lib/typescript/src/components/ProfileCard/ProfileCard.d.ts +89 -0
- package/lib/typescript/src/components/Table/Table.d.ts +9 -1
- package/lib/typescript/src/components/UpiHandle/UpiHandle.d.ts +7 -1
- package/lib/typescript/src/components/index.d.ts +34 -28
- package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +10 -4
- package/lib/typescript/src/icons/components/IconBrokenimage.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconImage.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconMic.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconMicoff.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconPause.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconPlay.d.ts +3 -0
- package/lib/typescript/src/icons/components/index.d.ts +6 -0
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/AutoplayControl/AutoplayControl.tsx +77 -0
- package/src/components/Carousel/Carousel.tsx +133 -23
- package/src/components/CompareTable/CompareTable.tsx +224 -30
- package/src/components/ContentSheet/ContentSheet.tsx +138 -8
- package/src/components/FormUpload/Additem.tsx +262 -0
- package/src/components/FormUpload/FormUpload.tsx +313 -0
- package/src/components/Grid/Grid.tsx +368 -0
- package/src/components/MoneyValue/MoneyValue.tsx +216 -65
- package/src/components/NumberPagination/NumberPagination.tsx +170 -0
- package/src/components/PdpCcCard/PdpCcCard.tsx +36 -1
- package/src/components/ProfileCard/ProfileCard.tsx +268 -0
- package/src/components/Table/Table.tsx +29 -4
- package/src/components/UpiHandle/UpiHandle.tsx +35 -24
- package/src/components/index.ts +329 -182
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/design-tokens/figma-modes.generated.ts +21 -10
- package/src/icons/components/IconBrokenimage.tsx +11 -0
- package/src/icons/components/IconImage.tsx +11 -0
- package/src/icons/components/IconMic.tsx +11 -0
- package/src/icons/components/IconMicoff.tsx +11 -0
- package/src/icons/components/IconPause.tsx +11 -0
- package/src/icons/components/IconPlay.tsx +11 -0
- package/src/icons/components/index.ts +6 -0
- 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)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { createContext, useContext } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
Text,
|
|
@@ -13,6 +13,16 @@ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
|
13
13
|
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
14
14
|
import type { Modes } from '../../design-tokens'
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Carries the root `Table`'s `columnWidth` down to every cell so that, when it
|
|
18
|
+
* is set, all columns (header + body, data-driven or composed) share the same
|
|
19
|
+
* fixed width without each cell having to repeat the prop. Cells still honour
|
|
20
|
+
* an explicit `width` prop over this context value. `undefined` (the default,
|
|
21
|
+
* when no `Table` ancestor is present, or `columnWidth` is unset) leaves cells
|
|
22
|
+
* to flex — preserving the original behaviour.
|
|
23
|
+
*/
|
|
24
|
+
const TableContext = createContext<number | undefined>(undefined)
|
|
25
|
+
|
|
16
26
|
/* -------------------------------------------------------------------------------------------------
|
|
17
27
|
* Shared types & token resolution
|
|
18
28
|
* -----------------------------------------------------------------------------------------------*/
|
|
@@ -135,6 +145,10 @@ function TableCell({
|
|
|
135
145
|
style,
|
|
136
146
|
}: TableCellProps) {
|
|
137
147
|
const t = resolveTableTokens(modes)
|
|
148
|
+
// A cell's width resolves in priority order: an explicit `width` prop, then
|
|
149
|
+
// the root Table's `columnWidth` (via context), then fall back to flex.
|
|
150
|
+
const ctxColumnWidth = useContext(TableContext)
|
|
151
|
+
const effectiveWidth = width ?? ctxColumnWidth
|
|
138
152
|
return (
|
|
139
153
|
<View
|
|
140
154
|
style={[
|
|
@@ -148,7 +162,7 @@ function TableCell({
|
|
|
148
162
|
borderColor: t.borderColor,
|
|
149
163
|
borderBottomWidth: isLastRow ? 0 : t.borderSize,
|
|
150
164
|
borderRightWidth: isLastColumn ? 0 : t.borderSize,
|
|
151
|
-
...(
|
|
165
|
+
...(effectiveWidth != null ? { width: effectiveWidth } : { flex, minWidth: 0 }),
|
|
152
166
|
},
|
|
153
167
|
style,
|
|
154
168
|
]}
|
|
@@ -188,6 +202,8 @@ function TableHeaderCell({
|
|
|
188
202
|
style,
|
|
189
203
|
}: TableHeaderCellProps) {
|
|
190
204
|
const t = resolveTableTokens(modes)
|
|
205
|
+
const ctxColumnWidth = useContext(TableContext)
|
|
206
|
+
const effectiveWidth = width ?? ctxColumnWidth
|
|
191
207
|
return (
|
|
192
208
|
<View
|
|
193
209
|
style={[
|
|
@@ -198,7 +214,7 @@ function TableHeaderCell({
|
|
|
198
214
|
justifyContent: alignToJustify(align),
|
|
199
215
|
paddingHorizontal: t.headerPaddingH,
|
|
200
216
|
paddingVertical: t.headerPaddingV,
|
|
201
|
-
...(
|
|
217
|
+
...(effectiveWidth != null ? { width: effectiveWidth } : { flex, minWidth: 0 }),
|
|
202
218
|
},
|
|
203
219
|
style,
|
|
204
220
|
]}
|
|
@@ -327,6 +343,14 @@ export type TableProps<T = Record<string, unknown>> = {
|
|
|
327
343
|
* for wide tables that exceed the viewport on mobile. @default false
|
|
328
344
|
*/
|
|
329
345
|
scrollable?: boolean
|
|
346
|
+
/**
|
|
347
|
+
* Fixed width (px) applied to every column — header and body alike — so
|
|
348
|
+
* all columns share the same width. An explicit per-column `width` (on
|
|
349
|
+
* `TableColumn`, or on an individual `Table.Cell`/`Table.HeaderCell`) still
|
|
350
|
+
* takes precedence when set. Omit to let columns flex to share the
|
|
351
|
+
* available space.
|
|
352
|
+
*/
|
|
353
|
+
columnWidth?: number
|
|
330
354
|
/** Accessibility label for the table container. */
|
|
331
355
|
accessibilityLabel?: string
|
|
332
356
|
/** Design token modes for theming (e.g. `{ "Color Mode": "Light" }`). */
|
|
@@ -360,6 +384,7 @@ function Table<T = Record<string, unknown>>({
|
|
|
360
384
|
getRowKey,
|
|
361
385
|
onRowPress,
|
|
362
386
|
scrollable = false,
|
|
387
|
+
columnWidth,
|
|
363
388
|
accessibilityLabel,
|
|
364
389
|
modes = EMPTY_MODES,
|
|
365
390
|
style,
|
|
@@ -424,7 +449,7 @@ function Table<T = Record<string, unknown>>({
|
|
|
424
449
|
accessibilityLabel={accessibilityLabel}
|
|
425
450
|
style={[{ overflow: 'hidden' }, !scrollable && { width: '100%' }, style]}
|
|
426
451
|
>
|
|
427
|
-
{body}
|
|
452
|
+
<TableContext.Provider value={columnWidth}>{body}</TableContext.Provider>
|
|
428
453
|
</View>
|
|
429
454
|
)
|
|
430
455
|
|
|
@@ -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
|
-
|
|
172
|
-
|
|
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
|
-
|
|
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
|
-
|
|
231
|
-
|
|
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
|
-
|
|
240
|
-
<
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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')}
|