jfs-components 0.1.23 → 0.1.28

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 (99) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/lib/commonjs/components/AutoplayControl/AutoplayControl.js +66 -0
  3. package/lib/commonjs/components/CardCTA/CardCTA.js +32 -25
  4. package/lib/commonjs/components/Carousel/Carousel.js +112 -19
  5. package/lib/commonjs/components/ChipSelect/ChipSelect.js +2 -1
  6. package/lib/commonjs/components/ContentSheet/ContentSheet.js +39 -5
  7. package/lib/commonjs/components/FormUpload/Additem.js +168 -0
  8. package/lib/commonjs/components/FormUpload/FormUpload.js +198 -0
  9. package/lib/commonjs/components/Grid/Grid.js +206 -0
  10. package/lib/commonjs/components/ListItem/ListItem.js +11 -4
  11. package/lib/commonjs/components/MetricData/MetricData.js +132 -0
  12. package/lib/commonjs/components/NumberPagination/NumberPagination.js +144 -0
  13. package/lib/commonjs/components/ProfileCard/ProfileCard.js +208 -0
  14. package/lib/commonjs/components/Rating/Rating.js +137 -0
  15. package/lib/commonjs/components/UpiHandle/UpiHandle.js +23 -11
  16. package/lib/commonjs/components/index.js +42 -0
  17. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  18. package/lib/commonjs/design-tokens/figma-modes.generated.js +23 -10
  19. package/lib/commonjs/icons/components/IconBrokenimage.js +19 -0
  20. package/lib/commonjs/icons/components/IconImage.js +19 -0
  21. package/lib/commonjs/icons/components/IconMic.js +19 -0
  22. package/lib/commonjs/icons/components/IconMicoff.js +19 -0
  23. package/lib/commonjs/icons/components/IconPause.js +19 -0
  24. package/lib/commonjs/icons/components/IconPlay.js +19 -0
  25. package/lib/commonjs/icons/components/index.js +66 -0
  26. package/lib/commonjs/icons/registry.js +2 -2
  27. package/lib/module/components/AutoplayControl/AutoplayControl.js +60 -0
  28. package/lib/module/components/CardCTA/CardCTA.js +32 -25
  29. package/lib/module/components/Carousel/Carousel.js +111 -19
  30. package/lib/module/components/ChipSelect/ChipSelect.js +2 -1
  31. package/lib/module/components/ContentSheet/ContentSheet.js +41 -7
  32. package/lib/module/components/FormUpload/Additem.js +162 -0
  33. package/lib/module/components/FormUpload/FormUpload.js +192 -0
  34. package/lib/module/components/Grid/Grid.js +201 -0
  35. package/lib/module/components/ListItem/ListItem.js +11 -4
  36. package/lib/module/components/MetricData/MetricData.js +127 -0
  37. package/lib/module/components/NumberPagination/NumberPagination.js +138 -0
  38. package/lib/module/components/ProfileCard/ProfileCard.js +202 -0
  39. package/lib/module/components/Rating/Rating.js +132 -0
  40. package/lib/module/components/UpiHandle/UpiHandle.js +23 -11
  41. package/lib/module/components/index.js +7 -1
  42. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  43. package/lib/module/design-tokens/figma-modes.generated.js +23 -10
  44. package/lib/module/icons/components/IconBrokenimage.js +12 -0
  45. package/lib/module/icons/components/IconImage.js +12 -0
  46. package/lib/module/icons/components/IconMic.js +12 -0
  47. package/lib/module/icons/components/IconMicoff.js +12 -0
  48. package/lib/module/icons/components/IconPause.js +12 -0
  49. package/lib/module/icons/components/IconPlay.js +12 -0
  50. package/lib/module/icons/components/index.js +6 -0
  51. package/lib/module/icons/registry.js +2 -2
  52. package/lib/typescript/src/components/AutoplayControl/AutoplayControl.d.ts +15 -0
  53. package/lib/typescript/src/components/Carousel/Carousel.d.ts +8 -1
  54. package/lib/typescript/src/components/ChipSelect/ChipSelect.d.ts +6 -1
  55. package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +7 -1
  56. package/lib/typescript/src/components/FormUpload/Additem.d.ts +96 -0
  57. package/lib/typescript/src/components/FormUpload/FormUpload.d.ts +85 -0
  58. package/lib/typescript/src/components/Grid/Grid.d.ts +87 -0
  59. package/lib/typescript/src/components/MetricData/MetricData.d.ts +53 -0
  60. package/lib/typescript/src/components/NumberPagination/NumberPagination.d.ts +21 -0
  61. package/lib/typescript/src/components/ProfileCard/ProfileCard.d.ts +89 -0
  62. package/lib/typescript/src/components/Rating/Rating.d.ts +30 -0
  63. package/lib/typescript/src/components/UpiHandle/UpiHandle.d.ts +7 -1
  64. package/lib/typescript/src/components/index.d.ts +34 -28
  65. package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +10 -4
  66. package/lib/typescript/src/icons/components/IconBrokenimage.d.ts +3 -0
  67. package/lib/typescript/src/icons/components/IconImage.d.ts +3 -0
  68. package/lib/typescript/src/icons/components/IconMic.d.ts +3 -0
  69. package/lib/typescript/src/icons/components/IconMicoff.d.ts +3 -0
  70. package/lib/typescript/src/icons/components/IconPause.d.ts +3 -0
  71. package/lib/typescript/src/icons/components/IconPlay.d.ts +3 -0
  72. package/lib/typescript/src/icons/components/index.d.ts +6 -0
  73. package/lib/typescript/src/icons/registry.d.ts +1 -1
  74. package/package.json +1 -1
  75. package/src/components/AutoplayControl/AutoplayControl.tsx +77 -0
  76. package/src/components/CardCTA/CardCTA.tsx +30 -15
  77. package/src/components/Carousel/Carousel.tsx +133 -23
  78. package/src/components/ChipSelect/ChipSelect.tsx +7 -1
  79. package/src/components/ContentSheet/ContentSheet.tsx +50 -0
  80. package/src/components/FormUpload/Additem.tsx +262 -0
  81. package/src/components/FormUpload/FormUpload.tsx +313 -0
  82. package/src/components/Grid/Grid.tsx +368 -0
  83. package/src/components/ListItem/ListItem.tsx +11 -4
  84. package/src/components/MetricData/MetricData.tsx +185 -0
  85. package/src/components/NumberPagination/NumberPagination.tsx +170 -0
  86. package/src/components/ProfileCard/ProfileCard.tsx +268 -0
  87. package/src/components/Rating/Rating.tsx +174 -0
  88. package/src/components/UpiHandle/UpiHandle.tsx +35 -24
  89. package/src/components/index.ts +329 -182
  90. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  91. package/src/design-tokens/figma-modes.generated.ts +23 -10
  92. package/src/icons/components/IconBrokenimage.tsx +11 -0
  93. package/src/icons/components/IconImage.tsx +11 -0
  94. package/src/icons/components/IconMic.tsx +11 -0
  95. package/src/icons/components/IconMicoff.tsx +11 -0
  96. package/src/icons/components/IconPause.tsx +11 -0
  97. package/src/icons/components/IconPlay.tsx +11 -0
  98. package/src/icons/components/index.ts +6 -0
  99. package/src/icons/registry.ts +25 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,46 @@ 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.28] - 2026-07-07
