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
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## [0.1.23] - 2026-07-01
|
|
8
|
+
|
|
9
|
+
- Added `Link` — token-driven underlined text link mirroring the `Text` API (`text`/`children`, `autolayout`, `textAlign`, `disableTruncation`, `singleLine`); navigation-agnostic with consumer-provided `onPress`.
|
|
10
|
+
- Added `HeroSection` — page-hero block with title/subtitle, search + filter row (`InputSearch` + tonal `IconButton`), and replaceable `titleSlot` / `searchSlot`; token-driven spacing and default modes for Page Hero context.
|
|
11
|
+
- Added `ContentSheet` — bottom-anchored sheet surface with auto height, rounded top corners, `modes` cascade to slot children, optional keyboard avoidance via UI-thread `useAnimatedKeyboard`, and safe-area bottom inset support.
|
|
12
|
+
- `CheckboxItem` — `children` is now the primary label slot (mirrors the Figma slot); legacy `label` prop kept for backward compatibility but deprecated.
|
|
13
|
+
- `ListItem` — container surface tokens from the `List Item Style` collection (`backgroundColor`, `borderColor`, `borderWidth`, `borderRadius`); Default style remains visually unchanged; Boxed style paints a filled, rounded, bordered surface.
|
|
14
|
+
- `Image` — new `cache` prop for iOS remote URL cache control (`default` / `reload` / `force-cache` / `only-if-cached`); `onLoad` and `onError` forwarded to RN `<Image>`.
|
|
15
|
+
- Card-family components (`Card`, `CardInsight`, `CardProviderInfo`, `MediaCard`, `DebitCard`, `CardFeedback`, `RechargeCard`, `TestimonialsCard`) — removed ad-hoc pressed opacity; pressed feedback now comes from design tokens.
|
|
16
|
+
- Library-wide Figma token sync and MDX token documentation refresh across 40+ components; updated `figma-modes.generated.ts`, `.token-metadata.json`, and Coin Variables tokens.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [0.1.19] - 2026-06-29
|
|
21
|
+
|
|
22
|
+
- Card components — optional `onPress` handler added to `Card`, `CardInsight`, `CardProviderInfo`, `MediaCard`, `DebitCard`, `CardFeedback`, `RechargeCard`, and `TestimonialsCard`. When set, the card renders through a `Pressable` with `accessibilityRole="button"`, optional `disabled`, and a pressed-state opacity feedback; when omitted, behaviour is unchanged.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
7
26
|
## [0.1.18] - 2026-06-29
|
|
8
27
|
|
|
9
28
|
- **Fix:** Remove `@react-native-masked-view/masked-view` — fixes `Element type is invalid` crash on New Architecture (`ProductMerchandisingCard` image cards, `AvatarGroup`, `GlassFill` progressive blur). All masking now uses `react-native-svg` (no new peer deps; consumers can drop `masked-view` if added as a workaround).
|
|
@@ -38,7 +38,10 @@ function Card({
|
|
|
38
38
|
children,
|
|
39
39
|
modes = _reactUtils.EMPTY_MODES,
|
|
40
40
|
mediaAspectRatio = 154 / 116,
|
|
41
|
-
style
|
|
41
|
+
style,
|
|
42
|
+
onPress,
|
|
43
|
+
disabled,
|
|
44
|
+
accessibilityLabel
|
|
42
45
|
}) {
|
|
43
46
|
// Resolve Card Container Tokens
|
|
44
47
|
const backgroundColor = (0, _figmaVariablesResolver.getVariableByName)('card/background/color', modes);
|
|
@@ -102,22 +105,36 @@ function Card({
|
|
|
102
105
|
paddingHorizontal: contentPaddingHorizontal,
|
|
103
106
|
paddingVertical: contentPaddingVertical
|
|
104
107
|
};
|
|
108
|
+
const content = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
109
|
+
children: [header && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
110
|
+
style: headerWrapperStyle,
|
|
111
|
+
children: headerWithModes
|
|
112
|
+
}), media && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
113
|
+
style: mediaWrapperStyle,
|
|
114
|
+
children: mediaWithModes
|
|
115
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
116
|
+
style: contentWrapperStyle,
|
|
117
|
+
children: children
|
|
118
|
+
})]
|
|
119
|
+
});
|
|
105
120
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(CardContext.Provider, {
|
|
106
121
|
value: {
|
|
107
122
|
modes
|
|
108
123
|
},
|
|
109
|
-
children: /*#__PURE__*/(0, _jsxRuntime.
|
|
124
|
+
children: onPress ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
125
|
+
style: [containerStyle, style],
|
|
126
|
+
onPress: onPress,
|
|
127
|
+
disabled: disabled,
|
|
128
|
+
accessibilityRole: "button",
|
|
129
|
+
accessibilityLabel: accessibilityLabel,
|
|
130
|
+
accessibilityState: {
|
|
131
|
+
disabled: !!disabled
|
|
132
|
+
},
|
|
133
|
+
children: content
|
|
134
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
110
135
|
style: [containerStyle, style],
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
children: headerWithModes
|
|
114
|
-
}), media && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
115
|
-
style: mediaWrapperStyle,
|
|
116
|
-
children: mediaWithModes
|
|
117
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
118
|
-
style: contentWrapperStyle,
|
|
119
|
-
children: children
|
|
120
|
-
})]
|
|
136
|
+
accessibilityLabel: accessibilityLabel,
|
|
137
|
+
children: content
|
|
121
138
|
})
|
|
122
139
|
});
|
|
123
140
|
}
|
|
@@ -29,7 +29,10 @@ const CardFeedbackContext = /*#__PURE__*/(0, _react.createContext)({});
|
|
|
29
29
|
function CardFeedback({
|
|
30
30
|
children,
|
|
31
31
|
modes: propModes,
|
|
32
|
-
style
|
|
32
|
+
style,
|
|
33
|
+
onPress,
|
|
34
|
+
disabled,
|
|
35
|
+
accessibilityLabel
|
|
33
36
|
}) {
|
|
34
37
|
const modes = {
|
|
35
38
|
'Appearance.System': 'positive',
|
|
@@ -58,8 +61,19 @@ function CardFeedback({
|
|
|
58
61
|
value: {
|
|
59
62
|
modes
|
|
60
63
|
},
|
|
61
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.
|
|
64
|
+
children: onPress ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
65
|
+
style: [containerStyle, style],
|
|
66
|
+
onPress: onPress,
|
|
67
|
+
disabled: disabled,
|
|
68
|
+
accessibilityRole: "button",
|
|
69
|
+
accessibilityLabel: accessibilityLabel,
|
|
70
|
+
accessibilityState: {
|
|
71
|
+
disabled: !!disabled
|
|
72
|
+
},
|
|
73
|
+
children: children
|
|
74
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
62
75
|
style: [containerStyle, style],
|
|
76
|
+
accessibilityLabel: accessibilityLabel,
|
|
63
77
|
children: children
|
|
64
78
|
})
|
|
65
79
|
});
|
|
@@ -33,7 +33,10 @@ function CardInsight({
|
|
|
33
33
|
footer,
|
|
34
34
|
divider,
|
|
35
35
|
modes = _reactUtils.EMPTY_MODES,
|
|
36
|
-
style
|
|
36
|
+
style,
|
|
37
|
+
onPress,
|
|
38
|
+
disabled,
|
|
39
|
+
accessibilityLabel
|
|
37
40
|
}) {
|
|
38
41
|
const background = (0, _figmaVariablesResolver.getVariableByName)('cardInsight/background', modes) ?? '#ffffff';
|
|
39
42
|
const radius = (0, _figmaVariablesResolver.getVariableByName)('cardInsight/radius', modes) ?? 12;
|
|
@@ -106,18 +109,18 @@ function CardInsight({
|
|
|
106
109
|
};
|
|
107
110
|
const footerNode = renderFooter();
|
|
108
111
|
const showDivider = footerNode !== null && divider !== false && divider !== null;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
112
|
+
const containerStyle = {
|
|
113
|
+
backgroundColor: background,
|
|
114
|
+
borderRadius: radius,
|
|
115
|
+
borderWidth,
|
|
116
|
+
borderColor,
|
|
117
|
+
paddingHorizontal,
|
|
118
|
+
paddingVertical,
|
|
119
|
+
gap,
|
|
120
|
+
overflow: 'hidden',
|
|
121
|
+
alignItems: 'stretch'
|
|
122
|
+
};
|
|
123
|
+
const content = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
121
124
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
122
125
|
style: {
|
|
123
126
|
flexDirection: 'row',
|
|
@@ -165,5 +168,23 @@ function CardInsight({
|
|
|
165
168
|
children: renderSlot()
|
|
166
169
|
}), showDivider ? renderDivider() : null, footerNode]
|
|
167
170
|
});
|
|
171
|
+
if (onPress) {
|
|
172
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
173
|
+
style: [containerStyle, style],
|
|
174
|
+
onPress: onPress,
|
|
175
|
+
disabled: disabled,
|
|
176
|
+
accessibilityRole: "button",
|
|
177
|
+
accessibilityLabel: accessibilityLabel ?? title,
|
|
178
|
+
accessibilityState: {
|
|
179
|
+
disabled: !!disabled
|
|
180
|
+
},
|
|
181
|
+
children: content
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
185
|
+
style: [containerStyle, style],
|
|
186
|
+
accessibilityLabel: accessibilityLabel,
|
|
187
|
+
children: content
|
|
188
|
+
});
|
|
168
189
|
}
|
|
169
190
|
var _default = exports.default = CardInsight;
|
|
@@ -23,7 +23,10 @@ function CardProviderInfo({
|
|
|
23
23
|
imageSource,
|
|
24
24
|
children,
|
|
25
25
|
modes = _reactUtils.EMPTY_MODES,
|
|
26
|
-
style
|
|
26
|
+
style,
|
|
27
|
+
onPress,
|
|
28
|
+
disabled,
|
|
29
|
+
accessibilityLabel
|
|
27
30
|
}) {
|
|
28
31
|
const background = (0, _figmaVariablesResolver.getVariableByName)('card/providerInfo/background', modes) ?? '#fef4e5';
|
|
29
32
|
const border = (0, _figmaVariablesResolver.getVariableByName)('card/providerInfo/border', modes) ?? '#fef4e5';
|
|
@@ -48,8 +51,7 @@ function CardProviderInfo({
|
|
|
48
51
|
for (let i = 0; i < childArray.length; i += 2) {
|
|
49
52
|
rows.push(childArray.slice(i, i + 2));
|
|
50
53
|
}
|
|
51
|
-
|
|
52
|
-
style: [containerStyle, style],
|
|
54
|
+
const content = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
53
55
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_ProductLabel.default, {
|
|
54
56
|
label: label,
|
|
55
57
|
imageSource: imageSource,
|
|
@@ -72,5 +74,23 @@ function CardProviderInfo({
|
|
|
72
74
|
}, i))
|
|
73
75
|
})]
|
|
74
76
|
});
|
|
77
|
+
if (onPress) {
|
|
78
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
79
|
+
style: [containerStyle, style],
|
|
80
|
+
onPress: onPress,
|
|
81
|
+
disabled: disabled,
|
|
82
|
+
accessibilityRole: "button",
|
|
83
|
+
accessibilityLabel: accessibilityLabel ?? label,
|
|
84
|
+
accessibilityState: {
|
|
85
|
+
disabled: !!disabled
|
|
86
|
+
},
|
|
87
|
+
children: content
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
91
|
+
style: [containerStyle, style],
|
|
92
|
+
accessibilityLabel: accessibilityLabel,
|
|
93
|
+
children: content
|
|
94
|
+
});
|
|
75
95
|
}
|
|
76
96
|
var _default = exports.default = CardProviderInfo;
|
|
@@ -30,13 +30,18 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
30
30
|
* ```tsx
|
|
31
31
|
* const [checked, setChecked] = useState(false)
|
|
32
32
|
*
|
|
33
|
+
* // Recommended: pass the label via the children slot.
|
|
33
34
|
* <CheckboxItem
|
|
34
|
-
* label="Fixed deposit • 0245"
|
|
35
35
|
* checked={checked}
|
|
36
36
|
* onValueChange={setChecked}
|
|
37
37
|
* control="leading"
|
|
38
38
|
* modes={{ 'Color Mode': 'Light' }}
|
|
39
|
-
*
|
|
39
|
+
* >
|
|
40
|
+
* Fixed deposit • 0245
|
|
41
|
+
* </CheckboxItem>
|
|
42
|
+
*
|
|
43
|
+
* // Still supported (deprecated): the `label` prop.
|
|
44
|
+
* <CheckboxItem label="Fixed deposit • 0245" />
|
|
40
45
|
* ```
|
|
41
46
|
*/
|
|
42
47
|
const CheckboxItem = /*#__PURE__*/(0, _react.forwardRef)(function CheckboxItem({
|
|
@@ -44,7 +49,8 @@ const CheckboxItem = /*#__PURE__*/(0, _react.forwardRef)(function CheckboxItem({
|
|
|
44
49
|
defaultChecked = false,
|
|
45
50
|
onValueChange,
|
|
46
51
|
disabled = false,
|
|
47
|
-
|
|
52
|
+
children,
|
|
53
|
+
label,
|
|
48
54
|
control = 'leading',
|
|
49
55
|
endSlot,
|
|
50
56
|
endSlotWidth = 80,
|
|
@@ -53,6 +59,16 @@ const CheckboxItem = /*#__PURE__*/(0, _react.forwardRef)(function CheckboxItem({
|
|
|
53
59
|
labelStyle,
|
|
54
60
|
accessibilityLabel
|
|
55
61
|
}, ref) {
|
|
62
|
+
// Label slot resolution — the `children` slot is the primary API; the legacy
|
|
63
|
+
// `label` prop is a backward-compatible fallback. Precedence:
|
|
64
|
+
// 1. `children`, when provided (non-null / non-false).
|
|
65
|
+
// 2. otherwise the legacy `label` prop, whenever it was passed at all —
|
|
66
|
+
// including an explicit `null`/`false`, which (as before) hides the
|
|
67
|
+
// label entirely.
|
|
68
|
+
// 3. otherwise the Figma placeholder, so the default story still renders
|
|
69
|
+
// something meaningful.
|
|
70
|
+
const hasChildren = children != null && children !== false;
|
|
71
|
+
const slotContent = hasChildren ? children : label !== undefined ? label : 'Fixed deposit • 0245';
|
|
56
72
|
const isTrailing = control === 'trailing';
|
|
57
73
|
const isControlled = controlledChecked !== undefined;
|
|
58
74
|
const [internalChecked, setInternalChecked] = (0, _react.useState)(defaultChecked);
|
|
@@ -91,7 +107,7 @@ const CheckboxItem = /*#__PURE__*/(0, _react.forwardRef)(function CheckboxItem({
|
|
|
91
107
|
lineHeight: labelLineHeight,
|
|
92
108
|
fontWeight: labelFontWeight
|
|
93
109
|
};
|
|
94
|
-
const a11yLabel = accessibilityLabel ?? (typeof
|
|
110
|
+
const a11yLabel = accessibilityLabel ?? (typeof slotContent === 'string' ? slotContent : undefined);
|
|
95
111
|
const checkboxNode = /*#__PURE__*/(0, _jsxRuntime.jsx)(_Checkbox.default, {
|
|
96
112
|
checked: isChecked,
|
|
97
113
|
disabled: disabled,
|
|
@@ -101,17 +117,24 @@ const CheckboxItem = /*#__PURE__*/(0, _react.forwardRef)(function CheckboxItem({
|
|
|
101
117
|
accessibilityLabel: a11yLabel
|
|
102
118
|
} : {})
|
|
103
119
|
});
|
|
104
|
-
|
|
120
|
+
|
|
121
|
+
// A plain string/number renders with the token-driven label style. Any other
|
|
122
|
+
// node is treated as custom slot content: it fills the label region and
|
|
123
|
+
// receives the parent `modes` (so nested design-system components theme
|
|
124
|
+
// correctly). An explicit null/false hides the label entirely (legacy
|
|
125
|
+
// behaviour of the `label` prop).
|
|
126
|
+
const isTextSlot = typeof slotContent === 'string' || typeof slotContent === 'number';
|
|
127
|
+
const labelNode = slotContent == null || slotContent === false ? null : isTextSlot ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
105
128
|
style: [resolvedLabelStyle, labelStyle],
|
|
106
129
|
selectable: false,
|
|
107
|
-
children:
|
|
130
|
+
children: slotContent
|
|
108
131
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
109
132
|
style: {
|
|
110
133
|
flex: 1,
|
|
111
134
|
minWidth: 0
|
|
112
135
|
},
|
|
113
|
-
children:
|
|
114
|
-
})
|
|
136
|
+
children: (0, _reactUtils.cloneChildrenWithModes)(slotContent, modes)
|
|
137
|
+
});
|
|
115
138
|
const endSlotNode = endSlot ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
116
139
|
style: {
|
|
117
140
|
width: endSlotWidth,
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
|
|
10
|
+
var _reactNativeSafeAreaContext = require("react-native-safe-area-context");
|
|
11
|
+
var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
|
|
12
|
+
var _reactUtils = require("../../utils/react-utils");
|
|
13
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
14
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
15
|
+
const IS_WEB = _reactNative.Platform.OS === 'web';
|
|
16
|
+
function resolveContentSheetStyle(modes) {
|
|
17
|
+
const backgroundColor = (0, _figmaVariablesResolver.getVariableByName)('contentSheet/bg', modes);
|
|
18
|
+
const gap = (0, _figmaVariablesResolver.getVariableByName)('contentSheet/gap', modes);
|
|
19
|
+
const paddingHorizontal = (0, _figmaVariablesResolver.getVariableByName)('contentSheet/padding/horizontal', modes);
|
|
20
|
+
const paddingTop = (0, _figmaVariablesResolver.getVariableByName)('contentSheet/padding/top', modes);
|
|
21
|
+
const paddingBottom = (0, _figmaVariablesResolver.getVariableByName)('contentSheet/padding/bottom', modes);
|
|
22
|
+
const radiusTop = (0, _figmaVariablesResolver.getVariableByName)('contentSheet/padding/radius/top', modes);
|
|
23
|
+
const radiusBottom = (0, _figmaVariablesResolver.getVariableByName)('contentSheet/padding/radius/bottom', modes);
|
|
24
|
+
return {
|
|
25
|
+
container: {
|
|
26
|
+
backgroundColor,
|
|
27
|
+
gap,
|
|
28
|
+
paddingHorizontal,
|
|
29
|
+
paddingTop,
|
|
30
|
+
// paddingBottom is applied separately so the safe-area inset can be added.
|
|
31
|
+
borderTopLeftRadius: radiusTop,
|
|
32
|
+
borderTopRightRadius: radiusTop,
|
|
33
|
+
borderBottomLeftRadius: radiusBottom,
|
|
34
|
+
borderBottomRightRadius: radiusBottom,
|
|
35
|
+
flexDirection: 'column',
|
|
36
|
+
alignItems: 'stretch',
|
|
37
|
+
overflow: 'hidden'
|
|
38
|
+
},
|
|
39
|
+
paddingBottom
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const pinnedStyle = {
|
|
43
|
+
position: 'absolute',
|
|
44
|
+
left: 0,
|
|
45
|
+
right: 0,
|
|
46
|
+
bottom: 0
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* ContentSheet — a bottom-anchored surface that is essentially one big slot
|
|
51
|
+
* with padding and rounded top corners, mirroring the Figma "Content Sheet".
|
|
52
|
+
*
|
|
53
|
+
* Behaviour highlights:
|
|
54
|
+
* - **Auto height (free & automatic).** The sheet never sets an explicit
|
|
55
|
+
* height. React Native's layout engine (Yoga) sizes it to its children on
|
|
56
|
+
* the native thread, so when the slot content grows or shrinks the sheet
|
|
57
|
+
* follows with zero JS measurement and zero extra re-renders — the most
|
|
58
|
+
* performant option possible.
|
|
59
|
+
* - **Keyboard avoidance** on iOS and Android via `avoidKeyboard` (UI-thread
|
|
60
|
+
* `useAnimatedKeyboard`, no re-renders).
|
|
61
|
+
* - **Bottom pinned** by default; opt out with `pinToBottom={false}`.
|
|
62
|
+
* - **Token-driven** styling via `getVariableByName` + `modes`, with `modes`
|
|
63
|
+
* cascaded to all slot children.
|
|
64
|
+
*/
|
|
65
|
+
function ContentSheet({
|
|
66
|
+
children,
|
|
67
|
+
modes = _reactUtils.EMPTY_MODES,
|
|
68
|
+
avoidKeyboard = true,
|
|
69
|
+
keyboardSpacing = 0,
|
|
70
|
+
safeAreaBottom = true,
|
|
71
|
+
pinToBottom = true,
|
|
72
|
+
style,
|
|
73
|
+
...rest
|
|
74
|
+
}) {
|
|
75
|
+
// Read the safe-area inset directly from context (rather than
|
|
76
|
+
// `useSafeAreaInsets`, which throws when no provider is mounted). This keeps
|
|
77
|
+
// the sheet usable without a `SafeAreaProvider` — it simply falls back to 0.
|
|
78
|
+
const safeAreaInsets = (0, _react.useContext)(_reactNativeSafeAreaContext.SafeAreaInsetsContext);
|
|
79
|
+
const bottomInset = safeAreaInsets?.bottom ?? 0;
|
|
80
|
+
const resolved = (0, _react.useMemo)(() => resolveContentSheetStyle(modes), [modes]);
|
|
81
|
+
const processedChildren = (0, _react.useMemo)(() => (0, _reactUtils.cloneChildrenWithModes)((0, _reactUtils.flattenChildren)(children), modes), [children, modes]);
|
|
82
|
+
const paddingBottom = resolved.paddingBottom + (safeAreaBottom ? bottomInset : 0);
|
|
83
|
+
const containerStyle = (0, _react.useMemo)(() => [pinToBottom ? pinnedStyle : null, resolved.container, {
|
|
84
|
+
paddingBottom
|
|
85
|
+
}, style], [pinToBottom, resolved.container, paddingBottom, style]);
|
|
86
|
+
|
|
87
|
+
// Switching between the keyboard-aware and plain implementation is keyed off
|
|
88
|
+
// `avoidKeyboard` (and platform). Because they are distinct component types,
|
|
89
|
+
// React remounts on toggle, so the conditional `useAnimatedKeyboard` hook
|
|
90
|
+
// inside `KeyboardAwareSheet` always runs in a stable hook order.
|
|
91
|
+
if (avoidKeyboard && !IS_WEB) {
|
|
92
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(KeyboardAwareSheet, {
|
|
93
|
+
style: containerStyle,
|
|
94
|
+
spacing: keyboardSpacing,
|
|
95
|
+
...rest,
|
|
96
|
+
children: processedChildren
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
100
|
+
style: containerStyle,
|
|
101
|
+
...rest,
|
|
102
|
+
children: processedChildren
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Native-only wrapper that lifts the sheet by the live keyboard height. The
|
|
107
|
+
* translation is computed on the UI thread from `useAnimatedKeyboard`, so the
|
|
108
|
+
* sheet tracks the keyboard frame-for-frame without any JS work.
|
|
109
|
+
*/
|
|
110
|
+
function KeyboardAwareSheet({
|
|
111
|
+
style,
|
|
112
|
+
spacing,
|
|
113
|
+
children,
|
|
114
|
+
...rest
|
|
115
|
+
}) {
|
|
116
|
+
const keyboard = (0, _reactNativeReanimated.useAnimatedKeyboard)();
|
|
117
|
+
const keyboardStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
|
|
118
|
+
const height = keyboard.height.value;
|
|
119
|
+
return {
|
|
120
|
+
transform: [{
|
|
121
|
+
translateY: height > 0 ? -(height + spacing) : 0
|
|
122
|
+
}]
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
|
|
126
|
+
style: [style, keyboardStyle],
|
|
127
|
+
...rest,
|
|
128
|
+
children: children
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
var _default = exports.default = ContentSheet;
|
|
@@ -22,7 +22,10 @@ function DebitCard({
|
|
|
22
22
|
bankLogoSlot,
|
|
23
23
|
providerLogoSlot,
|
|
24
24
|
modes: propModes = _reactUtils.EMPTY_MODES,
|
|
25
|
-
style
|
|
25
|
+
style,
|
|
26
|
+
onPress,
|
|
27
|
+
disabled,
|
|
28
|
+
accessibilityLabel
|
|
26
29
|
}) {
|
|
27
30
|
const {
|
|
28
31
|
modes: globalModes
|
|
@@ -121,8 +124,7 @@ function DebitCard({
|
|
|
121
124
|
};
|
|
122
125
|
return cardArtSource;
|
|
123
126
|
}, [cardArtSource]);
|
|
124
|
-
|
|
125
|
-
style: [containerStyle, style],
|
|
127
|
+
const content = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
126
128
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
127
129
|
style: headerStyle,
|
|
128
130
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
@@ -162,5 +164,23 @@ function DebitCard({
|
|
|
162
164
|
})]
|
|
163
165
|
})]
|
|
164
166
|
});
|
|
167
|
+
if (onPress) {
|
|
168
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
169
|
+
style: [containerStyle, style],
|
|
170
|
+
onPress: onPress,
|
|
171
|
+
disabled: disabled,
|
|
172
|
+
accessibilityRole: "button",
|
|
173
|
+
accessibilityLabel: accessibilityLabel ?? `${bankName} ${cardType} ${cardNumber}`,
|
|
174
|
+
accessibilityState: {
|
|
175
|
+
disabled: !!disabled
|
|
176
|
+
},
|
|
177
|
+
children: content
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
181
|
+
style: [containerStyle, style],
|
|
182
|
+
accessibilityLabel: accessibilityLabel,
|
|
183
|
+
children: content
|
|
184
|
+
});
|
|
165
185
|
}
|
|
166
186
|
var _default = exports.default = DebitCard;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
|
|
10
|
+
var _JFSThemeProvider = require("../../design-tokens/JFSThemeProvider");
|
|
11
|
+
var _reactUtils = require("../../utils/react-utils");
|
|
12
|
+
var _Title = _interopRequireDefault(require("../Title/Title"));
|
|
13
|
+
var _InputSearch = _interopRequireDefault(require("../InputSearch/InputSearch"));
|
|
14
|
+
var _IconButton = _interopRequireDefault(require("../IconButton/IconButton"));
|
|
15
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
16
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
17
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
18
|
+
// The filter button in the design is a tonal "secondary" variant (light-purple
|
|
19
|
+
// fill, purple icon). These modes reproduce `iconButton/background = #dbcfff`
|
|
20
|
+
// and `iconButton/icon/color = #5d00b5`. Applied as defaults so the button
|
|
21
|
+
// matches the Figma design out of the box; consumer-provided `modes` still win.
|
|
22
|
+
const FILTER_BUTTON_MODES = {
|
|
23
|
+
AppearanceBrand: 'Secondary',
|
|
24
|
+
Emphasis: 'Medium'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Inside a Hero Section the Title is flush with the section padding — the
|
|
28
|
+
// `title/padding/*` tokens default to 16 but resolve to 0 in the `Page Hero`
|
|
29
|
+
// context (matching the Figma design). Cascaded so the title lines up with the
|
|
30
|
+
// search row instead of getting a double 16px inset.
|
|
31
|
+
const TITLE_MODES = {
|
|
32
|
+
context7: 'Page Hero'
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* HeroSection is a page-level header block: a title (with optional subtitle),
|
|
36
|
+
* an optional search row (a flexible search input plus a trailing filter
|
|
37
|
+
* button), and a free-form content slot below.
|
|
38
|
+
*
|
|
39
|
+
* All spacing/background values resolve from the `HeroSection / Output` design
|
|
40
|
+
* token collection via `getVariableByName`. `modes` cascade to every slot and
|
|
41
|
+
* child component so downstream tokens stay in sync.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* <HeroSection
|
|
46
|
+
* title="Transactions"
|
|
47
|
+
* subtitle="Last 30 days"
|
|
48
|
+
* searchValue={query}
|
|
49
|
+
* onSearchChange={setQuery}
|
|
50
|
+
* onFilterPress={openFilters}
|
|
51
|
+
* >
|
|
52
|
+
* <TransactionList data={items} />
|
|
53
|
+
* </HeroSection>
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
function HeroSection({
|
|
57
|
+
title = 'Page Title',
|
|
58
|
+
subtitle = 'Subtitle',
|
|
59
|
+
titleTextAlign = 'Left',
|
|
60
|
+
showTitle = true,
|
|
61
|
+
titleSlot,
|
|
62
|
+
showSearch = true,
|
|
63
|
+
searchValue,
|
|
64
|
+
onSearchChange,
|
|
65
|
+
searchPlaceholder = 'Search',
|
|
66
|
+
searchSlot,
|
|
67
|
+
showFilter = true,
|
|
68
|
+
filterIcon = 'ic_filter',
|
|
69
|
+
onFilterPress,
|
|
70
|
+
filterAccessibilityLabel = 'Filter',
|
|
71
|
+
filterSlot,
|
|
72
|
+
children,
|
|
73
|
+
modes: propModes = _reactUtils.EMPTY_MODES,
|
|
74
|
+
style,
|
|
75
|
+
testID
|
|
76
|
+
}) {
|
|
77
|
+
const {
|
|
78
|
+
modes: globalModes
|
|
79
|
+
} = (0, _JFSThemeProvider.useTokens)();
|
|
80
|
+
const modes = (0, _react.useMemo)(() => ({
|
|
81
|
+
...globalModes,
|
|
82
|
+
...propModes
|
|
83
|
+
}), [globalModes, propModes]);
|
|
84
|
+
const gap = Number((0, _figmaVariablesResolver.getVariableByName)('heroSection/gap', modes));
|
|
85
|
+
const paddingHorizontal = Number((0, _figmaVariablesResolver.getVariableByName)('heroSection/padding/horizontal', modes));
|
|
86
|
+
const paddingVertical = Number((0, _figmaVariablesResolver.getVariableByName)('heroSection/padding/vertical', modes));
|
|
87
|
+
const containerStyle = {
|
|
88
|
+
backgroundColor: '#ffffff',
|
|
89
|
+
flexDirection: 'column',
|
|
90
|
+
alignItems: 'flex-start',
|
|
91
|
+
gap,
|
|
92
|
+
paddingHorizontal,
|
|
93
|
+
paddingVertical,
|
|
94
|
+
width: '100%'
|
|
95
|
+
};
|
|
96
|
+
const titleContent = (0, _react.useMemo)(() => {
|
|
97
|
+
if (titleSlot !== undefined && titleSlot !== null) {
|
|
98
|
+
return (0, _reactUtils.cloneChildrenWithModes)(titleSlot, modes);
|
|
99
|
+
}
|
|
100
|
+
if (!showTitle) return null;
|
|
101
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Title.default, {
|
|
102
|
+
title: title,
|
|
103
|
+
subtitle: subtitle,
|
|
104
|
+
textAlign: titleTextAlign,
|
|
105
|
+
modes: {
|
|
106
|
+
...TITLE_MODES,
|
|
107
|
+
...modes
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}, [titleSlot, showTitle, title, subtitle, titleTextAlign, modes]);
|
|
111
|
+
const searchInput = (0, _react.useMemo)(() => {
|
|
112
|
+
if (searchSlot !== undefined && searchSlot !== null) {
|
|
113
|
+
return (0, _reactUtils.cloneChildrenWithModes)(searchSlot, modes);
|
|
114
|
+
}
|
|
115
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_InputSearch.default, {
|
|
116
|
+
supportText: false,
|
|
117
|
+
placeholder: searchPlaceholder,
|
|
118
|
+
value: searchValue,
|
|
119
|
+
onChangeText: onSearchChange,
|
|
120
|
+
containerStyle: {
|
|
121
|
+
flex: 1
|
|
122
|
+
},
|
|
123
|
+
modes: modes
|
|
124
|
+
});
|
|
125
|
+
}, [searchSlot, searchPlaceholder, searchValue, onSearchChange, modes]);
|
|
126
|
+
const filterControl = (0, _react.useMemo)(() => {
|
|
127
|
+
if (filterSlot !== undefined && filterSlot !== null) {
|
|
128
|
+
return (0, _reactUtils.cloneChildrenWithModes)(filterSlot, modes);
|
|
129
|
+
}
|
|
130
|
+
if (!showFilter) return null;
|
|
131
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_IconButton.default, {
|
|
132
|
+
iconName: filterIcon,
|
|
133
|
+
onPress: onFilterPress,
|
|
134
|
+
accessibilityLabel: filterAccessibilityLabel,
|
|
135
|
+
modes: {
|
|
136
|
+
...FILTER_BUTTON_MODES,
|
|
137
|
+
...modes
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}, [filterSlot, showFilter, filterIcon, onFilterPress, filterAccessibilityLabel, modes]);
|
|
141
|
+
const searchRow = searchSlot !== undefined && searchSlot !== null ? true : showSearch;
|
|
142
|
+
const bodyContent = (0, _react.useMemo)(() => {
|
|
143
|
+
if (children === undefined || children === null) return null;
|
|
144
|
+
return (0, _reactUtils.cloneChildrenWithModes)(children, modes);
|
|
145
|
+
}, [children, modes]);
|
|
146
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
147
|
+
style: [containerStyle, style],
|
|
148
|
+
testID: testID,
|
|
149
|
+
children: [titleContent, searchRow ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
150
|
+
style: {
|
|
151
|
+
flexDirection: 'row',
|
|
152
|
+
alignItems: 'center',
|
|
153
|
+
gap,
|
|
154
|
+
width: '100%'
|
|
155
|
+
},
|
|
156
|
+
children: [searchInput, filterControl]
|
|
157
|
+
}) : null, bodyContent != null ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
158
|
+
style: {
|
|
159
|
+
width: '100%'
|
|
160
|
+
},
|
|
161
|
+
children: bodyContent
|
|
162
|
+
}) : null]
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
var _default = exports.default = HeroSection;
|