jfs-components 0.1.25 → 0.1.30
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 +53 -0
- package/lib/commonjs/components/AutoplayControl/AutoplayControl.js +66 -0
- package/lib/commonjs/components/Carousel/Carousel.js +112 -19
- package/lib/commonjs/components/CompareTable/CompareTable.js +191 -25
- package/lib/commonjs/components/ContentSheet/ContentSheet.js +104 -10
- package/lib/commonjs/components/FormUpload/Additem.js +168 -0
- package/lib/commonjs/components/FormUpload/FormUpload.js +198 -0
- package/lib/commonjs/components/Grid/Grid.js +206 -0
- package/lib/commonjs/components/MoneyValue/MoneyValue.js +179 -54
- package/lib/commonjs/components/NumberPagination/NumberPagination.js +144 -0
- package/lib/commonjs/components/PdpCcCard/PdpCcCard.js +7 -1
- package/lib/commonjs/components/ProfileCard/ProfileCard.js +208 -0
- package/lib/commonjs/components/Table/Table.js +27 -7
- package/lib/commonjs/components/UpiHandle/UpiHandle.js +23 -11
- package/lib/commonjs/components/index.js +42 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/design-tokens/figma-modes.generated.js +21 -10
- package/lib/commonjs/icons/components/IconBrokenimage.js +19 -0
- package/lib/commonjs/icons/components/IconImage.js +19 -0
- package/lib/commonjs/icons/components/IconMic.js +19 -0
- package/lib/commonjs/icons/components/IconMicoff.js +19 -0
- package/lib/commonjs/icons/components/IconPause.js +19 -0
- package/lib/commonjs/icons/components/IconPlay.js +19 -0
- package/lib/commonjs/icons/components/index.js +66 -0
- package/lib/commonjs/icons/registry.js +2 -2
- package/lib/module/components/AutoplayControl/AutoplayControl.js +60 -0
- package/lib/module/components/Carousel/Carousel.js +111 -19
- package/lib/module/components/CompareTable/CompareTable.js +191 -26
- package/lib/module/components/ContentSheet/ContentSheet.js +108 -14
- package/lib/module/components/FormUpload/Additem.js +162 -0
- package/lib/module/components/FormUpload/FormUpload.js +192 -0
- package/lib/module/components/Grid/Grid.js +201 -0
- package/lib/module/components/MoneyValue/MoneyValue.js +182 -57
- package/lib/module/components/NumberPagination/NumberPagination.js +138 -0
- package/lib/module/components/PdpCcCard/PdpCcCard.js +7 -1
- package/lib/module/components/ProfileCard/ProfileCard.js +202 -0
- package/lib/module/components/Table/Table.js +27 -7
- package/lib/module/components/UpiHandle/UpiHandle.js +23 -11
- package/lib/module/components/index.js +7 -1
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/design-tokens/figma-modes.generated.js +21 -10
- package/lib/module/icons/components/IconBrokenimage.js +12 -0
- package/lib/module/icons/components/IconImage.js +12 -0
- package/lib/module/icons/components/IconMic.js +12 -0
- package/lib/module/icons/components/IconMicoff.js +12 -0
- package/lib/module/icons/components/IconPause.js +12 -0
- package/lib/module/icons/components/IconPlay.js +12 -0
- package/lib/module/icons/components/index.js +6 -0
- package/lib/module/icons/registry.js +2 -2
- package/lib/typescript/src/components/AutoplayControl/AutoplayControl.d.ts +15 -0
- package/lib/typescript/src/components/Carousel/Carousel.d.ts +8 -1
- package/lib/typescript/src/components/CompareTable/CompareTable.d.ts +21 -1
- package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +20 -1
- package/lib/typescript/src/components/FormUpload/Additem.d.ts +96 -0
- package/lib/typescript/src/components/FormUpload/FormUpload.d.ts +85 -0
- package/lib/typescript/src/components/Grid/Grid.d.ts +87 -0
- package/lib/typescript/src/components/MoneyValue/MoneyValue.d.ts +10 -1
- package/lib/typescript/src/components/NumberPagination/NumberPagination.d.ts +21 -0
- package/lib/typescript/src/components/PdpCcCard/PdpCcCard.d.ts +25 -1
- package/lib/typescript/src/components/ProfileCard/ProfileCard.d.ts +89 -0
- package/lib/typescript/src/components/Table/Table.d.ts +9 -1
- package/lib/typescript/src/components/UpiHandle/UpiHandle.d.ts +7 -1
- package/lib/typescript/src/components/index.d.ts +34 -28
- package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +10 -4
- package/lib/typescript/src/icons/components/IconBrokenimage.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconImage.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconMic.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconMicoff.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconPause.d.ts +3 -0
- package/lib/typescript/src/icons/components/IconPlay.d.ts +3 -0
- package/lib/typescript/src/icons/components/index.d.ts +6 -0
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/AutoplayControl/AutoplayControl.tsx +77 -0
- package/src/components/Carousel/Carousel.tsx +133 -23
- package/src/components/CompareTable/CompareTable.tsx +224 -30
- package/src/components/ContentSheet/ContentSheet.tsx +138 -8
- package/src/components/FormUpload/Additem.tsx +262 -0
- package/src/components/FormUpload/FormUpload.tsx +313 -0
- package/src/components/Grid/Grid.tsx +368 -0
- package/src/components/MoneyValue/MoneyValue.tsx +216 -65
- package/src/components/NumberPagination/NumberPagination.tsx +170 -0
- package/src/components/PdpCcCard/PdpCcCard.tsx +36 -1
- package/src/components/ProfileCard/ProfileCard.tsx +268 -0
- package/src/components/Table/Table.tsx +29 -4
- package/src/components/UpiHandle/UpiHandle.tsx +35 -24
- package/src/components/index.ts +329 -182
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/design-tokens/figma-modes.generated.ts +21 -10
- package/src/icons/components/IconBrokenimage.tsx +11 -0
- package/src/icons/components/IconImage.tsx +11 -0
- package/src/icons/components/IconMic.tsx +11 -0
- package/src/icons/components/IconMicoff.tsx +11 -0
- package/src/icons/components/IconPause.tsx +11 -0
- package/src/icons/components/IconPlay.tsx +11 -0
- package/src/icons/components/index.ts +6 -0
- package/src/icons/registry.ts +25 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,59 @@ 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.30] - 2026-07-10
|
|
8
|
+
|
|
9
|
+
- `MoneyValue` — new `editable` prop (inline edit mode): tapping the value shows a numeric `TextInput` that accepts only digits + one decimal point; edit commits on blur/submit via new `onValueChange` callback; keyboard avoidance lifts the component above the keyboard using `Keyboard.addListener` + `measureInWindow` (same pattern as `ActionFooter`).
|
|
10
|
+
- `ContentSheet` — new `visible` prop controls a bottom pop-up / exit animation driven by Reanimated's `withSpring` (same spring config as `Drawer`: damping 32, stiffness 300). Sheet springs up from below the bottom edge on mount / `visible=true` and springs back down on `visible=false`. Animation composes with keyboard avoidance so both `translateY` offsets are summed in a single `useAnimatedStyle`.
|
|
11
|
+
- `CompareTable` — new `columnWidth` prop enables fixed-width scroll mode: each selection card and table cell gets this exact width; the cards row and every section body become independent horizontal `ScrollView`s whose scroll events are chained so they stay aligned in lockstep; the cards row pins to the top as a sticky header on the vertical axis; gray section-header rows render full-width outside the horizontal scroll so they don't slide with the cells.
|
|
12
|
+
- `PdpCcCard` — new `numberOfLines`, `disableTruncation`, and `singleLine` props passed through to the `Title` component for precise control over title truncation and wrapping.
|
|
13
|
+
- `Table` — new `columnWidth` prop applies a fixed width to every column (header + body) for uniform column sizing in scrollable layouts; new `scrollable` prop wraps the table in a horizontal `ScrollView`.
|
|
14
|
+
- Stories and MDX documentation updated for all new features.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## [0.1.28] - 2026-07-07
|
|
19
|
+
|
|
20
|
+
- Added `FormUpload` — token-driven attachment field with label, support text, `<Form>` validation integration, and a controlled `attachments` / `onAttachmentsChange` API. Renders a row of `Additem` cells (preview + empty upload trigger) with optional `maxCount` cap; accepts an injected `picker` callback so consumers wire their own image/document picker without adding native deps to the library.
|
|
21
|
+
- Added `Additem` — single upload cell used by `FormUpload` and `Grid`; `'empty'` state opens the injected picker, `'preview'` shows the selected asset with remove affordance (`additem/*` tokens).
|
|
22
|
+
- Added `Grid` — token-driven layout primitive with configurable `columns`, optional fixed `rows`, fractional `columnFrs` / `rowFrs` track weights, gap/padding tokens, and `justifyContent` / `alignItems`; each child becomes a cell with `modes` cascade.
|
|
23
|
+
- `ContentSheet` — new optional `title` prop renders a centered sticky header using the same `drawer/title*` token family as `Drawer`, keeping bottom-sheet title styling consistent across surfaces.
|
|
24
|
+
- Added `ic_broken_image`, `ic_image`, `ic_mic`, and `ic_mic_off` icons to the icon registry.
|
|
25
|
+
- Regenerated component MDX token documentation and `.token-metadata.json` across the library.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## [0.1.27] - 2026-07-07
|
|
30
|
+
|
|
31
|
+
- Added `ProfileCard` — dark rounded profile summary card (`profileCard/*` tokens) with an `Attached` monogram `Avatar` (optional `IconCapsule` badge), declarative header props (`name`, `monogram`, `avatarSource`, `upiHandles`) or a full `header` slot override, and a body slot below a `Divider`. Forces `context 10: jioPlus` so nested children (`ListItem`, `UpiHandle`, `Text`, `Button`, …) resolve on-dark profile-card tokens automatically; `Profile Card Appearance` mode (`Default` | `Premium`) recolors the whole card via `modes`.
|
|
32
|
+
- `UpiHandle` — avatar visibility is now **source-driven**: the leading image renders only when `source` / `avatarSource` is provided; omitting it hides the avatar and force-sets `UPI Handle Image: False` so left padding stays symmetric (imageless layout used by `ProfileCard` handle pills). Removed the bundled default `Image.png` placeholder. Consumer `UPI Handle Image` mode values are overridden by source presence so avatar visibility and token layout stay in lockstep. New `WithoutImage` story and MDX usage docs.
|
|
33
|
+
- `Section` — `Section.Bento` stories updated with `UpiHandle` pills that pass explicit avatar `source` values, matching the Figma Bento nav/upi layout (node 216-13845).
|
|
34
|
+
- Figma token sync — `context 10` mode renamed `Profile card` → `jioPlus`; `Profile Card Appearance` collection wired for `ProfileCard` / `UpiHandle`.
|
|
35
|
+
- `Carousel` — new `type="Numbered"` variant: full-bleed slides with floating `AutoplayControl` + `NumberPagination` overlay at the bottom center; interactive play/pause state wired to autoplay.
|
|
36
|
+
- `Carousel` — **Fix:** autoplay pagination no longer flickers back to the previous active page during scroll animations; `activeIndex` is derived from settled offsets instead of intermediate scroll positions.
|
|
37
|
+
- Regenerated component MDX token documentation and `.token-metadata.json` across the library.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## [0.1.26] - 2026-07-07
|
|
42
|
+
|
|
43
|
+
- Added `AutoplayControl` — token-driven play/pause toggle button for carousel autoplay (`autoplayControl/*` tokens; `ic_play` / `ic_pause` icons).
|
|
44
|
+
- Added `NumberPagination` — frosted-glass page-number strip with active/inactive control states (`numberPagination/*`, `controlNumber/*`, `control/*` tokens); supports `children` slot override.
|
|
45
|
+
- Added `ic_play` and `ic_pause` icons to the icon registry.
|
|
46
|
+
- Regenerated component MDX token documentation and `.token-metadata.json` across the library.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## [0.1.25] - 2026-07-03
|
|
51
|
+
|
|
52
|
+
- Added `MetricData` — compact centered metric block (`title`, `value`, optional `caption` and leading `icon`); typography, colours, gaps, and padding resolve from `metricdata/*` tokens; `value` accepts a string or custom node (e.g. `MoneyValue`).
|
|
53
|
+
- Added `Rating` — token-driven star row (`rating/gap`, `ratingStar/*`); filled vs empty stars share one SVG glyph and differ by `Rating Star State`; optional `onChange` for interactive selection, `readOnly` for static display.
|
|
54
|
+
- `CardCTA` — **Fix:** restore true 2fr right-column geometry on Android. Replaces the `flexBasis: 'auto'` workaround (which over-expanded the icon column and caused early body-text wrapping) with `flexBasis: 0` plus an explicit `minWidth` floor sized to the resolved `IconCapsule` + horizontal padding, so the column keeps the design ratio without collapsing when the left text is long.
|
|
55
|
+
- `ChipSelect` — new `showCloseIcon` prop (default `true`); set `showCloseIcon={false}` to hide the close icon in the active state.
|
|
56
|
+
- `ListItem` — vertical layout support text is hard-clamped to exactly two lines via `numberOfLines={2}` (spaces become line breaks, e.g. `"Split Payments"` → two lines). Fixes Bento grid labels wrapping onto a third line on native, where React Native has no `white-space: nowrap` equivalent. Horizontal layout support text uses the same two-line clamp.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
7
60
|
## [0.1.23] - 2026-07-01
|
|
8
61
|
|
|
9
62
|
- Added `Link` — token-driven underlined text link mirroring the `Text` API (`text`/`children`, `autolayout`, `textAlign`, `disableTruncation`, `singleLine`); navigation-agnostic with consumer-provided `onPress`.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = AutoplayControl;
|
|
7
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
|
|
10
|
+
var _Icon = _interopRequireDefault(require("../../icons/Icon"));
|
|
11
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
12
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
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 resolveAutoplayControlTokens(modes, disabled) {
|
|
15
|
+
const width = (0, _figmaVariablesResolver.getVariableByName)('autoplayControl/width', modes) ?? 36;
|
|
16
|
+
const height = (0, _figmaVariablesResolver.getVariableByName)('autoplayControl/height', modes) ?? 36;
|
|
17
|
+
const radiusRaw = (0, _figmaVariablesResolver.getVariableByName)('autoplayControl/radius', modes) ?? 9999;
|
|
18
|
+
const backgroundColor = (0, _figmaVariablesResolver.getVariableByName)('autoplayControl/background/color', modes) ?? '#ffffff';
|
|
19
|
+
const iconColor = (0, _figmaVariablesResolver.getVariableByName)('autoplayControl/icon/color', modes) ?? '#000000';
|
|
20
|
+
const borderRadius = radiusRaw === 9999 ? width / 2 : radiusRaw;
|
|
21
|
+
return {
|
|
22
|
+
containerStyle: {
|
|
23
|
+
width,
|
|
24
|
+
height,
|
|
25
|
+
borderRadius,
|
|
26
|
+
backgroundColor,
|
|
27
|
+
alignItems: 'center',
|
|
28
|
+
justifyContent: 'center',
|
|
29
|
+
opacity: disabled ? 0.5 : 1
|
|
30
|
+
},
|
|
31
|
+
iconColor
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* AutoplayControl is a button component that toggles between play and pause states.
|
|
37
|
+
* All styling values are resolved from Figma design tokens.
|
|
38
|
+
*/
|
|
39
|
+
function AutoplayControl({
|
|
40
|
+
state = 'pause',
|
|
41
|
+
modes,
|
|
42
|
+
onPress,
|
|
43
|
+
disabled = false,
|
|
44
|
+
style
|
|
45
|
+
}) {
|
|
46
|
+
const tokens = (0, _react.useMemo)(() => resolveAutoplayControlTokens(modes ?? {}, disabled), [modes, disabled]);
|
|
47
|
+
const iconName = state === 'play' ? 'ic_play' : 'ic_pause';
|
|
48
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
49
|
+
onPress: disabled ? undefined : onPress,
|
|
50
|
+
disabled: disabled,
|
|
51
|
+
style: ({
|
|
52
|
+
pressed
|
|
53
|
+
}) => [tokens.containerStyle, pressed && {
|
|
54
|
+
opacity: 0.7
|
|
55
|
+
}, style],
|
|
56
|
+
accessibilityRole: "button",
|
|
57
|
+
accessibilityState: {
|
|
58
|
+
disabled
|
|
59
|
+
},
|
|
60
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon.default, {
|
|
61
|
+
name: iconName,
|
|
62
|
+
size: 24,
|
|
63
|
+
color: tokens.iconColor
|
|
64
|
+
})
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -11,7 +11,10 @@ var _react = _interopRequireWildcard(require("react"));
|
|
|
11
11
|
var _reactNative = require("react-native");
|
|
12
12
|
var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
|
|
13
13
|
var _reactUtils = require("../../utils/react-utils");
|
|
14
|
+
var _AutoplayControl = _interopRequireDefault(require("../AutoplayControl/AutoplayControl"));
|
|
15
|
+
var _NumberPagination = _interopRequireDefault(require("../NumberPagination/NumberPagination"));
|
|
14
16
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
17
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
18
|
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); }
|
|
16
19
|
// ---------------------------------------------------------------------------
|
|
17
20
|
// Context
|
|
@@ -45,9 +48,11 @@ function Carousel({
|
|
|
45
48
|
itemWidth: itemWidthProp,
|
|
46
49
|
paddingHorizontal: paddingHorizontalProp,
|
|
47
50
|
paddingVertical: paddingVerticalProp,
|
|
51
|
+
type = 'Default',
|
|
48
52
|
onIndexChange,
|
|
49
53
|
style
|
|
50
54
|
}) {
|
|
55
|
+
const isNumbered = type === 'Numbered';
|
|
51
56
|
// ---- Token resolution ----
|
|
52
57
|
const tokenGap = parseFloat((0, _figmaVariablesResolver.getVariableByName)('carousel/gap', modes));
|
|
53
58
|
const gap = gapProp ?? tokenGap;
|
|
@@ -55,27 +60,43 @@ function Carousel({
|
|
|
55
60
|
const containerPaddingV = parseFloat((0, _figmaVariablesResolver.getVariableByName)('carousel/padding/vertical', modes));
|
|
56
61
|
// Outer container max height per Figma (`carousel/maxHeight`).
|
|
57
62
|
const maxHeight = parseFloat((0, _figmaVariablesResolver.getVariableByName)('carousel/maxHeight', modes));
|
|
63
|
+
// Gap between AutoplayControl and NumberPagination in the Numbered overlay.
|
|
64
|
+
const controlGap = parseFloat((0, _figmaVariablesResolver.getVariableByName)('carouselControl/gap', modes));
|
|
65
|
+
|
|
58
66
|
// Spacing between the cards row and the pagination dots uses `carousel/gap`.
|
|
59
67
|
const paginationOffset = gap;
|
|
60
68
|
|
|
61
69
|
// ---- Refs & state ----
|
|
62
70
|
const scrollRef = (0, _react.useRef)(null);
|
|
63
71
|
const [activeIndex, setActiveIndex] = (0, _react.useState)(0);
|
|
72
|
+
const activeIndexRef = (0, _react.useRef)(0);
|
|
64
73
|
const [containerWidth, setContainerWidth] = (0, _react.useState)(0);
|
|
65
74
|
const autoPlayTimer = (0, _react.useRef)(null);
|
|
66
75
|
const userInteracting = (0, _react.useRef)(false);
|
|
76
|
+
// Guard: suppress handleScroll index updates during programmatic scrollTo
|
|
77
|
+
// animations so intermediate offsets don't flicker activeIndex back.
|
|
78
|
+
const programmaticScroll = (0, _react.useRef)(false);
|
|
79
|
+
const programmaticTimer = (0, _react.useRef)(null);
|
|
80
|
+
(0, _react.useEffect)(() => {
|
|
81
|
+
activeIndexRef.current = activeIndex;
|
|
82
|
+
}, [activeIndex]);
|
|
83
|
+
|
|
84
|
+
// Interactive play/pause state for the Numbered variant.
|
|
85
|
+
const [isPlaying, setIsPlaying] = (0, _react.useState)(autoPlay);
|
|
67
86
|
|
|
68
87
|
// Flatten children so we can count items
|
|
69
88
|
const items = (0, _react.useMemo)(() => _react.default.Children.toArray(children).filter(_react.default.isValidElement), [children]);
|
|
70
89
|
const totalItems = items.length;
|
|
71
90
|
|
|
72
|
-
// Effective item width: provided prop, or full container width minus peek offsets
|
|
91
|
+
// Effective item width: provided prop, or full container width minus peek offsets.
|
|
92
|
+
// For the Numbered variant the items are full-bleed (no peek padding).
|
|
73
93
|
const effectiveItemWidth = (0, _react.useMemo)(() => {
|
|
74
94
|
if (itemWidthProp != null) return itemWidthProp;
|
|
75
95
|
if (containerWidth === 0) return 0;
|
|
96
|
+
if (isNumbered) return containerWidth;
|
|
76
97
|
// Full-width minus peekOffset on each side
|
|
77
98
|
return containerWidth - containerPaddingH * 4;
|
|
78
|
-
}, [itemWidthProp, containerWidth, containerPaddingH]);
|
|
99
|
+
}, [itemWidthProp, containerWidth, containerPaddingH, isNumbered]);
|
|
79
100
|
|
|
80
101
|
// Snap interval = item width + gap
|
|
81
102
|
const snapInterval = effectiveItemWidth + gap;
|
|
@@ -89,6 +110,17 @@ function Carousel({
|
|
|
89
110
|
} else {
|
|
90
111
|
targetIndex = Math.max(0, Math.min(index, totalItems - 1));
|
|
91
112
|
}
|
|
113
|
+
// Lock out handleScroll during the animated scroll so intermediate
|
|
114
|
+
// offsets don't flicker activeIndex back to the old value.
|
|
115
|
+
if (animated) {
|
|
116
|
+
programmaticScroll.current = true;
|
|
117
|
+
if (programmaticTimer.current != null) {
|
|
118
|
+
clearTimeout(programmaticTimer.current);
|
|
119
|
+
}
|
|
120
|
+
programmaticTimer.current = setTimeout(() => {
|
|
121
|
+
programmaticScroll.current = false;
|
|
122
|
+
}, 500);
|
|
123
|
+
}
|
|
92
124
|
scrollRef.current?.scrollTo({
|
|
93
125
|
x: targetIndex * snapInterval,
|
|
94
126
|
animated
|
|
@@ -103,6 +135,9 @@ function Carousel({
|
|
|
103
135
|
// ---- Scroll event handler ----
|
|
104
136
|
const handleScroll = (0, _react.useCallback)(event => {
|
|
105
137
|
if (snapInterval === 0) return;
|
|
138
|
+
// Skip index recalculation during programmatic scrollTo animations
|
|
139
|
+
// to prevent the active indicator from flickering back to the old page.
|
|
140
|
+
if (programmaticScroll.current) return;
|
|
106
141
|
const offsetX = event.nativeEvent.contentOffset.x;
|
|
107
142
|
const newIndex = Math.round(offsetX / snapInterval);
|
|
108
143
|
const clampedIndex = Math.max(0, Math.min(newIndex, totalItems - 1));
|
|
@@ -120,22 +155,19 @@ function Carousel({
|
|
|
120
155
|
}
|
|
121
156
|
}, []);
|
|
122
157
|
const startAutoPlay = (0, _react.useCallback)(() => {
|
|
123
|
-
if (!autoPlay || totalItems <= 1) return;
|
|
158
|
+
if (!autoPlay && !isPlaying || totalItems <= 1) return;
|
|
159
|
+
if (!isPlaying) return;
|
|
124
160
|
clearAutoPlay();
|
|
125
161
|
autoPlayTimer.current = setInterval(() => {
|
|
126
162
|
if (!userInteracting.current) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
});
|
|
133
|
-
onIndexChange?.(next);
|
|
134
|
-
return next;
|
|
135
|
-
});
|
|
163
|
+
const prev = activeIndexRef.current;
|
|
164
|
+
const next = loop ? (prev + 1) % totalItems : Math.min(prev + 1, totalItems - 1);
|
|
165
|
+
// Route through scrollToIndex so the programmaticScroll guard
|
|
166
|
+
// suppresses intermediate handleScroll offsets during the animation.
|
|
167
|
+
scrollToIndex(next, true);
|
|
136
168
|
}
|
|
137
169
|
}, autoPlayInterval);
|
|
138
|
-
}, [autoPlay, totalItems, loop,
|
|
170
|
+
}, [autoPlay, isPlaying, totalItems, loop, autoPlayInterval, clearAutoPlay, scrollToIndex]);
|
|
139
171
|
(0, _react.useEffect)(() => {
|
|
140
172
|
startAutoPlay();
|
|
141
173
|
return clearAutoPlay;
|
|
@@ -146,8 +178,23 @@ function Carousel({
|
|
|
146
178
|
const handleScrollEndDrag = (0, _react.useCallback)(() => {
|
|
147
179
|
userInteracting.current = false;
|
|
148
180
|
// Reset autoplay timer after interaction
|
|
149
|
-
if (autoPlay) startAutoPlay();
|
|
150
|
-
}, [autoPlay, startAutoPlay]);
|
|
181
|
+
if (autoPlay || isPlaying) startAutoPlay();
|
|
182
|
+
}, [autoPlay, isPlaying, startAutoPlay]);
|
|
183
|
+
const finishProgrammaticScroll = (0, _react.useCallback)(event => {
|
|
184
|
+
programmaticScroll.current = false;
|
|
185
|
+
if (programmaticTimer.current != null) {
|
|
186
|
+
clearTimeout(programmaticTimer.current);
|
|
187
|
+
programmaticTimer.current = null;
|
|
188
|
+
}
|
|
189
|
+
if (snapInterval === 0) return;
|
|
190
|
+
const offsetX = event.nativeEvent.contentOffset.x;
|
|
191
|
+
const newIndex = Math.round(offsetX / snapInterval);
|
|
192
|
+
const clampedIndex = Math.max(0, Math.min(newIndex, totalItems - 1));
|
|
193
|
+
if (clampedIndex !== activeIndexRef.current) {
|
|
194
|
+
setActiveIndex(clampedIndex);
|
|
195
|
+
onIndexChange?.(clampedIndex);
|
|
196
|
+
}
|
|
197
|
+
}, [snapInterval, totalItems, onIndexChange]);
|
|
151
198
|
|
|
152
199
|
// ---- Layout ----
|
|
153
200
|
const handleLayout = (0, _react.useCallback)(e => {
|
|
@@ -164,14 +211,37 @@ function Carousel({
|
|
|
164
211
|
goPrev
|
|
165
212
|
}), [modes, activeIndex, totalItems, goTo, goNext, goPrev]);
|
|
166
213
|
|
|
214
|
+
// ---- Toggle handler for Numbered variant ----
|
|
215
|
+
const handleToggleAutoplay = (0, _react.useCallback)(() => {
|
|
216
|
+
setIsPlaying(prev => {
|
|
217
|
+
if (prev) {
|
|
218
|
+
// Pausing
|
|
219
|
+
clearAutoPlay();
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
// Resuming — autoplay will restart via the effect
|
|
223
|
+
return true;
|
|
224
|
+
});
|
|
225
|
+
}, [clearAutoPlay]);
|
|
226
|
+
|
|
227
|
+
// Restart autoplay whenever isPlaying flips to true
|
|
228
|
+
(0, _react.useEffect)(() => {
|
|
229
|
+
if (isPlaying) startAutoPlay();else clearAutoPlay();
|
|
230
|
+
}, [isPlaying, startAutoPlay, clearAutoPlay]);
|
|
231
|
+
|
|
167
232
|
// ---- Render ----
|
|
168
233
|
// Explicit padding props win verbatim; otherwise fall back to the
|
|
169
234
|
// token-derived values (horizontal keeps its legacy ×2 "peek" multiplier).
|
|
170
|
-
|
|
171
|
-
const
|
|
235
|
+
// Numbered variant is full-bleed: zero padding.
|
|
236
|
+
const effectivePaddingH = isNumbered ? 0 : paddingHorizontalProp ?? containerPaddingH;
|
|
237
|
+
const effectivePaddingV = isNumbered ? 0 : paddingVerticalProp ?? containerPaddingV;
|
|
172
238
|
const outerStyle = {
|
|
173
239
|
paddingVertical: effectivePaddingV,
|
|
174
|
-
maxHeight
|
|
240
|
+
maxHeight: isNumbered ? undefined : maxHeight,
|
|
241
|
+
// Numbered needs relative positioning for the floating controls overlay.
|
|
242
|
+
...(isNumbered ? {
|
|
243
|
+
overflow: 'hidden'
|
|
244
|
+
} : {})
|
|
175
245
|
};
|
|
176
246
|
const contentContainerStyle = {
|
|
177
247
|
paddingHorizontal: effectivePaddingH,
|
|
@@ -197,6 +267,7 @@ function Carousel({
|
|
|
197
267
|
scrollEventThrottle: 16,
|
|
198
268
|
onScrollBeginDrag: handleScrollBeginDrag,
|
|
199
269
|
onScrollEndDrag: handleScrollEndDrag,
|
|
270
|
+
onMomentumScrollEnd: finishProgrammaticScroll,
|
|
200
271
|
children: items.map((child, index) => {
|
|
201
272
|
// Strict slot box: width must be honored; never grow or shrink with
|
|
202
273
|
// content, and clip anything that misbehaves (e.g. a child whose
|
|
@@ -233,11 +304,33 @@ function Carousel({
|
|
|
233
304
|
children: childWithModes
|
|
234
305
|
}, index);
|
|
235
306
|
})
|
|
236
|
-
}), showPagination && totalItems > 1 && /*#__PURE__*/(0, _jsxRuntime.jsx)(Pagination, {
|
|
307
|
+
}), !isNumbered && showPagination && totalItems > 1 && /*#__PURE__*/(0, _jsxRuntime.jsx)(Pagination, {
|
|
237
308
|
modes: modes,
|
|
238
309
|
style: {
|
|
239
310
|
marginTop: paginationOffset
|
|
240
311
|
}
|
|
312
|
+
}), isNumbered && showPagination && totalItems > 1 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
313
|
+
style: {
|
|
314
|
+
position: 'absolute',
|
|
315
|
+
bottom: 12,
|
|
316
|
+
left: 0,
|
|
317
|
+
right: 0,
|
|
318
|
+
flexDirection: 'row',
|
|
319
|
+
justifyContent: 'center',
|
|
320
|
+
alignItems: 'center',
|
|
321
|
+
gap: controlGap
|
|
322
|
+
},
|
|
323
|
+
pointerEvents: "box-none",
|
|
324
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_AutoplayControl.default, {
|
|
325
|
+
state: isPlaying ? 'pause' : 'play',
|
|
326
|
+
onPress: handleToggleAutoplay,
|
|
327
|
+
modes: modes
|
|
328
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_NumberPagination.default, {
|
|
329
|
+
activePage: activeIndex + 1,
|
|
330
|
+
totalPages: totalItems,
|
|
331
|
+
onPageChange: page => goTo(page - 1),
|
|
332
|
+
modes: modes
|
|
333
|
+
})]
|
|
241
334
|
})]
|
|
242
335
|
})
|
|
243
336
|
});
|
|
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
|
-
var _react =
|
|
7
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
|
|
10
10
|
var _reactUtils = require("../../utils/react-utils");
|
|
@@ -15,6 +15,7 @@ var _IconButton = _interopRequireDefault(require("../IconButton/IconButton"));
|
|
|
15
15
|
var _Icon = _interopRequireDefault(require("../../icons/Icon"));
|
|
16
16
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
17
17
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
|
+
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
19
|
/**
|
|
19
20
|
* Brand link colour used by the selection-card CTA. The Figma node resolves
|
|
20
21
|
* `button/foreground` to this value, but no reproducible token mode currently
|
|
@@ -111,7 +112,8 @@ function CompareTable({
|
|
|
111
112
|
maxColumns = 4,
|
|
112
113
|
modes = _reactUtils.EMPTY_MODES,
|
|
113
114
|
style,
|
|
114
|
-
disableTruncation
|
|
115
|
+
disableTruncation,
|
|
116
|
+
columnWidth
|
|
115
117
|
}) {
|
|
116
118
|
// --- selection card tokens ------------------------------------------------
|
|
117
119
|
const cardBg = (0, _figmaVariablesResolver.getVariableByName)('selectionCard/background/color', modes) ?? '#ffffff';
|
|
@@ -168,6 +170,70 @@ function CompareTable({
|
|
|
168
170
|
const showAddCard = onAddColumn != null && columnCount < maxColumns;
|
|
169
171
|
/** Grid columns shared by the selection-card row and every table row. */
|
|
170
172
|
const gridColumnCount = columnCount + (showAddCard ? 1 : 0);
|
|
173
|
+
const showCardsRow = columnCount > 0 || showAddCard;
|
|
174
|
+
|
|
175
|
+
// --- fixed-width scroll mode -------------------------------------------
|
|
176
|
+
// `columnWidth` opts the component into scroll mode (see prop doc). In that
|
|
177
|
+
// mode the cards row and each section's table body are independent
|
|
178
|
+
// horizontal ScrollViews; their scroll events are chained so they move in
|
|
179
|
+
// lockstep, and the cards row pins to the top on the vertical axis.
|
|
180
|
+
const useFixedWidth = columnWidth != null;
|
|
181
|
+
/** Total scrollable content width shared by every horizontal surface. */
|
|
182
|
+
const contentWidth = useFixedWidth ? gridColumnCount * columnWidth : 0;
|
|
183
|
+
const cardsScrollRef = (0, _react.useRef)(null);
|
|
184
|
+
const bodyScrollRefs = (0, _react.useRef)(new Map());
|
|
185
|
+
// Stable per-section ref-callback instances so React doesn't detach/reattach
|
|
186
|
+
// on every render (which would churn the map and re-fire scrollTo).
|
|
187
|
+
const bodyRefCallbacks = (0, _react.useRef)(new Map());
|
|
188
|
+
/** Last offset we synced everyone to. Echoed onScroll events reporting this
|
|
189
|
+
* same value are ignored — this is what prevents feedback loops. */
|
|
190
|
+
const lastSyncX = (0, _react.useRef)(0);
|
|
191
|
+
const applySync = (0, _react.useCallback)((x, source) => {
|
|
192
|
+
if (Math.abs(x - lastSyncX.current) < 0.5) return;
|
|
193
|
+
lastSyncX.current = x;
|
|
194
|
+
if (source !== 'cards' && cardsScrollRef.current) {
|
|
195
|
+
cardsScrollRef.current.scrollTo({
|
|
196
|
+
x,
|
|
197
|
+
animated: false
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
bodyScrollRefs.current.forEach((ref, idx) => {
|
|
201
|
+
if (source === 'cards' || idx !== source) {
|
|
202
|
+
ref.scrollTo({
|
|
203
|
+
x,
|
|
204
|
+
animated: false
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}, []);
|
|
209
|
+
const onCardsScroll = (0, _react.useCallback)(e => {
|
|
210
|
+
applySync(e.nativeEvent.contentOffset.x, 'cards');
|
|
211
|
+
}, [applySync]);
|
|
212
|
+
const onBodyScroll = (0, _react.useCallback)(idx => e => {
|
|
213
|
+
applySync(e.nativeEvent.contentOffset.x, idx);
|
|
214
|
+
}, [applySync]);
|
|
215
|
+
const getBodyRefCallback = (0, _react.useCallback)(idx => {
|
|
216
|
+
let cb = bodyRefCallbacks.current.get(idx);
|
|
217
|
+
if (!cb) {
|
|
218
|
+
cb = ref => {
|
|
219
|
+
if (ref) {
|
|
220
|
+
bodyScrollRefs.current.set(idx, ref);
|
|
221
|
+
// Bring a freshly-expanded section's body into sync with
|
|
222
|
+
// the rest of the surfaces.
|
|
223
|
+
if (lastSyncX.current > 0) {
|
|
224
|
+
ref.scrollTo({
|
|
225
|
+
x: lastSyncX.current,
|
|
226
|
+
animated: false
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
bodyScrollRefs.current.delete(idx);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
bodyRefCallbacks.current.set(idx, cb);
|
|
234
|
+
}
|
|
235
|
+
return cb;
|
|
236
|
+
}, []);
|
|
171
237
|
const cardLabelStyle = {
|
|
172
238
|
...NO_WRAP_TEXT,
|
|
173
239
|
color: cardLabelColor,
|
|
@@ -198,8 +264,12 @@ function CompareTable({
|
|
|
198
264
|
fontWeight: toWeight(cellFontWeight)
|
|
199
265
|
};
|
|
200
266
|
const cardStyle = {
|
|
201
|
-
|
|
202
|
-
|
|
267
|
+
...(useFixedWidth ? {
|
|
268
|
+
width: columnWidth
|
|
269
|
+
} : {
|
|
270
|
+
flex: 1,
|
|
271
|
+
minWidth: 0
|
|
272
|
+
}),
|
|
203
273
|
minHeight: 147,
|
|
204
274
|
backgroundColor: cardBg,
|
|
205
275
|
borderRadius: cardRadius,
|
|
@@ -318,6 +388,7 @@ function CompareTable({
|
|
|
318
388
|
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Table.default.Cell, {
|
|
319
389
|
modes: modes,
|
|
320
390
|
flex: 1,
|
|
391
|
+
width: useFixedWidth ? columnWidth : undefined,
|
|
321
392
|
align: "left",
|
|
322
393
|
isLastColumn: isLastCol,
|
|
323
394
|
isLastRow: isLastRow,
|
|
@@ -371,31 +442,126 @@ function CompareTable({
|
|
|
371
442
|
})
|
|
372
443
|
})]
|
|
373
444
|
});
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
445
|
+
|
|
446
|
+
// Render a single section in fixed-width scroll mode. Unlike `renderTable`
|
|
447
|
+
// (used in flex mode), the gray header row is rendered full-width OUTSIDE
|
|
448
|
+
// the body's horizontal ScrollView so it stays put horizontally (it does
|
|
449
|
+
// not slide with the cells) while still scrolling vertically with the table.
|
|
450
|
+
// The body rows live inside a horizontal ScrollView that is scroll-synced
|
|
451
|
+
// with the cards row and every other section's body.
|
|
452
|
+
const renderSection = (section, index) => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_Accordion.default, {
|
|
453
|
+
title: section.title,
|
|
454
|
+
defaultExpanded: section.defaultExpanded ?? index === 0,
|
|
455
|
+
modes: modes,
|
|
456
|
+
children: [section.header != null && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
383
457
|
style: {
|
|
384
|
-
|
|
385
|
-
|
|
458
|
+
width: '100%',
|
|
459
|
+
backgroundColor: headerBg,
|
|
460
|
+
paddingHorizontal: headerPaddingH,
|
|
461
|
+
paddingVertical: headerPaddingV,
|
|
462
|
+
borderBottomWidth: cellBorderSize,
|
|
463
|
+
borderBottomColor: cellBorderColor
|
|
386
464
|
},
|
|
387
|
-
children:
|
|
388
|
-
|
|
465
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
466
|
+
style: headerTextStyle,
|
|
467
|
+
children: section.header
|
|
468
|
+
})
|
|
469
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
470
|
+
ref: getBodyRefCallback(index),
|
|
471
|
+
horizontal: true,
|
|
472
|
+
showsHorizontalScrollIndicator: false,
|
|
473
|
+
scrollEventThrottle: 16,
|
|
474
|
+
onScroll: onBodyScroll(index),
|
|
475
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
476
|
+
style: {
|
|
477
|
+
width: contentWidth
|
|
478
|
+
},
|
|
479
|
+
children: section.rows.map((row, rowIndex) => {
|
|
480
|
+
const isLastRow = rowIndex === section.rows.length - 1;
|
|
481
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
482
|
+
style: {
|
|
483
|
+
flexDirection: 'row',
|
|
484
|
+
width: '100%'
|
|
485
|
+
},
|
|
486
|
+
children: [Array.from({
|
|
487
|
+
length: columnCount
|
|
488
|
+
}).map((_, colIndex) => renderTableCell(renderCellContent(row.values?.[colIndex], `${rowIndex}-${colIndex}`), colIndex, isLastRow, colIndex)), showAddCard && renderTableCell(null, gridColumnCount - 1, isLastRow, '__add_spacer__')]
|
|
489
|
+
}, row.key ?? rowIndex);
|
|
490
|
+
})
|
|
491
|
+
})
|
|
492
|
+
})]
|
|
493
|
+
}, section.key ?? section.title ?? index);
|
|
494
|
+
const outerStyle = {
|
|
495
|
+
width: '100%',
|
|
496
|
+
backgroundColor: cardBg,
|
|
497
|
+
borderWidth: 1,
|
|
498
|
+
borderColor: '#e8e8e8',
|
|
499
|
+
overflow: 'hidden'
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
// --- flex mode (columnWidth omitted): original behaviour, unchanged -------
|
|
503
|
+
if (!useFixedWidth) {
|
|
504
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
505
|
+
style: [outerStyle, style],
|
|
506
|
+
children: [showCardsRow && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
507
|
+
style: {
|
|
508
|
+
flexDirection: 'row',
|
|
509
|
+
width: '100%'
|
|
510
|
+
},
|
|
511
|
+
children: [columns.map(renderCard), showAddCard && renderAddCard()]
|
|
512
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
513
|
+
style: {
|
|
514
|
+
width: '100%'
|
|
515
|
+
},
|
|
516
|
+
children: sections.map((section, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_Accordion.default, {
|
|
517
|
+
title: section.title,
|
|
518
|
+
defaultExpanded: section.defaultExpanded ?? index === 0,
|
|
519
|
+
modes: modes,
|
|
520
|
+
children: renderTable(section)
|
|
521
|
+
}, section.key ?? section.title ?? index))
|
|
522
|
+
})]
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// --- fixed-width scroll mode: chained horizontal sync + sticky cards -----
|
|
527
|
+
// The cards row (index 0) is a sticky header of the vertical ScrollView so
|
|
528
|
+
// it pins to the top while the sections scroll beneath. The cards row is
|
|
529
|
+
// itself a horizontal ScrollView, synced with every section body. For the
|
|
530
|
+
// vertical scroll (and thus the sticky behaviour) to engage, the host must
|
|
531
|
+
// give CompareTable a bounded height (e.g. a sized container or flex:1).
|
|
532
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
533
|
+
style: [outerStyle, style, {
|
|
534
|
+
flexDirection: 'column',
|
|
535
|
+
flex: 1
|
|
536
|
+
}],
|
|
537
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.ScrollView, {
|
|
389
538
|
style: {
|
|
390
|
-
width: '100%'
|
|
539
|
+
width: '100%',
|
|
540
|
+
flex: 1
|
|
391
541
|
},
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
542
|
+
stickyHeaderIndices: showCardsRow ? [0] : [],
|
|
543
|
+
showsVerticalScrollIndicator: false,
|
|
544
|
+
directionalLockEnabled: true,
|
|
545
|
+
children: [showCardsRow && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
|
|
546
|
+
ref: cardsScrollRef,
|
|
547
|
+
horizontal: true,
|
|
548
|
+
showsHorizontalScrollIndicator: false,
|
|
549
|
+
scrollEventThrottle: 16,
|
|
550
|
+
onScroll: onCardsScroll,
|
|
551
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
552
|
+
style: {
|
|
553
|
+
flexDirection: 'row',
|
|
554
|
+
width: contentWidth
|
|
555
|
+
},
|
|
556
|
+
children: [columns.map(renderCard), showAddCard && renderAddCard()]
|
|
557
|
+
})
|
|
558
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
559
|
+
style: {
|
|
560
|
+
width: '100%'
|
|
561
|
+
},
|
|
562
|
+
children: sections.map(renderSection)
|
|
563
|
+
})]
|
|
564
|
+
})
|
|
399
565
|
});
|
|
400
566
|
}
|
|
401
567
|
var _default = exports.default = CompareTable;
|