jfs-components 0.1.19 → 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 (40) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +31 -8
  3. package/lib/commonjs/components/ContentSheet/ContentSheet.js +131 -0
  4. package/lib/commonjs/components/HeroSection/HeroSection.js +165 -0
  5. package/lib/commonjs/components/Image/Image.js +33 -7
  6. package/lib/commonjs/components/Link/Link.js +115 -0
  7. package/lib/commonjs/components/ListItem/ListItem.js +16 -1
  8. package/lib/commonjs/components/index.js +21 -0
  9. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  10. package/lib/commonjs/design-tokens/figma-modes.generated.js +53 -43
  11. package/lib/commonjs/icons/registry.js +1 -1
  12. package/lib/module/components/CheckboxItem/CheckboxItem.js +31 -8
  13. package/lib/module/components/ContentSheet/ContentSheet.js +126 -0
  14. package/lib/module/components/HeroSection/HeroSection.js +159 -0
  15. package/lib/module/components/Image/Image.js +34 -7
  16. package/lib/module/components/Link/Link.js +110 -0
  17. package/lib/module/components/ListItem/ListItem.js +16 -1
  18. package/lib/module/components/index.js +3 -0
  19. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  20. package/lib/module/design-tokens/figma-modes.generated.js +53 -43
  21. package/lib/module/icons/registry.js +1 -1
  22. package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +26 -3
  23. package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +71 -0
  24. package/lib/typescript/src/components/HeroSection/HeroSection.d.ts +80 -0
  25. package/lib/typescript/src/components/Image/Image.d.ts +27 -2
  26. package/lib/typescript/src/components/Link/Link.d.ts +73 -0
  27. package/lib/typescript/src/components/index.d.ts +3 -0
  28. package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +8 -0
  29. package/lib/typescript/src/icons/registry.d.ts +1 -1
  30. package/package.json +1 -1
  31. package/src/components/CheckboxItem/CheckboxItem.tsx +59 -14
  32. package/src/components/ContentSheet/ContentSheet.tsx +217 -0
  33. package/src/components/HeroSection/HeroSection.tsx +231 -0
  34. package/src/components/Image/Image.tsx +55 -3
  35. package/src/components/Link/Link.tsx +159 -0
  36. package/src/components/ListItem/ListItem.tsx +15 -0
  37. package/src/components/index.ts +3 -0
  38. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  39. package/src/design-tokens/figma-modes.generated.ts +53 -43
  40. package/src/icons/registry.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@ 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
+
7
20
  ## [0.1.19] - 2026-06-29
8
21
 
9
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.
@@ -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
- label = 'Fixed deposit • 0245',
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 label === 'string' ? label : undefined);
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
- const labelNode = label != null && label !== false ? typeof label === 'string' || typeof label === 'number' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
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: label
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: label
114
- }) : null;
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;
@@ -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;
@@ -11,11 +11,32 @@ var _SkeletonGroup = require("../../skeleton/SkeletonGroup");
11
11
  var _jsxRuntime = require("react/jsx-runtime");
12
12
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
13
  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); }
