jfs-components 0.0.56 → 0.0.58

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 (26) hide show
  1. package/lib/commonjs/components/ActionFooter/ActionFooter.js +20 -14
  2. package/lib/commonjs/components/ButtonGroup/ButtonGroup.js +10 -7
  3. package/lib/commonjs/components/ListGroup/ListGroup.js +1 -1
  4. package/lib/commonjs/components/Section/Section.js +71 -8
  5. package/lib/commonjs/components/Tabs/Tabs.js +6 -3
  6. package/lib/commonjs/components/TransactionBubble/TransactionBubble.js +24 -20
  7. package/lib/commonjs/icons/registry.js +1 -1
  8. package/lib/module/components/ActionFooter/ActionFooter.js +21 -15
  9. package/lib/module/components/ButtonGroup/ButtonGroup.js +10 -7
  10. package/lib/module/components/ListGroup/ListGroup.js +1 -1
  11. package/lib/module/components/Section/Section.js +73 -9
  12. package/lib/module/components/Tabs/Tabs.js +6 -3
  13. package/lib/module/components/TransactionBubble/TransactionBubble.js +24 -20
  14. package/lib/module/icons/registry.js +1 -1
  15. package/lib/typescript/src/components/ActionFooter/ActionFooter.d.ts +4 -10
  16. package/lib/typescript/src/components/ButtonGroup/ButtonGroup.d.ts +3 -1
  17. package/lib/typescript/src/components/TransactionBubble/TransactionBubble.d.ts +9 -6
  18. package/lib/typescript/src/icons/registry.d.ts +1 -1
  19. package/package.json +1 -1
  20. package/src/components/ActionFooter/ActionFooter.tsx +20 -14
  21. package/src/components/ButtonGroup/ButtonGroup.tsx +16 -10
  22. package/src/components/ListGroup/ListGroup.tsx +1 -1
  23. package/src/components/Section/Section.tsx +90 -8
  24. package/src/components/Tabs/Tabs.tsx +2 -2
  25. package/src/components/TransactionBubble/TransactionBubble.tsx +17 -14
  26. package/src/icons/registry.ts +1 -1
@@ -3,7 +3,8 @@
3
3
  import React from 'react';
4
4
  import { View, Platform } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
- import { cloneChildrenWithModes } from '../../utils/react-utils';
6
+ import { cloneChildrenWithModes, flattenChildren } from '../../utils/react-utils';
7
+ import IconButton from '../IconButton/IconButton';
7
8
  import { jsx as _jsx } from "react/jsx-runtime";
8
9
  /**
9
10
  * ActionFooter component that provides a fixed footer container for action buttons.
@@ -23,18 +24,12 @@ import { jsx as _jsx } from "react/jsx-runtime";
23
24
  *
24
25
  * @example
25
26
  * ```tsx
26
- * // Basic usage - modes are automatically passed to all children
27
+ * // Basic usage - modes are automatically passed to all children.
28
+ * // Non-IconButton children (e.g., Button) are auto-stretched to fill.
27
29
  * <ActionFooter modes={modes}>
28
30
  * <IconButton iconName="ic_split" />
29
- * <Button label="Request" style={{ flex: 1 }} />
30
- * <Button label="Pay" style={{ flex: 1 }} />
31
- * </ActionFooter>
32
- *
33
- * // Children can override with their own modes (merged with parent)
34
- * <ActionFooter modes={modes}>
35
- * <IconButton iconName="ic_split" />
36
- * <Button label="Request" modes={{ Appearance: 'secondary' }} style={{ flex: 1 }} />
37
- * <Button label="Pay" modes={{ Appearance: 'primary' }} style={{ flex: 1 }} />
31
+ * <Button label="Request" />
32
+ * <Button label="Pay" />
38
33
  * </ActionFooter>
39
34
  * ```
40
35
  */
