jfs-components 0.1.18 → 0.1.23
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 +19 -0
- package/lib/commonjs/components/Card/Card.js +29 -12
- package/lib/commonjs/components/CardFeedback/CardFeedback.js +16 -2
- package/lib/commonjs/components/CardInsight/CardInsight.js +34 -13
- package/lib/commonjs/components/CardProviderInfo/CardProviderInfo.js +23 -3
- package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +31 -8
- package/lib/commonjs/components/ContentSheet/ContentSheet.js +131 -0
- package/lib/commonjs/components/DebitCard/DebitCard.js +23 -3
- package/lib/commonjs/components/HeroSection/HeroSection.js +165 -0
- package/lib/commonjs/components/Image/Image.js +33 -7
- package/lib/commonjs/components/Link/Link.js +115 -0
- package/lib/commonjs/components/ListItem/ListItem.js +16 -1
- package/lib/commonjs/components/MediaCard/MediaCard.js +24 -7
- package/lib/commonjs/components/RechargeCard/RechargeCard.js +34 -13
- package/lib/commonjs/components/TestimonialsCard/TestimonialsCard.js +23 -5
- package/lib/commonjs/components/index.js +21 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/design-tokens/figma-modes.generated.js +53 -43
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/Card/Card.js +31 -14
- package/lib/module/components/CardFeedback/CardFeedback.js +17 -3
- package/lib/module/components/CardInsight/CardInsight.js +36 -15
- package/lib/module/components/CardProviderInfo/CardProviderInfo.js +25 -5
- package/lib/module/components/CheckboxItem/CheckboxItem.js +31 -8
- package/lib/module/components/ContentSheet/ContentSheet.js +126 -0
- package/lib/module/components/DebitCard/DebitCard.js +25 -5
- package/lib/module/components/HeroSection/HeroSection.js +159 -0
- package/lib/module/components/Image/Image.js +34 -7
- package/lib/module/components/Link/Link.js +110 -0
- package/lib/module/components/ListItem/ListItem.js +16 -1
- package/lib/module/components/MediaCard/MediaCard.js +26 -9
- package/lib/module/components/RechargeCard/RechargeCard.js +36 -15
- package/lib/module/components/TestimonialsCard/TestimonialsCard.js +25 -7
- package/lib/module/components/index.js +3 -0
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/design-tokens/figma-modes.generated.js +53 -43
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/Card/Card.d.ts +10 -1
- package/lib/typescript/src/components/CardFeedback/CardFeedback.d.ts +10 -1
- package/lib/typescript/src/components/CardInsight/CardInsight.d.ts +10 -1
- package/lib/typescript/src/components/CardProviderInfo/CardProviderInfo.d.ts +10 -1
- package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +26 -3
- package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +71 -0
- package/lib/typescript/src/components/DebitCard/DebitCard.d.ts +10 -1
- package/lib/typescript/src/components/HeroSection/HeroSection.d.ts +80 -0
- package/lib/typescript/src/components/Image/Image.d.ts +27 -2
- package/lib/typescript/src/components/Link/Link.d.ts +73 -0
- package/lib/typescript/src/components/MediaCard/MediaCard.d.ts +10 -1
- package/lib/typescript/src/components/RechargeCard/RechargeCard.d.ts +10 -1
- package/lib/typescript/src/components/TestimonialsCard/TestimonialsCard.d.ts +8 -1
- package/lib/typescript/src/components/index.d.ts +3 -0
- package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +8 -0
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/Card/Card.tsx +46 -15
- package/src/components/CardFeedback/CardFeedback.tsx +29 -4
- package/src/components/CardInsight/CardInsight.tsx +48 -17
- package/src/components/CardProviderInfo/CardProviderInfo.tsx +36 -3
- package/src/components/CheckboxItem/CheckboxItem.tsx +59 -14
- package/src/components/ContentSheet/ContentSheet.tsx +217 -0
- package/src/components/DebitCard/DebitCard.tsx +36 -3
- package/src/components/HeroSection/HeroSection.tsx +231 -0
- package/src/components/Image/Image.tsx +55 -3
- package/src/components/Link/Link.tsx +159 -0
- package/src/components/ListItem/ListItem.tsx +15 -0
- package/src/components/MediaCard/MediaCard.tsx +40 -9
- package/src/components/RechargeCard/RechargeCard.tsx +48 -13
- package/src/components/TestimonialsCard/TestimonialsCard.tsx +37 -6
- package/src/components/index.ts +3 -0
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/design-tokens/figma-modes.generated.ts +53 -43
- package/src/icons/registry.ts +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import React, { createContext, useContext, isValidElement, cloneElement } from 'react';
|
|
4
|
-
import { View, Text } from 'react-native';
|
|
4
|
+
import { View, Text, Pressable } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
6
|
import { EMPTY_MODES } from '../../utils/react-utils';
|
|
7
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
8
|
/**
|
|
9
9
|
* Context to share 'modes' with child components like Card.Title and Card.SupportText.
|
|
10
10
|
* This ensures that nested components can resolve their tokens correctly without
|
|
@@ -32,7 +32,10 @@ export function Card({
|
|
|
32
32
|
children,
|
|
33
33
|
modes = EMPTY_MODES,
|
|
34
34
|
mediaAspectRatio = 154 / 116,
|
|
35
|
-
style
|
|
35
|
+
style,
|
|
36
|
+
onPress,
|
|
37
|
+
disabled,
|
|
38
|
+
accessibilityLabel
|
|
36
39
|
}) {
|
|
37
40
|
// Resolve Card Container Tokens
|
|
38
41
|
const backgroundColor = getVariableByName('card/background/color', modes);
|
|
@@ -96,22 +99,36 @@ export function Card({
|
|
|
96
99
|
paddingHorizontal: contentPaddingHorizontal,
|
|
97
100
|
paddingVertical: contentPaddingVertical
|
|
98
101
|
};
|
|
102
|
+
const content = /*#__PURE__*/_jsxs(_Fragment, {
|
|
103
|
+
children: [header && /*#__PURE__*/_jsx(View, {
|
|
104
|
+
style: headerWrapperStyle,
|
|
105
|
+
children: headerWithModes
|
|
106
|
+
}), media && /*#__PURE__*/_jsx(View, {
|
|
107
|
+
style: mediaWrapperStyle,
|
|
108
|
+
children: mediaWithModes
|
|
109
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
110
|
+
style: contentWrapperStyle,
|
|
111
|
+
children: children
|
|
112
|
+
})]
|
|
113
|
+
});
|
|
99
114
|
return /*#__PURE__*/_jsx(CardContext.Provider, {
|
|
100
115
|
value: {
|
|
101
116
|
modes
|
|
102
117
|
},
|
|
103
|
-
children: /*#__PURE__*/
|
|
118
|
+
children: onPress ? /*#__PURE__*/_jsx(Pressable, {
|
|
119
|
+
style: [containerStyle, style],
|
|
120
|
+
onPress: onPress,
|
|
121
|
+
disabled: disabled,
|
|
122
|
+
accessibilityRole: "button",
|
|
123
|
+
accessibilityLabel: accessibilityLabel,
|
|
124
|
+
accessibilityState: {
|
|
125
|
+
disabled: !!disabled
|
|
126
|
+
},
|
|
127
|
+
children: content
|
|
128
|
+
}) : /*#__PURE__*/_jsx(View, {
|
|
104
129
|
style: [containerStyle, style],
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
children: headerWithModes
|
|
108
|
-
}), media && /*#__PURE__*/_jsx(View, {
|
|
109
|
-
style: mediaWrapperStyle,
|
|
110
|
-
children: mediaWithModes
|
|
111
|
-
}), /*#__PURE__*/_jsx(View, {
|
|
112
|
-
style: contentWrapperStyle,
|
|
113
|
-
children: children
|
|
114
|
-
})]
|
|
130
|
+
accessibilityLabel: accessibilityLabel,
|
|
131
|
+
children: content
|
|
115
132
|
})
|
|
116
133
|
});
|
|
117
134
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import React, { createContext, useContext, isValidElement, cloneElement } from 'react';
|
|
4
|
-
import { View, Text } from 'react-native';
|
|
4
|
+
import { View, Text, Pressable } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
6
|
import IconComponent from '../../icons/Icon';
|
|
7
7
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
@@ -17,7 +17,10 @@ const CardFeedbackContext = /*#__PURE__*/createContext({});
|
|
|
17
17
|
export function CardFeedback({
|
|
18
18
|
children,
|
|
19
19
|
modes: propModes,
|
|
20
|
-
style
|
|
20
|
+
style,
|
|
21
|
+
onPress,
|
|
22
|
+
disabled,
|
|
23
|
+
accessibilityLabel
|
|
21
24
|
}) {
|
|
22
25
|
const modes = {
|
|
23
26
|
'Appearance.System': 'positive',
|
|
@@ -46,8 +49,19 @@ export function CardFeedback({
|
|
|
46
49
|
value: {
|
|
47
50
|
modes
|
|
48
51
|
},
|
|
49
|
-
children: /*#__PURE__*/_jsx(
|
|
52
|
+
children: onPress ? /*#__PURE__*/_jsx(Pressable, {
|
|
53
|
+
style: [containerStyle, style],
|
|
54
|
+
onPress: onPress,
|
|
55
|
+
disabled: disabled,
|
|
56
|
+
accessibilityRole: "button",
|
|
57
|
+
accessibilityLabel: accessibilityLabel,
|
|
58
|
+
accessibilityState: {
|
|
59
|
+
disabled: !!disabled
|
|
60
|
+
},
|
|
61
|
+
children: children
|
|
62
|
+
}) : /*#__PURE__*/_jsx(View, {
|
|
50
63
|
style: [containerStyle, style],
|
|
64
|
+
accessibilityLabel: accessibilityLabel,
|
|
51
65
|
children: children
|
|
52
66
|
})
|
|
53
67
|
});
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import React from 'react';
|
|
4
|
-
import { View, Text } from 'react-native';
|
|
4
|
+
import { View, Text, Pressable } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
6
|
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
|
|
7
7
|
import Badge from '../Badge/Badge';
|
|
8
8
|
import Divider from '../Divider/Divider';
|
|
9
9
|
import Nudge from '../Nudge/Nudge';
|
|
10
10
|
import SavingsGoalSummary from '../SavingsGoalSummary/SavingsGoalSummary';
|
|
11
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
12
12
|
/**
|
|
13
13
|
* `CardInsight` renders a single insight card composed of:
|
|
14
14
|
*
|
|
@@ -28,7 +28,10 @@ function CardInsight({
|
|
|
28
28
|
footer,
|
|
29
29
|
divider,
|
|
30
30
|
modes = EMPTY_MODES,
|
|
31
|
-
style
|
|
31
|
+
style,
|
|
32
|
+
onPress,
|
|
33
|
+
disabled,
|
|
34
|
+
accessibilityLabel
|
|
32
35
|
}) {
|
|
33
36
|
const background = getVariableByName('cardInsight/background', modes) ?? '#ffffff';
|
|
34
37
|
const radius = getVariableByName('cardInsight/radius', modes) ?? 12;
|
|
@@ -101,18 +104,18 @@ function CardInsight({
|
|
|
101
104
|
};
|
|
102
105
|
const footerNode = renderFooter();
|
|
103
106
|
const showDivider = footerNode !== null && divider !== false && divider !== null;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
107
|
+
const containerStyle = {
|
|
108
|
+
backgroundColor: background,
|
|
109
|
+
borderRadius: radius,
|
|
110
|
+
borderWidth,
|
|
111
|
+
borderColor,
|
|
112
|
+
paddingHorizontal,
|
|
113
|
+
paddingVertical,
|
|
114
|
+
gap,
|
|
115
|
+
overflow: 'hidden',
|
|
116
|
+
alignItems: 'stretch'
|
|
117
|
+
};
|
|
118
|
+
const content = /*#__PURE__*/_jsxs(_Fragment, {
|
|
116
119
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
117
120
|
style: {
|
|
118
121
|
flexDirection: 'row',
|
|
@@ -160,5 +163,23 @@ function CardInsight({
|
|
|
160
163
|
children: renderSlot()
|
|
161
164
|
}), showDivider ? renderDivider() : null, footerNode]
|
|
162
165
|
});
|
|
166
|
+
if (onPress) {
|
|
167
|
+
return /*#__PURE__*/_jsx(Pressable, {
|
|
168
|
+
style: [containerStyle, style],
|
|
169
|
+
onPress: onPress,
|
|
170
|
+
disabled: disabled,
|
|
171
|
+
accessibilityRole: "button",
|
|
172
|
+
accessibilityLabel: accessibilityLabel ?? title,
|
|
173
|
+
accessibilityState: {
|
|
174
|
+
disabled: !!disabled
|
|
175
|
+
},
|
|
176
|
+
children: content
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return /*#__PURE__*/_jsx(View, {
|
|
180
|
+
style: [containerStyle, style],
|
|
181
|
+
accessibilityLabel: accessibilityLabel,
|
|
182
|
+
children: content
|
|
183
|
+
});
|
|
163
184
|
}
|
|
164
185
|
export default CardInsight;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import React from 'react';
|
|
4
|
-
import { View } from 'react-native';
|
|
4
|
+
import { View, Pressable } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
6
|
import ProductLabel from '../ProductLabel/ProductLabel';
|
|
7
7
|
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
|
|
8
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
9
|
/**
|
|
10
10
|
* CardProviderInfo displays a product header (ProductLabel) followed by a
|
|
11
11
|
* 2-column grid of children (typically StatItem instances).
|
|
@@ -18,7 +18,10 @@ function CardProviderInfo({
|
|
|
18
18
|
imageSource,
|
|
19
19
|
children,
|
|
20
20
|
modes = EMPTY_MODES,
|
|
21
|
-
style
|
|
21
|
+
style,
|
|
22
|
+
onPress,
|
|
23
|
+
disabled,
|
|
24
|
+
accessibilityLabel
|
|
22
25
|
}) {
|
|
23
26
|
const background = getVariableByName('card/providerInfo/background', modes) ?? '#fef4e5';
|
|
24
27
|
const border = getVariableByName('card/providerInfo/border', modes) ?? '#fef4e5';
|
|
@@ -43,8 +46,7 @@ function CardProviderInfo({
|
|
|
43
46
|
for (let i = 0; i < childArray.length; i += 2) {
|
|
44
47
|
rows.push(childArray.slice(i, i + 2));
|
|
45
48
|
}
|
|
46
|
-
|
|
47
|
-
style: [containerStyle, style],
|
|
49
|
+
const content = /*#__PURE__*/_jsxs(_Fragment, {
|
|
48
50
|
children: [/*#__PURE__*/_jsx(ProductLabel, {
|
|
49
51
|
label: label,
|
|
50
52
|
imageSource: imageSource,
|
|
@@ -67,5 +69,23 @@ function CardProviderInfo({
|
|
|
67
69
|
}, i))
|
|
68
70
|
})]
|
|
69
71
|
});
|
|
72
|
+
if (onPress) {
|
|
73
|
+
return /*#__PURE__*/_jsx(Pressable, {
|
|
74
|
+
style: [containerStyle, style],
|
|
75
|
+
onPress: onPress,
|
|
76
|
+
disabled: disabled,
|
|
77
|
+
accessibilityRole: "button",
|
|
78
|
+
accessibilityLabel: accessibilityLabel ?? label,
|
|
79
|
+
accessibilityState: {
|
|
80
|
+
disabled: !!disabled
|
|
81
|
+
},
|
|
82
|
+
children: content
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return /*#__PURE__*/_jsx(View, {
|
|
86
|
+
style: [containerStyle, style],
|
|
87
|
+
accessibilityLabel: accessibilityLabel,
|
|
88
|
+
children: content
|
|
89
|
+
});
|
|
70
90
|
}
|
|
71
91
|
export default CardProviderInfo;
|
|
@@ -24,13 +24,18 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
24
24
|
* ```tsx
|
|
25
25
|
* const [checked, setChecked] = useState(false)
|
|
26
26
|
*
|
|
27
|
+
* // Recommended: pass the label via the children slot.
|
|
27
28
|
* <CheckboxItem
|
|
28
|
-
* label="Fixed deposit • 0245"
|
|
29
29
|
* checked={checked}
|
|
30
30
|
* onValueChange={setChecked}
|
|
31
31
|
* control="leading"
|
|
32
32
|
* modes={{ 'Color Mode': 'Light' }}
|
|
33
|
-
*
|
|
33
|
+
* >
|
|
34
|
+
* Fixed deposit • 0245
|
|
35
|
+
* </CheckboxItem>
|
|
36
|
+
*
|
|
37
|
+
* // Still supported (deprecated): the `label` prop.
|
|
38
|
+
* <CheckboxItem label="Fixed deposit • 0245" />
|
|
34
39
|
* ```
|
|
35
40
|
*/
|
|
36
41
|
const CheckboxItem = /*#__PURE__*/forwardRef(function CheckboxItem({
|
|
@@ -38,7 +43,8 @@ const CheckboxItem = /*#__PURE__*/forwardRef(function CheckboxItem({
|
|
|
38
43
|
defaultChecked = false,
|
|
39
44
|
onValueChange,
|
|
40
45
|
disabled = false,
|
|
41
|
-
|
|
46
|
+
children,
|
|
47
|
+
label,
|
|
42
48
|
control = 'leading',
|
|
43
49
|
endSlot,
|
|
44
50
|
endSlotWidth = 80,
|
|
@@ -47,6 +53,16 @@ const CheckboxItem = /*#__PURE__*/forwardRef(function CheckboxItem({
|
|
|
47
53
|
labelStyle,
|
|
48
54
|
accessibilityLabel
|
|
49
55
|
}, ref) {
|
|
56
|
+
// Label slot resolution — the `children` slot is the primary API; the legacy
|
|
57
|
+
// `label` prop is a backward-compatible fallback. Precedence:
|
|
58
|
+
// 1. `children`, when provided (non-null / non-false).
|
|
59
|
+
// 2. otherwise the legacy `label` prop, whenever it was passed at all —
|
|
60
|
+
// including an explicit `null`/`false`, which (as before) hides the
|
|
61
|
+
// label entirely.
|
|
62
|
+
// 3. otherwise the Figma placeholder, so the default story still renders
|
|
63
|
+
// something meaningful.
|
|
64
|
+
const hasChildren = children != null && children !== false;
|
|
65
|
+
const slotContent = hasChildren ? children : label !== undefined ? label : 'Fixed deposit • 0245';
|
|
50
66
|
const isTrailing = control === 'trailing';
|
|
51
67
|
const isControlled = controlledChecked !== undefined;
|
|
52
68
|
const [internalChecked, setInternalChecked] = useState(defaultChecked);
|
|
@@ -85,7 +101,7 @@ const CheckboxItem = /*#__PURE__*/forwardRef(function CheckboxItem({
|
|
|
85
101
|
lineHeight: labelLineHeight,
|
|
86
102
|
fontWeight: labelFontWeight
|
|
87
103
|
};
|
|
88
|
-
const a11yLabel = accessibilityLabel ?? (typeof
|
|
104
|
+
const a11yLabel = accessibilityLabel ?? (typeof slotContent === 'string' ? slotContent : undefined);
|
|
89
105
|
const checkboxNode = /*#__PURE__*/_jsx(Checkbox, {
|
|
90
106
|
checked: isChecked,
|
|
91
107
|
disabled: disabled,
|
|
@@ -95,17 +111,24 @@ const CheckboxItem = /*#__PURE__*/forwardRef(function CheckboxItem({
|
|
|
95
111
|
accessibilityLabel: a11yLabel
|
|
96
112
|
} : {})
|
|
97
113
|
});
|
|
98
|
-
|
|
114
|
+
|
|
115
|
+
// A plain string/number renders with the token-driven label style. Any other
|
|
116
|
+
// node is treated as custom slot content: it fills the label region and
|
|
117
|
+
// receives the parent `modes` (so nested design-system components theme
|
|
118
|
+
// correctly). An explicit null/false hides the label entirely (legacy
|
|
119
|
+
// behaviour of the `label` prop).
|
|
120
|
+
const isTextSlot = typeof slotContent === 'string' || typeof slotContent === 'number';
|
|
121
|
+
const labelNode = slotContent == null || slotContent === false ? null : isTextSlot ? /*#__PURE__*/_jsx(Text, {
|
|
99
122
|
style: [resolvedLabelStyle, labelStyle],
|
|
100
123
|
selectable: false,
|
|
101
|
-
children:
|
|
124
|
+
children: slotContent
|
|
102
125
|
}) : /*#__PURE__*/_jsx(View, {
|
|
103
126
|
style: {
|
|
104
127
|
flex: 1,
|
|
105
128
|
minWidth: 0
|
|
106
129
|
},
|
|
107
|
-
children:
|
|
108
|
-
})
|
|
130
|
+
children: cloneChildrenWithModes(slotContent, modes)
|
|
131
|
+
});
|
|
109
132
|
const endSlotNode = endSlot ? /*#__PURE__*/_jsx(View, {
|
|
110
133
|
style: {
|
|
111
134
|
width: endSlotWidth,
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useContext, useMemo } from 'react';
|
|
4
|
+
import { Platform, View } from 'react-native';
|
|
5
|
+
import Animated, { useAnimatedKeyboard, useAnimatedStyle } from 'react-native-reanimated';
|
|
6
|
+
import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
|
|
7
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
8
|
+
import { EMPTY_MODES, cloneChildrenWithModes, flattenChildren } from '../../utils/react-utils';
|
|
9
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
10
|
+
const IS_WEB = Platform.OS === 'web';
|
|
11
|
+
function resolveContentSheetStyle(modes) {
|
|
12
|
+
const backgroundColor = getVariableByName('contentSheet/bg', modes);
|
|
13
|
+
const gap = getVariableByName('contentSheet/gap', modes);
|
|
14
|
+
const paddingHorizontal = getVariableByName('contentSheet/padding/horizontal', modes);
|
|
15
|
+
const paddingTop = getVariableByName('contentSheet/padding/top', modes);
|
|
16
|
+
const paddingBottom = getVariableByName('contentSheet/padding/bottom', modes);
|
|
17
|
+
const radiusTop = getVariableByName('contentSheet/padding/radius/top', modes);
|
|
18
|
+
const radiusBottom = getVariableByName('contentSheet/padding/radius/bottom', modes);
|
|
19
|
+
return {
|
|
20
|
+
container: {
|
|
21
|
+
backgroundColor,
|
|
22
|
+
gap,
|
|
23
|
+
paddingHorizontal,
|
|
24
|
+
paddingTop,
|
|
25
|
+
// paddingBottom is applied separately so the safe-area inset can be added.
|
|
26
|
+
borderTopLeftRadius: radiusTop,
|
|
27
|
+
borderTopRightRadius: radiusTop,
|
|
28
|
+
borderBottomLeftRadius: radiusBottom,
|
|
29
|
+
borderBottomRightRadius: radiusBottom,
|
|
30
|
+
flexDirection: 'column',
|
|
31
|
+
alignItems: 'stretch',
|
|
32
|
+
overflow: 'hidden'
|
|
33
|
+
},
|
|
34
|
+
paddingBottom
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const pinnedStyle = {
|
|
38
|
+
position: 'absolute',
|
|
39
|
+
left: 0,
|
|
40
|
+
right: 0,
|
|
41
|
+
bottom: 0
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* ContentSheet — a bottom-anchored surface that is essentially one big slot
|
|
46
|
+
* with padding and rounded top corners, mirroring the Figma "Content Sheet".
|
|
47
|
+
*
|
|
48
|
+
* Behaviour highlights:
|
|
49
|
+
* - **Auto height (free & automatic).** The sheet never sets an explicit
|
|
50
|
+
* height. React Native's layout engine (Yoga) sizes it to its children on
|
|
51
|
+
* the native thread, so when the slot content grows or shrinks the sheet
|
|
52
|
+
* follows with zero JS measurement and zero extra re-renders — the most
|
|
53
|
+
* performant option possible.
|
|
54
|
+
* - **Keyboard avoidance** on iOS and Android via `avoidKeyboard` (UI-thread
|
|
55
|
+
* `useAnimatedKeyboard`, no re-renders).
|
|
56
|
+
* - **Bottom pinned** by default; opt out with `pinToBottom={false}`.
|
|
57
|
+
* - **Token-driven** styling via `getVariableByName` + `modes`, with `modes`
|
|
58
|
+
* cascaded to all slot children.
|
|
59
|
+
*/
|
|
60
|
+
function ContentSheet({
|
|
61
|
+
children,
|
|
62
|
+
modes = EMPTY_MODES,
|
|
63
|
+
avoidKeyboard = true,
|
|
64
|
+
keyboardSpacing = 0,
|
|
65
|
+
safeAreaBottom = true,
|
|
66
|
+
pinToBottom = true,
|
|
67
|
+
style,
|
|
68
|
+
...rest
|
|
69
|
+
}) {
|
|
70
|
+
// Read the safe-area inset directly from context (rather than
|
|
71
|
+
// `useSafeAreaInsets`, which throws when no provider is mounted). This keeps
|
|
72
|
+
// the sheet usable without a `SafeAreaProvider` — it simply falls back to 0.
|
|
73
|
+
const safeAreaInsets = useContext(SafeAreaInsetsContext);
|
|
74
|
+
const bottomInset = safeAreaInsets?.bottom ?? 0;
|
|
75
|
+
const resolved = useMemo(() => resolveContentSheetStyle(modes), [modes]);
|
|
76
|
+
const processedChildren = useMemo(() => cloneChildrenWithModes(flattenChildren(children), modes), [children, modes]);
|
|
77
|
+
const paddingBottom = resolved.paddingBottom + (safeAreaBottom ? bottomInset : 0);
|
|
78
|
+
const containerStyle = useMemo(() => [pinToBottom ? pinnedStyle : null, resolved.container, {
|
|
79
|
+
paddingBottom
|
|
80
|
+
}, style], [pinToBottom, resolved.container, paddingBottom, style]);
|
|
81
|
+
|
|
82
|
+
// Switching between the keyboard-aware and plain implementation is keyed off
|
|
83
|
+
// `avoidKeyboard` (and platform). Because they are distinct component types,
|
|
84
|
+
// React remounts on toggle, so the conditional `useAnimatedKeyboard` hook
|
|
85
|
+
// inside `KeyboardAwareSheet` always runs in a stable hook order.
|
|
86
|
+
if (avoidKeyboard && !IS_WEB) {
|
|
87
|
+
return /*#__PURE__*/_jsx(KeyboardAwareSheet, {
|
|
88
|
+
style: containerStyle,
|
|
89
|
+
spacing: keyboardSpacing,
|
|
90
|
+
...rest,
|
|
91
|
+
children: processedChildren
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return /*#__PURE__*/_jsx(View, {
|
|
95
|
+
style: containerStyle,
|
|
96
|
+
...rest,
|
|
97
|
+
children: processedChildren
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Native-only wrapper that lifts the sheet by the live keyboard height. The
|
|
102
|
+
* translation is computed on the UI thread from `useAnimatedKeyboard`, so the
|
|
103
|
+
* sheet tracks the keyboard frame-for-frame without any JS work.
|
|
104
|
+
*/
|
|
105
|
+
function KeyboardAwareSheet({
|
|
106
|
+
style,
|
|
107
|
+
spacing,
|
|
108
|
+
children,
|
|
109
|
+
...rest
|
|
110
|
+
}) {
|
|
111
|
+
const keyboard = useAnimatedKeyboard();
|
|
112
|
+
const keyboardStyle = useAnimatedStyle(() => {
|
|
113
|
+
const height = keyboard.height.value;
|
|
114
|
+
return {
|
|
115
|
+
transform: [{
|
|
116
|
+
translateY: height > 0 ? -(height + spacing) : 0
|
|
117
|
+
}]
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
return /*#__PURE__*/_jsx(Animated.View, {
|
|
121
|
+
style: [style, keyboardStyle],
|
|
122
|
+
...rest,
|
|
123
|
+
children: children
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
export default ContentSheet;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import React from 'react';
|
|
4
|
-
import { View, Text, Image } from 'react-native';
|
|
4
|
+
import { View, Text, Image, Pressable } from 'react-native';
|
|
5
5
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
6
|
import { useTokens } from '../../design-tokens/JFSThemeProvider';
|
|
7
7
|
import { cloneChildrenWithModes, EMPTY_MODES } from '../../utils/react-utils';
|
|
8
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
9
9
|
const defaultCardArt = require('./cf64a67d5075caa8924d20b4e4e650d47b3ee08b.png');
|
|
10
10
|
function DebitCard({
|
|
11
11
|
cardholderName = 'Shruti Shukla',
|
|
@@ -17,7 +17,10 @@ function DebitCard({
|
|
|
17
17
|
bankLogoSlot,
|
|
18
18
|
providerLogoSlot,
|
|
19
19
|
modes: propModes = EMPTY_MODES,
|
|
20
|
-
style
|
|
20
|
+
style,
|
|
21
|
+
onPress,
|
|
22
|
+
disabled,
|
|
23
|
+
accessibilityLabel
|
|
21
24
|
}) {
|
|
22
25
|
const {
|
|
23
26
|
modes: globalModes
|
|
@@ -116,8 +119,7 @@ function DebitCard({
|
|
|
116
119
|
};
|
|
117
120
|
return cardArtSource;
|
|
118
121
|
}, [cardArtSource]);
|
|
119
|
-
|
|
120
|
-
style: [containerStyle, style],
|
|
122
|
+
const content = /*#__PURE__*/_jsxs(_Fragment, {
|
|
121
123
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
122
124
|
style: headerStyle,
|
|
123
125
|
children: [/*#__PURE__*/_jsxs(View, {
|
|
@@ -157,5 +159,23 @@ function DebitCard({
|
|
|
157
159
|
})]
|
|
158
160
|
})]
|
|
159
161
|
});
|
|
162
|
+
if (onPress) {
|
|
163
|
+
return /*#__PURE__*/_jsx(Pressable, {
|
|
164
|
+
style: [containerStyle, style],
|
|
165
|
+
onPress: onPress,
|
|
166
|
+
disabled: disabled,
|
|
167
|
+
accessibilityRole: "button",
|
|
168
|
+
accessibilityLabel: accessibilityLabel ?? `${bankName} ${cardType} ${cardNumber}`,
|
|
169
|
+
accessibilityState: {
|
|
170
|
+
disabled: !!disabled
|
|
171
|
+
},
|
|
172
|
+
children: content
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return /*#__PURE__*/_jsx(View, {
|
|
176
|
+
style: [containerStyle, style],
|
|
177
|
+
accessibilityLabel: accessibilityLabel,
|
|
178
|
+
children: content
|
|
179
|
+
});
|
|
160
180
|
}
|
|
161
181
|
export default DebitCard;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useMemo } from 'react';
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
6
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider';
|
|
7
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils';
|
|
8
|
+
import Title from '../Title/Title';
|
|
9
|
+
import InputSearch from '../InputSearch/InputSearch';
|
|
10
|
+
import IconButton from '../IconButton/IconButton';
|
|
11
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
12
|
+
// The filter button in the design is a tonal "secondary" variant (light-purple
|
|
13
|
+
// fill, purple icon). These modes reproduce `iconButton/background = #dbcfff`
|
|
14
|
+
// and `iconButton/icon/color = #5d00b5`. Applied as defaults so the button
|
|
15
|
+
// matches the Figma design out of the box; consumer-provided `modes` still win.
|
|
16
|
+
const FILTER_BUTTON_MODES = {
|
|
17
|
+
AppearanceBrand: 'Secondary',
|
|
18
|
+
Emphasis: 'Medium'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Inside a Hero Section the Title is flush with the section padding — the
|
|
22
|
+
// `title/padding/*` tokens default to 16 but resolve to 0 in the `Page Hero`
|
|
23
|
+
// context (matching the Figma design). Cascaded so the title lines up with the
|
|
24
|
+
// search row instead of getting a double 16px inset.
|
|
25
|
+
const TITLE_MODES = {
|
|
26
|
+
context7: 'Page Hero'
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* HeroSection is a page-level header block: a title (with optional subtitle),
|
|
30
|
+
* an optional search row (a flexible search input plus a trailing filter
|
|
31
|
+
* button), and a free-form content slot below.
|
|
32
|
+
*
|
|
33
|
+
* All spacing/background values resolve from the `HeroSection / Output` design
|
|
34
|
+
* token collection via `getVariableByName`. `modes` cascade to every slot and
|
|
35
|
+
* child component so downstream tokens stay in sync.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* <HeroSection
|
|
40
|
+
* title="Transactions"
|
|
41
|
+
* subtitle="Last 30 days"
|
|
42
|
+
* searchValue={query}
|
|
43
|
+
* onSearchChange={setQuery}
|
|
44
|
+
* onFilterPress={openFilters}
|
|
45
|
+
* >
|
|
46
|
+
* <TransactionList data={items} />
|
|
47
|
+
* </HeroSection>
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
function HeroSection({
|
|
51
|
+
title = 'Page Title',
|
|
52
|
+
subtitle = 'Subtitle',
|
|
53
|
+
titleTextAlign = 'Left',
|
|
54
|
+
showTitle = true,
|
|
55
|
+
titleSlot,
|
|
56
|
+
showSearch = true,
|
|
57
|
+
searchValue,
|
|
58
|
+
onSearchChange,
|
|
59
|
+
searchPlaceholder = 'Search',
|
|
60
|
+
searchSlot,
|
|
61
|
+
showFilter = true,
|
|
62
|
+
filterIcon = 'ic_filter',
|
|
63
|
+
onFilterPress,
|
|
64
|
+
filterAccessibilityLabel = 'Filter',
|
|
65
|
+
filterSlot,
|
|
66
|
+
children,
|
|
67
|
+
modes: propModes = EMPTY_MODES,
|
|
68
|
+
style,
|
|
69
|
+
testID
|
|
70
|
+
}) {
|
|
71
|
+
const {
|
|
72
|
+
modes: globalModes
|
|
73
|
+
} = useTokens();
|
|
74
|
+
const modes = useMemo(() => ({
|
|
75
|
+
...globalModes,
|
|
76
|
+
...propModes
|
|
77
|
+
}), [globalModes, propModes]);
|
|
78
|
+
const gap = Number(getVariableByName('heroSection/gap', modes));
|
|
79
|
+
const paddingHorizontal = Number(getVariableByName('heroSection/padding/horizontal', modes));
|
|
80
|
+
const paddingVertical = Number(getVariableByName('heroSection/padding/vertical', modes));
|
|
81
|
+
const containerStyle = {
|
|
82
|
+
backgroundColor: '#ffffff',
|
|
83
|
+
flexDirection: 'column',
|
|
84
|
+
alignItems: 'flex-start',
|
|
85
|
+
gap,
|
|
86
|
+
paddingHorizontal,
|
|
87
|
+
paddingVertical,
|
|
88
|
+
width: '100%'
|
|
89
|
+
};
|
|
90
|
+
const titleContent = useMemo(() => {
|
|
91
|
+
if (titleSlot !== undefined && titleSlot !== null) {
|
|
92
|
+
return cloneChildrenWithModes(titleSlot, modes);
|
|
93
|
+
}
|
|
94
|
+
if (!showTitle) return null;
|
|
95
|
+
return /*#__PURE__*/_jsx(Title, {
|
|
96
|
+
title: title,
|
|
97
|
+
subtitle: subtitle,
|
|
98
|
+
textAlign: titleTextAlign,
|
|
99
|
+
modes: {
|
|
100
|
+
...TITLE_MODES,
|
|
101
|
+
...modes
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}, [titleSlot, showTitle, title, subtitle, titleTextAlign, modes]);
|
|
105
|
+
const searchInput = useMemo(() => {
|
|
106
|
+
if (searchSlot !== undefined && searchSlot !== null) {
|
|
107
|
+
return cloneChildrenWithModes(searchSlot, modes);
|
|
108
|
+
}
|
|
109
|
+
return /*#__PURE__*/_jsx(InputSearch, {
|
|
110
|
+
supportText: false,
|
|
111
|
+
placeholder: searchPlaceholder,
|
|
112
|
+
value: searchValue,
|
|
113
|
+
onChangeText: onSearchChange,
|
|
114
|
+
containerStyle: {
|
|
115
|
+
flex: 1
|
|
116
|
+
},
|
|
117
|
+
modes: modes
|
|
118
|
+
});
|
|
119
|
+
}, [searchSlot, searchPlaceholder, searchValue, onSearchChange, modes]);
|
|
120
|
+
const filterControl = useMemo(() => {
|
|
121
|
+
if (filterSlot !== undefined && filterSlot !== null) {
|
|
122
|
+
return cloneChildrenWithModes(filterSlot, modes);
|
|
123
|
+
}
|
|
124
|
+
if (!showFilter) return null;
|
|
125
|
+
return /*#__PURE__*/_jsx(IconButton, {
|
|
126
|
+
iconName: filterIcon,
|
|
127
|
+
onPress: onFilterPress,
|
|
128
|
+
accessibilityLabel: filterAccessibilityLabel,
|
|
129
|
+
modes: {
|
|
130
|
+
...FILTER_BUTTON_MODES,
|
|
131
|
+
...modes
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}, [filterSlot, showFilter, filterIcon, onFilterPress, filterAccessibilityLabel, modes]);
|
|
135
|
+
const searchRow = searchSlot !== undefined && searchSlot !== null ? true : showSearch;
|
|
136
|
+
const bodyContent = useMemo(() => {
|
|
137
|
+
if (children === undefined || children === null) return null;
|
|
138
|
+
return cloneChildrenWithModes(children, modes);
|
|
139
|
+
}, [children, modes]);
|
|
140
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
141
|
+
style: [containerStyle, style],
|
|
142
|
+
testID: testID,
|
|
143
|
+
children: [titleContent, searchRow ? /*#__PURE__*/_jsxs(View, {
|
|
144
|
+
style: {
|
|
145
|
+
flexDirection: 'row',
|
|
146
|
+
alignItems: 'center',
|
|
147
|
+
gap,
|
|
148
|
+
width: '100%'
|
|
149
|
+
},
|
|
150
|
+
children: [searchInput, filterControl]
|
|
151
|
+
}) : null, bodyContent != null ? /*#__PURE__*/_jsx(View, {
|
|
152
|
+
style: {
|
|
153
|
+
width: '100%'
|
|
154
|
+
},
|
|
155
|
+
children: bodyContent
|
|
156
|
+
}) : null]
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
export default HeroSection;
|