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.
Files changed (72) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/lib/commonjs/components/Card/Card.js +29 -12
  3. package/lib/commonjs/components/CardFeedback/CardFeedback.js +16 -2
  4. package/lib/commonjs/components/CardInsight/CardInsight.js +34 -13
  5. package/lib/commonjs/components/CardProviderInfo/CardProviderInfo.js +23 -3
  6. package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +31 -8
  7. package/lib/commonjs/components/ContentSheet/ContentSheet.js +131 -0
  8. package/lib/commonjs/components/DebitCard/DebitCard.js +23 -3
  9. package/lib/commonjs/components/HeroSection/HeroSection.js +165 -0
  10. package/lib/commonjs/components/Image/Image.js +33 -7
  11. package/lib/commonjs/components/Link/Link.js +115 -0
  12. package/lib/commonjs/components/ListItem/ListItem.js +16 -1
  13. package/lib/commonjs/components/MediaCard/MediaCard.js +24 -7
  14. package/lib/commonjs/components/RechargeCard/RechargeCard.js +34 -13
  15. package/lib/commonjs/components/TestimonialsCard/TestimonialsCard.js +23 -5
  16. package/lib/commonjs/components/index.js +21 -0
  17. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  18. package/lib/commonjs/design-tokens/figma-modes.generated.js +53 -43
  19. package/lib/commonjs/icons/registry.js +1 -1
  20. package/lib/module/components/Card/Card.js +31 -14
  21. package/lib/module/components/CardFeedback/CardFeedback.js +17 -3
  22. package/lib/module/components/CardInsight/CardInsight.js +36 -15
  23. package/lib/module/components/CardProviderInfo/CardProviderInfo.js +25 -5
  24. package/lib/module/components/CheckboxItem/CheckboxItem.js +31 -8
  25. package/lib/module/components/ContentSheet/ContentSheet.js +126 -0
  26. package/lib/module/components/DebitCard/DebitCard.js +25 -5
  27. package/lib/module/components/HeroSection/HeroSection.js +159 -0
  28. package/lib/module/components/Image/Image.js +34 -7
  29. package/lib/module/components/Link/Link.js +110 -0
  30. package/lib/module/components/ListItem/ListItem.js +16 -1
  31. package/lib/module/components/MediaCard/MediaCard.js +26 -9
  32. package/lib/module/components/RechargeCard/RechargeCard.js +36 -15
  33. package/lib/module/components/TestimonialsCard/TestimonialsCard.js +25 -7
  34. package/lib/module/components/index.js +3 -0
  35. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  36. package/lib/module/design-tokens/figma-modes.generated.js +53 -43
  37. package/lib/module/icons/registry.js +1 -1
  38. package/lib/typescript/src/components/Card/Card.d.ts +10 -1
  39. package/lib/typescript/src/components/CardFeedback/CardFeedback.d.ts +10 -1
  40. package/lib/typescript/src/components/CardInsight/CardInsight.d.ts +10 -1
  41. package/lib/typescript/src/components/CardProviderInfo/CardProviderInfo.d.ts +10 -1
  42. package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +26 -3
  43. package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +71 -0
  44. package/lib/typescript/src/components/DebitCard/DebitCard.d.ts +10 -1
  45. package/lib/typescript/src/components/HeroSection/HeroSection.d.ts +80 -0
  46. package/lib/typescript/src/components/Image/Image.d.ts +27 -2
  47. package/lib/typescript/src/components/Link/Link.d.ts +73 -0
  48. package/lib/typescript/src/components/MediaCard/MediaCard.d.ts +10 -1
  49. package/lib/typescript/src/components/RechargeCard/RechargeCard.d.ts +10 -1
  50. package/lib/typescript/src/components/TestimonialsCard/TestimonialsCard.d.ts +8 -1
  51. package/lib/typescript/src/components/index.d.ts +3 -0
  52. package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +8 -0
  53. package/lib/typescript/src/icons/registry.d.ts +1 -1
  54. package/package.json +1 -1
  55. package/src/components/Card/Card.tsx +46 -15
  56. package/src/components/CardFeedback/CardFeedback.tsx +29 -4
  57. package/src/components/CardInsight/CardInsight.tsx +48 -17
  58. package/src/components/CardProviderInfo/CardProviderInfo.tsx +36 -3
  59. package/src/components/CheckboxItem/CheckboxItem.tsx +59 -14
  60. package/src/components/ContentSheet/ContentSheet.tsx +217 -0
  61. package/src/components/DebitCard/DebitCard.tsx +36 -3
  62. package/src/components/HeroSection/HeroSection.tsx +231 -0
  63. package/src/components/Image/Image.tsx +55 -3
  64. package/src/components/Link/Link.tsx +159 -0
  65. package/src/components/ListItem/ListItem.tsx +15 -0
  66. package/src/components/MediaCard/MediaCard.tsx +40 -9
  67. package/src/components/RechargeCard/RechargeCard.tsx +48 -13
  68. package/src/components/TestimonialsCard/TestimonialsCard.tsx +37 -6
  69. package/src/components/index.ts +3 -0
  70. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  71. package/src/design-tokens/figma-modes.generated.ts +53 -43
  72. 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__*/_jsxs(View, {
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
- children: [header && /*#__PURE__*/_jsx(View, {
106
- style: headerWrapperStyle,
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(View, {
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
- return /*#__PURE__*/_jsxs(View, {
105
- style: [{
106
- backgroundColor: background,
107
- borderRadius: radius,
108
- borderWidth,
109
- borderColor,
110
- paddingHorizontal,
111
- paddingVertical,
112
- gap,
113
- overflow: 'hidden',
114
- alignItems: 'stretch'
115
- }, style],
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
- return /*#__PURE__*/_jsxs(View, {
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
- label = 'Fixed deposit • 0245',
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 label === 'string' ? label : undefined);
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
- const labelNode = label != null && label !== false ? typeof label === 'string' || typeof label === 'number' ? /*#__PURE__*/_jsx(Text, {
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: label
124
+ children: slotContent
102
125
  }) : /*#__PURE__*/_jsx(View, {
103
126
  style: {
104
127
  flex: 1,
105
128
  minWidth: 0
106
129
  },
107
- children: label
108
- }) : null;
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
- return /*#__PURE__*/_jsxs(View, {
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;