8
+
9
+ - 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.
10
+ - 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).
11
+ - 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.
12
+ - `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.
13
+ - Added `ic_broken_image`, `ic_image`, `ic_mic`, and `ic_mic_off` icons to the icon registry.
14
+ - Regenerated component MDX token documentation and `.token-metadata.json` across the library.
15
+
16
+ ---
17
+
18
+ ## [0.1.27] - 2026-07-07
19
+
20
+ - Added `ProfileCard` — dark rounded profile summary card with `Attached` monogram `Avatar`, optional badge, name + `UpiHandle` pill row, and a body slot; `Profile Card Appearance` mode (`Default` | `Premium`) recolors the whole card via `modes`.
21
+ - `Carousel` — new `type="Numbered"` variant: full-bleed slides with floating `AutoplayControl` + `NumberPagination` overlay at the bottom center; interactive play/pause state wired to autoplay.
22
+ - `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.
23
+ - `UpiHandle` — **Fix:** avatar image visibility now follows the presence of `source` / `avatarSource` (`showImage` no longer renders an empty avatar slot when no source is provided); `UPI Handle Image` mode is force-set from the resolved source so token layout stays in sync.
24
+ - Regenerated component MDX token documentation and `.token-metadata.json` across the library.
25
+
26
+ ---
27
+
28
+ ## [0.1.26] - 2026-07-07
29
+
30
+ - Added `AutoplayControl` — token-driven play/pause toggle button for carousel autoplay (`autoplayControl/*` tokens; `ic_play` / `ic_pause` icons).
31
+ - Added `NumberPagination` — frosted-glass page-number strip with active/inactive control states (`numberPagination/*`, `controlNumber/*`, `control/*` tokens); supports `children` slot override.
32
+ - Added `ic_play` and `ic_pause` icons to the icon registry.
33
+ - Regenerated component MDX token documentation and `.token-metadata.json` across the library.
34
+
35
+ ---
36
+
37
+ ## [0.1.25] - 2026-07-03
38
+
39
+ - 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`).
40
+ - 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.
41
+ - `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.
42
+ - `ChipSelect` — new `showCloseIcon` prop (default `true`); set `showCloseIcon={false}` to hide the close icon in the active state.
43
+ - `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.
44
+
45
+ ---
46
+
7
47
  ## [0.1.23] - 2026-07-01
8
48
 
9
49
  - 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
+ }
@@ -230,22 +230,39 @@ function CardCTA({
230
230
  zIndex: 1
231
231
  };
232
232
 
233
- // NOTE: rightWrap must NOT shrink on native. On Android (Yoga), the default
234
- // `flex: 2` shorthand expands to `{ flexGrow: 2, flexShrink: 1, flexBasis: 0 }`,
235
- // which combined with `minWidth: 0` lets Yoga shrink this wrapper below
236
- // its own padding+IconCapsule width when leftWrap's text is long. Once that
237
- // happens the horizontal padding collapses, `alignItems: 'flex-end'` pins
238
- // the IconCapsule against the inner edge, and the icon ends up visually
239
- // touching the body text (the wrapper "appears not to exist"). Web hides
240
- // this because browsers honor `min-width: auto` on flex items. Use
241
- // explicit `flexGrow`/`flexShrink: 0`/`flexBasis: 'auto'` so the wrapper
242
- // is sized to its content as a floor and only grows for the design's
243
- // 3:2 ratio when extra space is available. leftWrap already absorbs tight
244
- // space via `flexShrink: 1` + `minWidth: 0`.
233
+ // NOTE: rightWrap must implement the design's TRUE `2fr` column.
234
+ //
235
+ // History of this styletwo 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: 'auto',
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
  });
@@ -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
- setActiveIndex(prev => {
128
- const next = loop ? (prev + 1) % totalItems : Math.min(prev + 1, totalItems - 1);
129
- scrollRef.current?.scrollTo({
130
- x: next * snapInterval,
131
- animated: true
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, snapInterval, autoPlayInterval, clearAutoPlay, onIndexChange]);
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
- const effectivePaddingH = paddingHorizontalProp ?? containerPaddingH;
171
- const effectivePaddingV = paddingVerticalProp ?? containerPaddingV;
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
  });
@@ -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
@@ -21,6 +21,18 @@ function resolveContentSheetStyle(modes) {
21
21
  const paddingBottom = (0, _figmaVariablesResolver.getVariableByName)('contentSheet/padding/bottom', modes);
22
22
  const radiusTop = (0, _figmaVariablesResolver.getVariableByName)('contentSheet/padding/radius/top', modes);
23
23
  const radiusBottom = (0, _figmaVariablesResolver.getVariableByName)('contentSheet/padding/radius/bottom', modes);
24
+
25
+ // Title tokens mirror the Drawer title so the two bottom-sheet surfaces stay
26
+ // visually aligned.
27
+ const titleColor = (0, _figmaVariablesResolver.getVariableByName)('drawer/title/color', modes);
28
+ const titleSize = (0, _figmaVariablesResolver.getVariableByName)('drawer/title/fontSize', modes);
29
+ const titleLineHeight = (0, _figmaVariablesResolver.getVariableByName)('drawer/title/lineHeight', modes);
30
+ const titleFontFamily = (0, _figmaVariablesResolver.getVariableByName)('drawer/title/fontFamily', modes);
31
+ const titleFontWeightRaw = (0, _figmaVariablesResolver.getVariableByName)('drawer/title/fontWeight', modes);
32
+ const titleFontWeight = typeof titleFontWeightRaw === 'number' ? titleFontWeightRaw.toString() : titleFontWeightRaw;
33
+ const titleWrapPaddingTop = (0, _figmaVariablesResolver.getVariableByName)('drawer/titleWrap/padding/top', modes);
34
+ const titleWrapPaddingBottom = (0, _figmaVariablesResolver.getVariableByName)('drawer/titleWrap/padding/bottom', modes);
35
+ const titleWrapPaddingHorizontal = (0, _figmaVariablesResolver.getVariableByName)('drawer/titleWrap/padding/horizontal', modes);
24
36
  return {
25
37
  container: {
26
38
  backgroundColor,
@@ -36,7 +48,21 @@ function resolveContentSheetStyle(modes) {
36
48
  alignItems: 'stretch',
37
49
  overflow: 'hidden'
38
50
  },
39
- paddingBottom
51
+ paddingBottom,
52
+ headerStyle: {
53
+ paddingTop: titleWrapPaddingTop,
54
+ paddingBottom: titleWrapPaddingBottom,
55
+ paddingHorizontal: titleWrapPaddingHorizontal,
56
+ alignItems: 'center'
57
+ },
58
+ titleStyle: {
59
+ color: titleColor,
60
+ fontSize: titleSize,
61
+ lineHeight: titleLineHeight,
62
+ fontFamily: titleFontFamily,
63
+ fontWeight: titleFontWeight,
64
+ textAlign: 'center'
65
+ }
40
66
  };
41
67
  }
42
68
  const pinnedStyle = {
@@ -65,6 +91,7 @@ const pinnedStyle = {
65
91
  function ContentSheet({
66
92
  children,
67
93
  modes = _reactUtils.EMPTY_MODES,
94
+ title,
68
95
  avoidKeyboard = true,
69
96
  keyboardSpacing = 0,
70
97
  safeAreaBottom = true,
@@ -88,18 +115,25 @@ function ContentSheet({
88
115
  // `avoidKeyboard` (and platform). Because they are distinct component types,
89
116
  // React remounts on toggle, so the conditional `useAnimatedKeyboard` hook
90
117
  // inside `KeyboardAwareSheet` always runs in a stable hook order.
118
+ const header = title ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
119
+ style: resolved.headerStyle,
120
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
121
+ style: resolved.titleStyle,
122
+ children: title
123
+ })
124
+ }) : null;
91
125
  if (avoidKeyboard && !IS_WEB) {
92
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(KeyboardAwareSheet, {
126
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(KeyboardAwareSheet, {
93
127
  style: containerStyle,
94
128
  spacing: keyboardSpacing,
95
129
  ...rest,
96
- children: processedChildren
130
+ children: [header, processedChildren]
97
131
  });
98
132
  }
99
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
133
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
100
134
  style: containerStyle,
101
135
  ...rest,
102
- children: processedChildren
136
+ children: [header, processedChildren]
103
137
  });
104
138
  }
105
139
  /**