14
- function normalizeSource(imageSource) {
14
+ /**
15
+ * iOS URL cache control (maps to RN's `source.cache`, no-op on Android/web):
16
+ * - `'default'` — use the native platform's default caching.
17
+ * - `'reload'` — ignore any cache, always fetch from the network.
18
+ * - `'force-cache'` — use the cached response regardless of age; fetch
19
+ * only if absent.
20
+ * - `'only-if-cached'` — use the cache only; never hit the network.
21
+ */
22
+
23
+ function normalizeSource(imageSource, cache) {
15
24
  if (imageSource == null) return undefined;
16
- if (typeof imageSource === 'string') return {
17
- uri: imageSource
18
- };
25
+ if (typeof imageSource === 'string') {
26
+ return cache ? {
27
+ uri: imageSource,
28
+ cache
29
+ } : {
30
+ uri: imageSource
31
+ };
32
+ }
33
+ // Only remote sources (single object with a `uri`) can carry a cache policy.
34
+ if (cache && !Array.isArray(imageSource) && typeof imageSource === 'object' && 'uri' in imageSource) {
35
+ return {
36
+ ...imageSource,
37
+ cache
38
+ };
39
+ }
19
40
  return imageSource;
20
41
  }
21
42
 
@@ -51,9 +72,12 @@ function Image({
51
72
  accessibilityLabel,
52
73
  accessibilityElementsHidden,
53
74
  importantForAccessibility,
54
- loading
75
+ loading,
76
+ cache,
77
+ onLoad,
78
+ onError
55
79
  }) {
56
- const source = (0, _react.useMemo)(() => normalizeSource(imageSource), [imageSource]);
80
+ const source = (0, _react.useMemo)(() => normalizeSource(imageSource, cache), [imageSource, cache]);
57
81
 
58
82
  // Explicit { width, height } means a "fill an exact box" layout — typically a
59
83
  // full-bleed hero/background where the asset is high-res and sharpness
@@ -110,7 +134,9 @@ function Image({
110
134
  resizeMethod: effectiveResizeMethod,
111
135
  accessibilityLabel: accessibilityLabel,
112
136
  accessibilityElementsHidden: accessibilityElementsHidden,
113
- importantForAccessibility: importantForAccessibility
137
+ importantForAccessibility: importantForAccessibility,
138
+ onLoad: onLoad,
139
+ onError: onError
114
140
  });
115
141
  }
116
142
  var _default = exports.default = /*#__PURE__*/_react.default.memo(Image);
@@ -0,0 +1,115 @@
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 _reactUtils = require("../../utils/react-utils");
11
+ var _jsxRuntime = require("react/jsx-runtime");
12
+ 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); }
13
+ const TEXT_ALIGN_MAP = {
14
+ Left: 'left',
15
+ Center: 'center'
16
+ };
17
+ const DISABLED_OPACITY = 0.4;
18
+
19
+ /**
20
+ * Link — an underlined, pressable text primitive.
21
+ *
22
+ * It renders a single React Native `<Text>` (not a `Pressable`), so it flows
23
+ * inline and can be nested inside {@link TextSegment} exactly like a `Text`
24
+ * run — the React Native equivalent of an `<a>` inside a `<p>`. Font family,
25
+ * size, weight, line-height and letter-spacing come from the dedicated `link/*`
26
+ * tokens, while the colour resolves from `text/foreground` (so a link sits on
27
+ * the same colour as the surrounding copy). The label is always underlined.
28
+ *
29
+ * @example Standalone
30
+ * ```tsx
31
+ * <Link text="Forgot PIN?" onPress={() => navigation.navigate('ResetPin')} />
32
+ * ```
33
+ *
34
+ * @example Inline inside TextSegment
35
+ * ```tsx
36
+ * <TextSegment>
37
+ * <Text>By continuing you agree to our </Text>
38
+ * <Link onPress={openTerms}>Terms</Link>
39
+ * <Text>.</Text>
40
+ * </TextSegment>
41
+ * ```
42
+ */
43
+ function Link({
44
+ text,
45
+ children,
46
+ onPress,
47
+ disabled = false,
48
+ autolayout = 'Fill',
49
+ textAlign = 'Left',
50
+ modes = _reactUtils.EMPTY_MODES,
51
+ style,
52
+ numberOfLines,
53
+ disableTruncation,
54
+ singleLine,
55
+ accessibilityLabel,
56
+ accessibilityHint
57
+ }) {
58
+ // Bindings mirror the Figma `Link` node exactly: colour from `text/foreground`
59
+ // (so the link matches the surrounding copy), everything else from the
60
+ // dedicated `link/*` tokens.
61
+ const foreground = (0, _figmaVariablesResolver.getVariableByName)('text/foreground', modes) ?? '#000000';
62
+ const fontFamily = (0, _figmaVariablesResolver.getVariableByName)('link/fontFamily', modes) ?? 'JioType Var';
63
+ const fontSize = (0, _figmaVariablesResolver.getVariableByName)('link/fontSize', modes) ?? 12;
64
+ const fontWeight = (0, _figmaVariablesResolver.getVariableByName)('link/fontWeight', modes) ?? 400;
65
+ const lineHeight = (0, _figmaVariablesResolver.getVariableByName)('link/lineHeight', modes) ?? 16;
66
+ const letterSpacing = (0, _figmaVariablesResolver.getVariableByName)('link/letterSpacing', modes) ?? -0.5;
67
+ const linkStyle = {
68
+ color: foreground,
69
+ fontFamily: fontFamily,
70
+ fontSize: fontSize,
71
+ fontWeight: String(fontWeight),
72
+ lineHeight: lineHeight,
73
+ letterSpacing: letterSpacing,
74
+ textAlign: TEXT_ALIGN_MAP[textAlign],
75
+ textDecorationLine: 'underline',
76
+ alignSelf: autolayout === 'Fill' ? 'stretch' : 'flex-start',
77
+ ...(disabled ? {
78
+ opacity: DISABLED_OPACITY
79
+ } : null)
80
+ };
81
+ const content = children !== undefined && children !== null && children !== false ? children : text !== undefined ? text : 'Link';
82
+ const handlePress = (0, _react.useCallback)(event => {
83
+ if (disabled) return;
84
+ onPress?.(event);
85
+ }, [disabled, onPress]);
86
+ const {
87
+ style: layoutStyle,
88
+ ...truncation
89
+ } = (0, _reactUtils.resolveTextLayout)({
90
+ disableTruncation,
91
+ singleLine,
92
+ numberOfLines
93
+ });
94
+ const resolvedLabel = accessibilityLabel ?? (typeof content === 'string' ? content : undefined);
95
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
96
+ accessibilityRole: "link",
97
+ accessibilityState: {
98
+ disabled
99
+ },
100
+ ...(resolvedLabel !== undefined ? {
101
+ accessibilityLabel: resolvedLabel
102
+ } : null),
103
+ ...(accessibilityHint !== undefined ? {
104
+ accessibilityHint
105
+ } : null),
106
+ ...(onPress !== undefined ? {
107
+ onPress: handlePress
108
+ } : null),
109
+ disabled: disabled,
110
+ style: [linkStyle, style, layoutStyle],
111
+ ...truncation,
112
+ children: content
113
+ });
114
+ }
115
+ var _default = exports.default = /*#__PURE__*/_react.default.memo(Link);
@@ -63,6 +63,17 @@ function resolveListItemTokens(modes) {
63
63
  const paddingLeft = (0, _figmaVariablesResolver.getVariableByName)('listItem/padding/left', resolvedModes) ?? 0;
64
64
  const paddingRight = (0, _figmaVariablesResolver.getVariableByName)('listItem/padding/right', resolvedModes) ?? 0;
65
65
  const textWrapGap = (0, _figmaVariablesResolver.getVariableByName)('listItem/text wrap', resolvedModes) ?? 0;
66
+
67
+ // Container surface — driven by the `List Item Style` collection
68
+ // (Default | Boxed | Minimal) plus `ListItem State`, `Selectable`,
69
+ // `Page type` and `Color Mode`. In the default style these all resolve to a
70
+ // transparent background / no border / 0 radius, so existing usages are
71
+ // visually unchanged; the "Boxed" style paints a filled, rounded, bordered
72
+ // surface.
73
+ const backgroundColor = (0, _figmaVariablesResolver.getVariableByName)('listItem/background/color', resolvedModes);
74
+ const borderColor = (0, _figmaVariablesResolver.getVariableByName)('listItem/border/color', resolvedModes);
75
+ const borderWidth = (0, _figmaVariablesResolver.getVariableByName)('listItem/borderWidth', resolvedModes) ?? 0;
76
+ const borderRadius = (0, _figmaVariablesResolver.getVariableByName)('listItem/radius', resolvedModes) ?? 0;
66
77
  const titleColor = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/color', textModes);
67
78
  const titleFontSize = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/fontSize', textModes);
68
79
  const titleLineHeight = (0, _figmaVariablesResolver.getVariableByName)('listItem/title/lineHeight', textModes);
@@ -80,7 +91,11 @@ function resolveListItemTokens(modes) {
80
91
  paddingTop: paddingTop,
81
92
  paddingBottom: paddingBottom,
82
93
  paddingLeft: paddingLeft,
83
- paddingRight: paddingRight
94
+ paddingRight: paddingRight,
95
+ backgroundColor: backgroundColor,
96
+ borderColor: borderColor,
97
+ borderWidth,
98
+ borderRadius
84
99
  },
85
100
  horizontalLayoutStyle: {
86
101
  flexDirection: 'row',
@@ -267,6 +267,12 @@ Object.defineProperty(exports, "ComparisonBar", {
267
267
  return _ComparisonBar.default;
268
268
  }
269
269
  });
270
+ Object.defineProperty(exports, "ContentSheet", {
271
+ enumerable: true,
272
+ get: function () {
273
+ return _ContentSheet.default;
274
+ }
275
+ });
270
276
  Object.defineProperty(exports, "CoverageBarComparison", {
271
277
  enumerable: true,
272
278
  get: function () {
@@ -387,6 +393,12 @@ Object.defineProperty(exports, "HStack", {
387
393
  return _HStack.default;
388
394
  }
389
395
  });
396
+ Object.defineProperty(exports, "HeroSection", {
397
+ enumerable: true,
398
+ get: function () {
399
+ return _HeroSection.default;
400
+ }
401
+ });
390
402
  Object.defineProperty(exports, "HoldingsCard", {
391
403
  enumerable: true,
392
404
  get: function () {
@@ -447,6 +459,12 @@ Object.defineProperty(exports, "LinearProgress", {
447
459
  return _LinearProgress.default;
448
460
  }
449
461
  });
462
+ Object.defineProperty(exports, "Link", {
463
+ enumerable: true,
464
+ get: function () {
465
+ return _Link.default;
466
+ }
467
+ });
450
468
  Object.defineProperty(exports, "ListGroup", {
451
469
  enumerable: true,
452
470
  get: function () {
@@ -1011,6 +1029,7 @@ var _FilterBar = _interopRequireDefault(require("./FilterBar/FilterBar"));
1011
1029
  var _FullscreenModal = _interopRequireDefault(require("./FullscreenModal/FullscreenModal"));
1012
1030
  var _Form = _interopRequireWildcard(require("./Form/Form"));
1013
1031
  var _FormField = _interopRequireDefault(require("./FormField/FormField"));
1032
+ var _ContentSheet = _interopRequireDefault(require("./ContentSheet/ContentSheet"));
1014
1033
  var _CircularProgressBar = _interopRequireDefault(require("./CircularProgressBar/CircularProgressBar"));
1015
1034
  var _CircularProgressBarDoted = _interopRequireDefault(require("./CircularProgressBarDoted/CircularProgressBarDoted"));
1016
1035
  var _CircularRating = _interopRequireDefault(require("./CircularRating/CircularRating"));
@@ -1020,6 +1039,7 @@ var _ComparisonBar = _interopRequireDefault(require("./ComparisonBar/ComparisonB
1020
1039
  var _AllocationComparisonChart = _interopRequireDefault(require("./AllocationComparisonChart/AllocationComparisonChart"));
1021
1040
  var _MonthlyStatusGrid = _interopRequireWildcard(require("./MonthlyStatusGrid/MonthlyStatusGrid"));
1022
1041
  var _Gauge = _interopRequireDefault(require("./Gauge/Gauge"));
1042
+ var _HeroSection = _interopRequireDefault(require("./HeroSection/HeroSection"));
1023
1043
  var _HoldingsCard = _interopRequireDefault(require("./HoldingsCard/HoldingsCard"));
1024
1044
  var _HStack = _interopRequireDefault(require("./HStack/HStack"));
1025
1045
  var _Icon = _interopRequireDefault(require("./Icon/Icon"));
@@ -1027,6 +1047,7 @@ var _IconButton = _interopRequireDefault(require("./IconButton/IconButton"));
1027
1047
  var _IconCapsule = _interopRequireDefault(require("./IconCapsule/IconCapsule"));
1028
1048
  var _Image = _interopRequireDefault(require("./Image/Image"));
1029
1049
  var _LazyList = _interopRequireDefault(require("./LazyList/LazyList"));
1050
+ var _Link = _interopRequireDefault(require("./Link/Link"));
1030
1051
  var _LinearMeter = _interopRequireDefault(require("./LinearMeter/LinearMeter"));
1031
1052
  var _LinearProgress = _interopRequireDefault(require("./LinearProgress/LinearProgress"));
1032
1053
  var _ListGroup = _interopRequireDefault(require("./ListGroup/ListGroup"));