jfs-components 0.1.19 → 0.1.25
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 +13 -0
- package/lib/commonjs/components/CardCTA/CardCTA.js +32 -25
- package/lib/commonjs/components/CheckboxItem/CheckboxItem.js +31 -8
- package/lib/commonjs/components/ChipSelect/ChipSelect.js +2 -1
- package/lib/commonjs/components/ContentSheet/ContentSheet.js +131 -0
- 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 +27 -5
- package/lib/commonjs/components/MetricData/MetricData.js +132 -0
- package/lib/commonjs/components/Rating/Rating.js +137 -0
- 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 +55 -43
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/CardCTA/CardCTA.js +32 -25
- package/lib/module/components/CheckboxItem/CheckboxItem.js +31 -8
- package/lib/module/components/ChipSelect/ChipSelect.js +2 -1
- package/lib/module/components/ContentSheet/ContentSheet.js +126 -0
- 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 +27 -5
- package/lib/module/components/MetricData/MetricData.js +127 -0
- package/lib/module/components/Rating/Rating.js +132 -0
- 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 +55 -43
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/CheckboxItem/CheckboxItem.d.ts +26 -3
- package/lib/typescript/src/components/ChipSelect/ChipSelect.d.ts +6 -1
- package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +71 -0
- 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/MetricData/MetricData.d.ts +53 -0
- package/lib/typescript/src/components/Rating/Rating.d.ts +30 -0
- 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/CardCTA/CardCTA.tsx +30 -15
- package/src/components/CheckboxItem/CheckboxItem.tsx +59 -14
- package/src/components/ChipSelect/ChipSelect.tsx +7 -1
- package/src/components/ContentSheet/ContentSheet.tsx +217 -0
- 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 +26 -4
- package/src/components/MetricData/MetricData.tsx +185 -0
- package/src/components/Rating/Rating.tsx +174 -0
- 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 +55 -43
- 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.
|
|
@@ -230,22 +230,39 @@ function CardCTA({
|
|
|
230
230
|
zIndex: 1
|
|
231
231
|
};
|
|
232
232
|
|
|
233
|
-
// NOTE: rightWrap must
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
// the
|
|
239
|
-
//
|
|
240
|
-
// this because
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
//
|
|
233
|
+
// NOTE: rightWrap must implement the design's TRUE `2fr` column.
|
|
234
|
+
//
|
|
235
|
+
// History of this style — two Yoga pitfalls pull in opposite directions:
|
|
236
|
+
//
|
|
237
|
+
// 1. `flex: 2` (=> flexBasis: 0, flexShrink: 1): on Android, Yoga could
|
|
238
|
+
// shrink the wrapper below its padding+IconCapsule width when the left
|
|
239
|
+
// text was long, collapsing the padding and gluing the icon to the
|
|
240
|
+
// text. Browsers hide this because they honor `min-width: auto`; Yoga
|
|
241
|
+
// always treats min-width as 0 unless told otherwise.
|
|
242
|
+
// 2. The previous workaround, `flexBasis: 'auto'` + `flexGrow: 2`, fixed
|
|
243
|
+
// #1 but broke the column ratio: Yoga then sizes the wrapper to its
|
|
244
|
+
// content FIRST (icon + padding) and distributes only the leftover
|
|
245
|
+
// space 3:2. The icon width gets baked in on top of the 2fr share, so
|
|
246
|
+
// the right column ends up much wider than the design's 2fr and the
|
|
247
|
+
// body text wraps early with dead space to its right.
|
|
248
|
+
//
|
|
249
|
+
// Fundamental fix: keep the genuine fr-unit geometry (`flexBasis: 0`,
|
|
250
|
+
// grow 3:2, no shrink) and replace Yoga's missing `min-width: auto` with
|
|
251
|
+
// an explicit computed floor — the resolved IconCapsule size plus the
|
|
252
|
+
// wrapper's own horizontal padding. The column is exactly 2fr whenever
|
|
253
|
+
// space allows and can never collapse below its content.
|
|
254
|
+
const iconCapsuleModes = {
|
|
255
|
+
...modes,
|
|
256
|
+
'IconCapsule / Size': 'M',
|
|
257
|
+
Emphasis: 'Medium',
|
|
258
|
+
AppearanceBrand: 'Secondary'
|
|
259
|
+
};
|
|
260
|
+
const rightWrapMinWidth = ((0, _figmaVariablesResolver.getVariableByName)('iconCapsule/size', iconCapsuleModes) || 0) + (rightPaddingH || 0) * 2;
|
|
245
261
|
const rightWrapStyle = {
|
|
246
262
|
flexGrow: 2,
|
|
247
263
|
flexShrink: 0,
|
|
248
|
-
flexBasis:
|
|
264
|
+
flexBasis: 0,
|
|
265
|
+
minWidth: rightWrapMinWidth,
|
|
249
266
|
paddingHorizontal: rightPaddingH,
|
|
250
267
|
paddingVertical: rightPaddingV,
|
|
251
268
|
alignItems: 'flex-end',
|
|
@@ -499,19 +516,9 @@ function CardCTA({
|
|
|
499
516
|
})]
|
|
500
517
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
501
518
|
style: rightWrapStyle,
|
|
502
|
-
children: iconSlot ? (0, _reactUtils.cloneChildrenWithModes)(iconSlot, {
|
|
503
|
-
...modes,
|
|
504
|
-
'IconCapsule / Size': 'M',
|
|
505
|
-
Emphasis: 'Medium',
|
|
506
|
-
AppearanceBrand: 'Secondary'
|
|
507
|
-
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_IconCapsule.default, {
|
|
519
|
+
children: iconSlot ? (0, _reactUtils.cloneChildrenWithModes)(iconSlot, iconCapsuleModes) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_IconCapsule.default, {
|
|
508
520
|
iconName: iconName,
|
|
509
|
-
modes:
|
|
510
|
-
...modes,
|
|
511
|
-
'IconCapsule / Size': 'M',
|
|
512
|
-
Emphasis: 'Medium',
|
|
513
|
-
AppearanceBrand: 'Secondary'
|
|
514
|
-
}
|
|
521
|
+
modes: iconCapsuleModes
|
|
515
522
|
})
|
|
516
523
|
})]
|
|
517
524
|
});
|
|
@@ -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,
|
|
@@ -19,6 +19,7 @@ function ChipSelect({
|
|
|
19
19
|
label = 'Date',
|
|
20
20
|
active = false,
|
|
21
21
|
icon = 'ic_calendar_week',
|
|
22
|
+
showCloseIcon = true,
|
|
22
23
|
modes = _reactUtils.EMPTY_MODES,
|
|
23
24
|
style,
|
|
24
25
|
labelSlot,
|
|
@@ -92,7 +93,7 @@ function ChipSelect({
|
|
|
92
93
|
name: icon,
|
|
93
94
|
size: iconSize,
|
|
94
95
|
color: textColor
|
|
95
|
-
}), renderLabel(), active && /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon.default, {
|
|
96
|
+
}), renderLabel(), active && showCloseIcon && /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon.default, {
|
|
96
97
|
name: "ic_close",
|
|
97
98
|
size: iconSize,
|
|
98
99
|
color: textColor
|
|
@@ -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
|
-
|
|
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')
|
|
17
|
-
|
|
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);
|