@@ -90,16 +85,27 @@ function ActionFooter({
90
85
  const webShadow = Platform.OS === 'web' ? {
91
86
  boxShadow: '0px -12px 24px 0px rgba(12, 13, 16, 0.12), 0px -16px 48px 0px rgba(12, 13, 16, 0.16)'
92
87
  } : {};
93
-
94
- // Pass modes prop to all slot children
95
- const childrenWithModes = cloneChildrenWithModes(children, modes);
88
+ const flatChildren = flattenChildren(children);
89
+ const processedChildren = cloneChildrenWithModes(flatChildren, modes);
90
+ const enhancedChildren = processedChildren.map((child, index) => {
91
+ if (! /*#__PURE__*/React.isValidElement(child)) return child;
92
+ const element = child;
93
+ const isIconButton = element.type === IconButton;
94
+ const stretchStyle = isIconButton ? undefined : {
95
+ flex: 1
96
+ };
97
+ return /*#__PURE__*/React.cloneElement(element, {
98
+ key: element.key ?? index,
99
+ style: [stretchStyle, element.props.style]
100
+ });
101
+ });
96
102
  return /*#__PURE__*/_jsx(View, {
97
103
  style: [containerStyle, webShadow, style],
98
104
  accessibilityRole: "toolbar",
99
105
  accessibilityLabel: undefined,
100
106
  children: /*#__PURE__*/_jsx(View, {
101
107
  style: slotStyle,
102
- children: childrenWithModes
108
+ children: enhancedChildren
103
109
  })
104
110
  });
105
111
  }
@@ -4,19 +4,22 @@ import React from 'react';
4
4
  import { View } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import { flattenChildren } from '../../utils/react-utils';
7
+ import IconButton from '../IconButton/IconButton';
7
8
  import { jsx as _jsx } from "react/jsx-runtime";
8
9
  /**
9
10
  * ButtonGroup component that aggregates multiple buttons (e.g., IconButton)
10
11
  * and handles their layout and styling/theming via design tokens.
11
12
  *
12
13
  * It passes the provided `modes` to all its immediate React Element children.
14
+ * Non-IconButton children (e.g., Button) are automatically stretched to fill
15
+ * available space. IconButton children keep their natural width.
13
16
  *
14
17
  * @component
15
18
  * @example
16
19
  * ```jsx
17
20
  * <ButtonGroup modes={{"Appearance": "light"}}>
18
21
  * <IconButton iconName="ic_qr_code" />
19
- * <IconButton iconName="ic_photo" />
22
+ * <Button label="Pay" />
20
23
  * </ButtonGroup>
21
24
  * ```
22
25
  */
@@ -41,21 +44,21 @@ function ButtonGroup({
41
44
 
42
45
  // Flatten children to handle Fragments properly
43
46
  const flatChildren = flattenChildren(children);
44
-
45
- // Clone children to pass `modes` prop
46
47
  const childrenWithModes = React.Children.map(flatChildren, child => {
47
48
  if (/*#__PURE__*/React.isValidElement(child)) {
48
- // We safely try to pass `modes` to the child.
49
- // We merge the group modes with any modes defined on the child explicitly.
50
- // Child modes take precedence.
51
49
  const element = child;
52
50
  const childModes = element.props.modes || {};
53
51
  const mergedModes = {
54
52
  ...modes,
55
53
  ...childModes
56
54
  };
55
+ const isIconButton = element.type === IconButton;
56
+ const stretchStyle = isIconButton ? undefined : {
57
+ flex: 1
58
+ };
57
59
  return /*#__PURE__*/React.cloneElement(element, {
58
- modes: mergedModes
60
+ modes: mergedModes,
61
+ style: [stretchStyle, element.props.style]
59
62
  });
60
63
  }
61
64
  return child;
@@ -64,7 +64,7 @@ function ListGroup({
64
64
  borderWidth,
65
65
  borderStyle: 'solid',
66
66
  flexDirection: 'column',
67
- alignItems: 'flex-start',
67
+ alignItems: 'stretch',
68
68
  width: '100%'
69
69
  };
70
70
  const labelStyle = {
@@ -1,12 +1,76 @@
1
1
  "use strict";
2
2
 
3
- import React, { useState } from 'react';
3
+ import React, { useState, useRef, useCallback, useEffect } from 'react';
4
4
  import { View, Text, Pressable, Platform } from 'react-native';
5
5
  import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
6
6
  import NavArrow from '../NavArrow/NavArrow';
7
7
  import { usePressableWebSupport } from '../../utils/web-platform-utils';
8
8
  import { cloneChildrenWithModes, flattenChildren } from '../../utils/react-utils';
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Shared grid layout: measures widest child, enforces uniform width,
12
+ // chunks into fixed rows of up to maxColumns, space-between per row.
13
+ // ---------------------------------------------------------------------------
9
14
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
15
+ const SLOT_GRID_MAX_COLUMNS = 4;
16
+ function SlotGrid({
17
+ items,
18
+ gap,
19
+ maxColumns = SLOT_GRID_MAX_COLUMNS
20
+ }) {
21
+ const [maxItemWidth, setMaxItemWidth] = useState(null);
22
+ const itemWidthsRef = useRef(new Map());
23
+ const totalItems = items.length;
24
+ useEffect(() => {
25
+ itemWidthsRef.current.clear();
26
+ setMaxItemWidth(null);
27
+ }, [totalItems]);
28
+ const handleItemLayout = useCallback((index, width) => {
29
+ itemWidthsRef.current.set(index, width);
30
+ if (itemWidthsRef.current.size >= totalItems && totalItems > 0) {
31
+ setMaxItemWidth(Math.max(...itemWidthsRef.current.values()));
32
+ }
33
+ }, [totalItems]);
34
+ const isMeasured = maxItemWidth !== null;
35
+ const columns = Math.min(maxColumns, totalItems || 1);
36
+ const rows = [];
37
+ for (let i = 0; i < items.length; i += columns) {
38
+ rows.push(items.slice(i, i + columns));
39
+ }
40
+ return /*#__PURE__*/_jsx(View, {
41
+ style: {
42
+ gap,
43
+ ...(isMeasured ? {} : {
44
+ opacity: 0
45
+ })
46
+ },
47
+ children: rows.map((row, rowIndex) => {
48
+ const spacersNeeded = row.length < columns ? columns - row.length : 0;
49
+ return /*#__PURE__*/_jsxs(View, {
50
+ style: {
51
+ flexDirection: 'row',
52
+ justifyContent: 'space-between'
53
+ },
54
+ children: [row.map((child, colIndex) => {
55
+ const itemIndex = rowIndex * columns + colIndex;
56
+ return /*#__PURE__*/_jsx(View, {
57
+ onLayout: !isMeasured ? e => handleItemLayout(itemIndex, e.nativeEvent.layout.width) : undefined,
58
+ style: isMeasured ? {
59
+ width: maxItemWidth
60
+ } : undefined,
61
+ children: child
62
+ }, itemIndex);
63
+ }), isMeasured && spacersNeeded > 0 && Array.from({
64
+ length: spacersNeeded
65
+ }, (_, i) => /*#__PURE__*/_jsx(View, {
66
+ style: {
67
+ width: maxItemWidth
68
+ }
69
+ }, `spacer-${i}`))]
70
+ }, rowIndex);
71
+ })
72
+ });
73
+ }
10
74
  /**
11
75
  * Section component that mirrors the Figma "Section" component.
12
76
  *
@@ -196,9 +260,12 @@ function Section({
196
260
  }) : /*#__PURE__*/_jsx(View, {
197
261
  style: headerStyle,
198
262
  children: headerContent
199
- }), slot && /*#__PURE__*/_jsx(View, {
263
+ }), slot && slotDirection === 'row' && /*#__PURE__*/_jsx(SlotGrid, {
264
+ items: cloneChildrenWithModes(flattenChildren(slot), modes),
265
+ gap: sectionGap
266
+ }), slot && slotDirection === 'column' && /*#__PURE__*/_jsx(View, {
200
267
  style: {
201
- flexDirection: slotDirection,
268
+ flexDirection: 'column',
202
269
  gap: slotGap
203
270
  },
204
271
  children: cloneChildrenWithModes(flattenChildren(slot), modes)
@@ -259,12 +326,9 @@ function SectionBento({
259
326
  accessibilityLabel: undefined,
260
327
  accessibilityHint: accessibilityHint,
261
328
  ...rest,
262
- children: [processedNavSlot && /*#__PURE__*/_jsx(View, {
263
- style: {
264
- flexDirection: 'row',
265
- gap
266
- },
267
- children: processedNavSlot
329
+ children: [processedNavSlot && /*#__PURE__*/_jsx(SlotGrid, {
330
+ items: processedNavSlot,
331
+ gap: gap
268
332
  }), processedUpiSlot && /*#__PURE__*/_jsx(View, {
269
333
  style: {
270
334
  flexDirection: 'row',
@@ -37,8 +37,6 @@ function Tabs({
37
37
  const paddingBottom = getVariableByName('tabs/padding/bottom', modes) ?? 0;
38
38
  const paddingLeft = getVariableByName('tabs/padding/left', modes) ?? 0;
39
39
  const paddingRight = getVariableByName('tabs/padding/right', modes) ?? 0;
40
-
41
- // Forward modes to all TabItem children
42
40
  const enhancedChildren = React.Children.map(children, child => {
43
41
  if (/*#__PURE__*/React.isValidElement(child) && child.type === TabItem) {
44
42
  const childElement = child;
@@ -46,7 +44,12 @@ function Tabs({
46
44
  modes: {
47
45
  ...modes,
48
46
  ...(childElement.props.modes ?? {})
49
- }
47
+ },
48
+ ...(scrollable ? {} : {
49
+ style: [{
50
+ flex: 1
51
+ }, childElement.props.style]
52
+ })
50
53
  });
51
54
  }
52
55
  return child;
@@ -12,16 +12,19 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
12
  /**
13
13
  * TransactionBubble — Figma node 1517:1155.
14
14
  *
15
- * Layout (single horizontal row inside a rounded bordered pill):
15
+ * Layout (vertical stack inside a rounded bordered pill):
16
16
  *
17
17
  * ┌──────────────────────────────────────────────┐
18
- * │ Description Status · Date
19
- * │ ₹56
18
+ * │ Description
19
+ * │ ₹56
20
+ * │ [slot / children content] │
21
+ * │ ⚠ Expired · 20 Mar 2025 › │
20
22
  * └──────────────────────────────────────────────┘
21
23
  *
22
- * Left column: description text + MoneyValue, stacked vertically with `transactionBubble/gap`.
23
- * Right column: TransactionStatus + NavArrow, stacked vertically with `transactionBubble/statusWrap/gap`,
24
- * right-aligned.
24
+ * moneyValueWrap: description + MoneyValue, vertical with `transactionBubble/wrap/gap`.
25
+ * slotWrap: children (optional), rendered between moneyValueWrap and statusWrap.
26
+ * statusWrap: TransactionStatus + NavArrow, horizontal row with `transactionBubble/statusWrap/gap`.
27
+ * Container gap between sections: `transactionBubble/gap`.
25
28
  */
26
29
  function TransactionBubble({
27
30
  description = 'Payment to Uber India',
@@ -49,12 +52,13 @@ function TransactionBubble({
49
52
  const backgroundColor = getVariableByName('transactionBubble/background', resolvedModes) || '#ffffff';
50
53
  const borderColor = getVariableByName('transactionBubble/border/color', resolvedModes) || '#e5e5e5';
51
54
  const bubbleGap = Number(getVariableByName('transactionBubble/gap', resolvedModes)) || 8;
52
- const wrapGap = Number(getVariableByName('transactionBubble/wrap/gap', resolvedModes)) || 8;
55
+ const moneyValueWrapGap = Number(getVariableByName('transactionBubble/wrap/gap', resolvedModes)) || 8;
53
56
  const descriptionColor = getVariableByName('transactionBubble/description/color', resolvedModes) || '#24262b';
54
57
  const descriptionFontSize = Number(getVariableByName('transactionBubble/description/fontSize', resolvedModes)) || 14;
55
58
  const descriptionLineHeight = Number(getVariableByName('transactionBubble/description/lineHeight', resolvedModes)) || 17;
56
59
  const descriptionFontFamily = getVariableByName('transactionBubble/description/fontFamily', resolvedModes) || 'JioType Var';
57
60
  const statusWrapGap = Number(getVariableByName('transactionBubble/statusWrap/gap', resolvedModes)) || 4;
61
+ const statusWrapHeight = Number(getVariableByName('transactionBubble/statusWrap/height', resolvedModes)) || 18;
58
62
  const containerStyle = {
59
63
  padding,
60
64
  borderRadius: radius,
@@ -78,11 +82,13 @@ function TransactionBubble({
78
82
  accessibilityLabel: defaultAccessibilityLabel,
79
83
  webAccessibilityProps
80
84
  });
81
- const rightColumn = processedStatusSlot || /*#__PURE__*/_jsxs(View, {
85
+ const statusRow = processedStatusSlot || /*#__PURE__*/_jsxs(View, {
82
86
  style: {
83
- alignItems: 'flex-end',
84
- justifyContent: 'flex-end',
85
- gap: statusWrapGap
87
+ flexDirection: 'row',
88
+ alignItems: 'center',
89
+ justifyContent: 'space-between',
90
+ gap: statusWrapGap,
91
+ height: statusWrapHeight
86
92
  },
87
93
  children: [/*#__PURE__*/_jsx(TransactionStatus, {
88
94
  status: status,
@@ -95,13 +101,11 @@ function TransactionBubble({
95
101
  });
96
102
  const mainContent = /*#__PURE__*/_jsxs(View, {
97
103
  style: {
98
- flexDirection: 'row',
99
- gap: wrapGap
104
+ gap: bubbleGap
100
105
  },
101
106
  children: [/*#__PURE__*/_jsxs(View, {
102
107
  style: {
103
- flex: 1,
104
- gap: bubbleGap
108
+ gap: moneyValueWrapGap
105
109
  },
106
110
  children: [/*#__PURE__*/_jsx(Text, {
107
111
  style: descriptionStyle,
@@ -112,10 +116,10 @@ function TransactionBubble({
112
116
  currency: currency,
113
117
  modes: resolvedModes
114
118
  })]
115
- }), rightColumn]
119
+ }), processedChildren, statusRow]
116
120
  });
117
121
  if (onPress) {
118
- return /*#__PURE__*/_jsxs(Pressable, {
122
+ return /*#__PURE__*/_jsx(Pressable, {
119
123
  onPress: onPress,
120
124
  style: ({
121
125
  pressed
@@ -127,14 +131,14 @@ function TransactionBubble({
127
131
  accessibilityHint: accessibilityHint,
128
132
  ...webProps,
129
133
  ...rest,
130
- children: [mainContent, processedChildren]
134
+ children: mainContent
131
135
  });
132
136
  }
133
- return /*#__PURE__*/_jsxs(View, {
137
+ return /*#__PURE__*/_jsx(View, {
134
138
  style: [containerStyle, style],
135
139
  accessibilityLabel: defaultAccessibilityLabel,
136
140
  accessibilityHint: accessibilityHint,
137
- children: [mainContent, processedChildren]
141
+ children: mainContent
138
142
  });
139
143
  }
140
144
  export default TransactionBubble;