jfs-components 0.1.23 → 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.
- package/CHANGELOG.md +40 -0
- package/lib/commonjs/components/AutoplayControl/AutoplayControl.js +66 -0
- package/lib/commonjs/components/CardCTA/CardCTA.js +32 -25
- package/lib/commonjs/components/Carousel/Carousel.js +112 -19
- package/lib/commonjs/components/ChipSelect/ChipSelect.js +2 -1
- package/lib/commonjs/components/ContentSheet/ContentSheet.js +39 -5
- 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/ListItem/ListItem.js +11 -4
- package/lib/commonjs/components/MetricData/MetricData.js +132 -0
- package/lib/commonjs/components/NumberPagination/NumberPagination.js +144 -0
- package/lib/commonjs/components/ProfileCard/ProfileCard.js +208 -0
- package/lib/commonjs/components/Rating/Rating.js +137 -0
- 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 +23 -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/CardCTA/CardCTA.js +32 -25
- package/lib/module/components/Carousel/Carousel.js +111 -19
- package/lib/module/components/ChipSelect/ChipSelect.js +2 -1
- package/lib/module/components/ContentSheet/ContentSheet.js +41 -7
- 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/ListItem/ListItem.js +11 -4
- package/lib/module/components/MetricData/MetricData.js +127 -0
- package/lib/module/components/NumberPagination/NumberPagination.js +138 -0
- package/lib/module/components/ProfileCard/ProfileCard.js +202 -0
- package/lib/module/components/Rating/Rating.js +132 -0
- 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 +23 -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/ChipSelect/ChipSelect.d.ts +6 -1
- package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +7 -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/MetricData/MetricData.d.ts +53 -0
- package/lib/typescript/src/components/NumberPagination/NumberPagination.d.ts +21 -0
- package/lib/typescript/src/components/ProfileCard/ProfileCard.d.ts +89 -0
- package/lib/typescript/src/components/Rating/Rating.d.ts +30 -0
- 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/CardCTA/CardCTA.tsx +30 -15
- package/src/components/Carousel/Carousel.tsx +133 -23
- package/src/components/ChipSelect/ChipSelect.tsx +7 -1
- package/src/components/ContentSheet/ContentSheet.tsx +50 -0
- 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/ListItem/ListItem.tsx +11 -4
- package/src/components/MetricData/MetricData.tsx +185 -0
- package/src/components/NumberPagination/NumberPagination.tsx +170 -0
- package/src/components/ProfileCard/ProfileCard.tsx +268 -0
- package/src/components/Rating/Rating.tsx +174 -0
- 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 +23 -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,185 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
type StyleProp,
|
|
6
|
+
type ViewStyle,
|
|
7
|
+
type TextStyle,
|
|
8
|
+
} from 'react-native'
|
|
9
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
10
|
+
import { EMPTY_MODES } from '../../utils/react-utils'
|
|
11
|
+
import Icon from '../Icon/Icon'
|
|
12
|
+
import type { Modes } from '../../design-tokens'
|
|
13
|
+
|
|
14
|
+
const LINE_HEIGHT_RATIO = 1.2
|
|
15
|
+
const ICON_SIZE = 18
|
|
16
|
+
// Figma renders the value-row icon with no surrounding padding; the shared Icon
|
|
17
|
+
// component pulls padding from `icon/padding/*` tokens, so we zero it here.
|
|
18
|
+
const ICON_PADDING_RESET: ViewStyle = {
|
|
19
|
+
paddingLeft: 0,
|
|
20
|
+
paddingRight: 0,
|
|
21
|
+
paddingTop: 0,
|
|
22
|
+
paddingBottom: 0,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type MetricDataProps = {
|
|
26
|
+
/** Top label (small, 12px). Hidden when omitted. */
|
|
27
|
+
title?: string
|
|
28
|
+
/**
|
|
29
|
+
* The prominent value (20px bold). Pass a string, or any node (e.g. a
|
|
30
|
+
* `MoneyValue`) to compose a richer value.
|
|
31
|
+
*/
|
|
32
|
+
value?: string | React.ReactNode
|
|
33
|
+
/** Sub-label below the value (12px, muted). Hidden when omitted. */
|
|
34
|
+
caption?: string
|
|
35
|
+
/**
|
|
36
|
+
* Optional glyph shown to the left of the value. Pass a registry icon name
|
|
37
|
+
* (e.g. `'ic_card'`) or a custom node. Rendered at 18px, tinted to the value
|
|
38
|
+
* colour. Hidden when omitted.
|
|
39
|
+
*/
|
|
40
|
+
icon?: string | React.ReactNode
|
|
41
|
+
/** Design-token modes for theming. */
|
|
42
|
+
modes?: Modes
|
|
43
|
+
/** Override the container styles. */
|
|
44
|
+
style?: StyleProp<ViewStyle>
|
|
45
|
+
/** Override the title text styles. */
|
|
46
|
+
titleStyle?: StyleProp<TextStyle>
|
|
47
|
+
/** Override the value text styles. */
|
|
48
|
+
valueStyle?: StyleProp<TextStyle>
|
|
49
|
+
/** Override the caption text styles. */
|
|
50
|
+
captionStyle?: StyleProp<TextStyle>
|
|
51
|
+
/**
|
|
52
|
+
* Accessibility label. Defaults to the resolved `title`, `value` and
|
|
53
|
+
* `caption` text joined together.
|
|
54
|
+
*/
|
|
55
|
+
accessibilityLabel?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* MetricData — a compact, centered metric block.
|
|
60
|
+
*
|
|
61
|
+
* Stacks a small `title`, a prominent `value` (with an optional leading
|
|
62
|
+
* `icon`), and a muted `caption`. Typography, colours, gaps and padding all
|
|
63
|
+
* resolve from the `metricdata/*` design tokens. `title`, `caption` and `icon`
|
|
64
|
+
* are optional; only `value` is shown by default.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```tsx
|
|
68
|
+
* <MetricData title="Balance" value="₹1,20,000" caption="as of today" />
|
|
69
|
+
* <MetricData title="Cards" value="12" icon="ic_card" caption="active" />
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
function MetricData({
|
|
73
|
+
title,
|
|
74
|
+
value,
|
|
75
|
+
caption,
|
|
76
|
+
icon,
|
|
77
|
+
modes = EMPTY_MODES,
|
|
78
|
+
style,
|
|
79
|
+
titleStyle,
|
|
80
|
+
valueStyle,
|
|
81
|
+
captionStyle,
|
|
82
|
+
accessibilityLabel,
|
|
83
|
+
}: MetricDataProps) {
|
|
84
|
+
const titleColor = (getVariableByName('metricdata/title/color', modes) as string | null) ?? '#000000'
|
|
85
|
+
const titleFontFamily = (getVariableByName('metricdata/title/fontfamily', modes) as string | null) ?? 'JioType Var'
|
|
86
|
+
const titleFontSize = (getVariableByName('metricdata/title/fontsize', modes) as number | null) ?? 12
|
|
87
|
+
const titleFontWeight = String(getVariableByName('metricdata/title/fontweight', modes) ?? 400) as TextStyle['fontWeight']
|
|
88
|
+
|
|
89
|
+
const valueColor = (getVariableByName('metricdata/value/color', modes) as string | null) ?? '#000000'
|
|
90
|
+
const valueFontFamily = (getVariableByName('metricdata/value/fontfamily', modes) as string | null) ?? 'JioType Var'
|
|
91
|
+
const valueFontSize = (getVariableByName('metricdata/value/fontsize', modes) as number | null) ?? 20
|
|
92
|
+
const valueFontWeight = String(getVariableByName('metricdata/value/fontweight', modes) ?? 700) as TextStyle['fontWeight']
|
|
93
|
+
|
|
94
|
+
const captionColor = (getVariableByName('metricdata/caption/color', modes) as string | null) ?? '#777777'
|
|
95
|
+
const captionFontFamily = (getVariableByName('metricdata/caption/fontfamily', modes) as string | null) ?? 'JioType Var'
|
|
96
|
+
const captionFontSize = (getVariableByName('metricdata/caption/fontsize', modes) as number | null) ?? 12
|
|
97
|
+
const captionFontWeight = String(getVariableByName('metricdata/caption/fontweight', modes) ?? 500) as TextStyle['fontWeight']
|
|
98
|
+
|
|
99
|
+
const gap = (getVariableByName('metricdata/gap', modes) as number | null) ?? 4
|
|
100
|
+
const valueWrapGap = (getVariableByName('metricdata/valueWrap/gap', modes) as number | null) ?? 4
|
|
101
|
+
const paddingHorizontal = (getVariableByName('metricdata/padding/horizontal', modes) as number | null) ?? 10
|
|
102
|
+
const paddingVertical = (getVariableByName('metricdata/padding/vertical', modes) as number | null) ?? 10
|
|
103
|
+
|
|
104
|
+
const containerStyle: ViewStyle = {
|
|
105
|
+
flexDirection: 'column',
|
|
106
|
+
alignItems: 'center',
|
|
107
|
+
justifyContent: 'center',
|
|
108
|
+
gap,
|
|
109
|
+
paddingHorizontal,
|
|
110
|
+
paddingVertical,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const titleTextStyle: TextStyle = {
|
|
114
|
+
color: titleColor,
|
|
115
|
+
fontFamily: titleFontFamily,
|
|
116
|
+
fontSize: titleFontSize,
|
|
117
|
+
fontWeight: titleFontWeight,
|
|
118
|
+
lineHeight: titleFontSize * LINE_HEIGHT_RATIO,
|
|
119
|
+
textAlign: 'center',
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const valueTextStyle: TextStyle = {
|
|
123
|
+
color: valueColor,
|
|
124
|
+
fontFamily: valueFontFamily,
|
|
125
|
+
fontSize: valueFontSize,
|
|
126
|
+
fontWeight: valueFontWeight,
|
|
127
|
+
lineHeight: valueFontSize * LINE_HEIGHT_RATIO,
|
|
128
|
+
textAlign: 'center',
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const captionTextStyle: TextStyle = {
|
|
132
|
+
color: captionColor,
|
|
133
|
+
fontFamily: captionFontFamily,
|
|
134
|
+
fontSize: captionFontSize,
|
|
135
|
+
fontWeight: captionFontWeight,
|
|
136
|
+
lineHeight: captionFontSize * LINE_HEIGHT_RATIO,
|
|
137
|
+
textAlign: 'center',
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const iconNode =
|
|
141
|
+
icon == null ? null : typeof icon === 'string' ? (
|
|
142
|
+
<Icon
|
|
143
|
+
iconName={icon}
|
|
144
|
+
size={ICON_SIZE}
|
|
145
|
+
color={valueColor}
|
|
146
|
+
modes={modes}
|
|
147
|
+
style={ICON_PADDING_RESET}
|
|
148
|
+
/>
|
|
149
|
+
) : (
|
|
150
|
+
icon
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
const resolvedLabel =
|
|
154
|
+
accessibilityLabel ??
|
|
155
|
+
([title, typeof value === 'string' ? value : undefined, caption]
|
|
156
|
+
.filter(Boolean)
|
|
157
|
+
.join(', ') || undefined)
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<View
|
|
161
|
+
style={[containerStyle, style]}
|
|
162
|
+
accessible
|
|
163
|
+
{...(resolvedLabel !== undefined ? { accessibilityLabel: resolvedLabel } : null)}
|
|
164
|
+
>
|
|
165
|
+
{title !== undefined && title !== '' ? (
|
|
166
|
+
<Text style={[titleTextStyle, titleStyle]}>{title}</Text>
|
|
167
|
+
) : null}
|
|
168
|
+
|
|
169
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: valueWrapGap }}>
|
|
170
|
+
{iconNode}
|
|
171
|
+
{typeof value === 'string' ? (
|
|
172
|
+
<Text style={[valueTextStyle, valueStyle]}>{value}</Text>
|
|
173
|
+
) : (
|
|
174
|
+
value
|
|
175
|
+
)}
|
|
176
|
+
</View>
|
|
177
|
+
|
|
178
|
+
{caption !== undefined && caption !== '' ? (
|
|
179
|
+
<Text style={[captionTextStyle, captionStyle]}>{caption}</Text>
|
|
180
|
+
) : null}
|
|
181
|
+
</View>
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export default React.memo(MetricData)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Pressable,
|
|
4
|
+
Text,
|
|
5
|
+
View,
|
|
6
|
+
type StyleProp,
|
|
7
|
+
type TextStyle,
|
|
8
|
+
type ViewStyle,
|
|
9
|
+
} from 'react-native'
|
|
10
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
11
|
+
import GlassFill from '../../utils/GlassFill/GlassFill'
|
|
12
|
+
import type { Modes } from '../../design-tokens'
|
|
13
|
+
|
|
14
|
+
export type NumberPaginationProps = {
|
|
15
|
+
/** The 1-based active page index. Ignored if `children` is supplied. Defaults to 1. */
|
|
16
|
+
activePage?: number;
|
|
17
|
+
/** The total number of pages. Ignored if `children` is supplied. Defaults to 1. */
|
|
18
|
+
totalPages?: number;
|
|
19
|
+
/** Callback fired when a page button is pressed. Ignored if `children` is supplied. */
|
|
20
|
+
onPageChange?: (page: number) => void;
|
|
21
|
+
/** Custom children to override the default page buttons. */
|
|
22
|
+
children?: React.ReactNode;
|
|
23
|
+
modes?: Modes;
|
|
24
|
+
style?: StyleProp<ViewStyle>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface NumberPaginationTokens {
|
|
28
|
+
containerStyle: ViewStyle;
|
|
29
|
+
activeItemStyle: ViewStyle;
|
|
30
|
+
inactiveItemStyle: ViewStyle;
|
|
31
|
+
activeTextStyle: TextStyle;
|
|
32
|
+
inactiveTextStyle: TextStyle;
|
|
33
|
+
blurIntensity: number;
|
|
34
|
+
backgroundColor: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveNumberPaginationTokens(modes: Modes): NumberPaginationTokens {
|
|
38
|
+
const horizontalPadding = (getVariableByName('numberPagination/padding/horizontal', modes) ?? 4) as number
|
|
39
|
+
const verticalPadding = (getVariableByName('numberPagination/padding/vertical', modes) ?? 4) as number
|
|
40
|
+
const radiusRaw = (getVariableByName('numberPagination/radius', modes) ?? 9999) as number
|
|
41
|
+
const backgroundColor = (getVariableByName('numberPagination/background/color', modes) ?? 'rgba(141, 141, 141, 0.4)') as string
|
|
42
|
+
const borderColor = (getVariableByName('numberPagination/border/color', modes) ?? 'rgba(255, 255, 255, 0.15)') as string
|
|
43
|
+
|
|
44
|
+
// control number variables
|
|
45
|
+
const itemWidth = (getVariableByName('controlNumber/width', modes) ?? 32) as number
|
|
46
|
+
const itemHeight = (getVariableByName('controlNumber/height', modes) ?? 32) as number
|
|
47
|
+
const itemRadiusRaw = (getVariableByName('controlNumber/radius', modes) ?? 9999) as number
|
|
48
|
+
const activeBgColor = (getVariableByName('control/active/background/color', modes) ?? '#ffffff') as string
|
|
49
|
+
const activeTextColor = (getVariableByName('control/active/text/color', modes) ?? '#000000') as string
|
|
50
|
+
const inactiveTextColor = (getVariableByName('control/text/default', modes) ?? '#ffffff') as string
|
|
51
|
+
|
|
52
|
+
// typography variables
|
|
53
|
+
const fontFamily = (getVariableByName('numberPagination/fontFamily', modes) ?? 'JioType') as string
|
|
54
|
+
const fontSize = (getVariableByName('numberPagination/fontSize', modes) ?? 16) as number
|
|
55
|
+
const fontWeight = String(getVariableByName('numberPagination/fontWeight', modes) ?? '500') as TextStyle['fontWeight']
|
|
56
|
+
const lineHeight = (getVariableByName('numberPagination/lineHeight', modes) ?? 18) as number
|
|
57
|
+
|
|
58
|
+
const blurMinimal = (getVariableByName('blur/minimal', modes) ?? 29) as number
|
|
59
|
+
const blurIntensity = Math.max(0, Math.min(100, Math.round(Number(blurMinimal))))
|
|
60
|
+
|
|
61
|
+
const borderRadius = radiusRaw === 9999 ? 39 / 2 : radiusRaw
|
|
62
|
+
const itemRadius = itemRadiusRaw === 9999 ? itemWidth / 2 : itemRadiusRaw
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
containerStyle: {
|
|
66
|
+
height: 39,
|
|
67
|
+
borderRadius,
|
|
68
|
+
borderWidth: 1,
|
|
69
|
+
borderColor,
|
|
70
|
+
backgroundColor: 'transparent',
|
|
71
|
+
overflow: 'hidden',
|
|
72
|
+
flexDirection: 'row',
|
|
73
|
+
alignItems: 'center',
|
|
74
|
+
paddingHorizontal: horizontalPadding,
|
|
75
|
+
paddingVertical: verticalPadding,
|
|
76
|
+
},
|
|
77
|
+
activeItemStyle: {
|
|
78
|
+
width: itemWidth,
|
|
79
|
+
height: itemHeight,
|
|
80
|
+
borderRadius: itemRadius,
|
|
81
|
+
backgroundColor: activeBgColor,
|
|
82
|
+
alignItems: 'center',
|
|
83
|
+
justifyContent: 'center',
|
|
84
|
+
},
|
|
85
|
+
inactiveItemStyle: {
|
|
86
|
+
width: itemWidth,
|
|
87
|
+
height: itemHeight,
|
|
88
|
+
borderRadius: itemRadius,
|
|
89
|
+
backgroundColor: 'transparent',
|
|
90
|
+
alignItems: 'center',
|
|
91
|
+
justifyContent: 'center',
|
|
92
|
+
},
|
|
93
|
+
activeTextStyle: {
|
|
94
|
+
fontFamily,
|
|
95
|
+
fontSize,
|
|
96
|
+
fontWeight,
|
|
97
|
+
lineHeight,
|
|
98
|
+
color: activeTextColor,
|
|
99
|
+
textAlign: 'center',
|
|
100
|
+
},
|
|
101
|
+
inactiveTextStyle: {
|
|
102
|
+
fontFamily,
|
|
103
|
+
fontSize,
|
|
104
|
+
fontWeight,
|
|
105
|
+
lineHeight,
|
|
106
|
+
color: inactiveTextColor,
|
|
107
|
+
textAlign: 'center',
|
|
108
|
+
},
|
|
109
|
+
blurIntensity,
|
|
110
|
+
backgroundColor,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* NumberPagination component displays a horizontal list of page numbers on a frosted glass surface.
|
|
116
|
+
* All styling is driven by Figma design tokens with support for custom children slot.
|
|
117
|
+
*/
|
|
118
|
+
export default function NumberPagination({
|
|
119
|
+
activePage = 1,
|
|
120
|
+
totalPages = 1,
|
|
121
|
+
onPageChange,
|
|
122
|
+
children,
|
|
123
|
+
modes,
|
|
124
|
+
style,
|
|
125
|
+
}: NumberPaginationProps) {
|
|
126
|
+
const tokens = useMemo(() => resolveNumberPaginationTokens(modes ?? {}), [modes])
|
|
127
|
+
|
|
128
|
+
const renderPages = () => {
|
|
129
|
+
if (children) {
|
|
130
|
+
return children
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const pages = []
|
|
134
|
+
for (let i = 1; i <= totalPages; i++) {
|
|
135
|
+
const active = i === activePage
|
|
136
|
+
pages.push(
|
|
137
|
+
<Pressable
|
|
138
|
+
key={i}
|
|
139
|
+
onPress={() => onPageChange?.(i)}
|
|
140
|
+
style={({ pressed }) => [
|
|
141
|
+
active ? tokens.activeItemStyle : tokens.inactiveItemStyle,
|
|
142
|
+
pressed && { opacity: 0.7 },
|
|
143
|
+
]}
|
|
144
|
+
accessibilityRole="button"
|
|
145
|
+
accessibilityState={{ selected: active }}
|
|
146
|
+
accessibilityLabel={`Page ${i}`}
|
|
147
|
+
>
|
|
148
|
+
<Text style={active ? tokens.activeTextStyle : tokens.inactiveTextStyle}>
|
|
149
|
+
{i}
|
|
150
|
+
</Text>
|
|
151
|
+
</Pressable>
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
return pages
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<View style={[tokens.containerStyle, style]}>
|
|
159
|
+
<GlassFill
|
|
160
|
+
tint="light"
|
|
161
|
+
intensity={tokens.blurIntensity}
|
|
162
|
+
overlayColor={tokens.backgroundColor}
|
|
163
|
+
androidTintWash={false}
|
|
164
|
+
/>
|
|
165
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', height: '100%' }}>
|
|
166
|
+
{renderPages()}
|
|
167
|
+
</View>
|
|
168
|
+
</View>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
@@ -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)
|