jfs-components 0.0.57 → 0.0.59
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/lib/commonjs/components/ActionFooter/ActionFooter.js +20 -14
- package/lib/commonjs/components/Button/Button.js +1 -1
- package/lib/commonjs/components/ButtonGroup/ButtonGroup.js +11 -8
- package/lib/commonjs/components/Drawer/Drawer.js +1 -1
- package/lib/commonjs/components/ListGroup/ListGroup.js +1 -1
- package/lib/commonjs/components/Section/Section.js +71 -8
- package/lib/commonjs/components/Tabs/Tabs.js +6 -3
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/ActionFooter/ActionFooter.js +21 -15
- package/lib/module/components/Button/Button.js +1 -1
- package/lib/module/components/ButtonGroup/ButtonGroup.js +11 -8
- package/lib/module/components/Drawer/Drawer.js +1 -1
- package/lib/module/components/ListGroup/ListGroup.js +1 -1
- package/lib/module/components/Section/Section.js +73 -9
- package/lib/module/components/Tabs/Tabs.js +6 -3
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/ActionFooter/ActionFooter.d.ts +4 -10
- package/lib/typescript/src/components/ButtonGroup/ButtonGroup.d.ts +3 -1
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/ActionFooter/ActionFooter.tsx +20 -14
- package/src/components/Button/Button.tsx +1 -1
- package/src/components/ButtonGroup/ButtonGroup.tsx +17 -11
- package/src/components/Drawer/Drawer.tsx +1 -1
- package/src/components/ListGroup/ListGroup.tsx +1 -1
- package/src/components/Section/Section.tsx +90 -8
- package/src/components/Tabs/Tabs.tsx +2 -2
- package/src/icons/registry.ts +1 -1
|
@@ -38,18 +38,12 @@ export type ActionFooterProps = {
|
|
|
38
38
|
*
|
|
39
39
|
* @example
|
|
40
40
|
* ```tsx
|
|
41
|
-
* // Basic usage - modes are automatically passed to all children
|
|
41
|
+
* // Basic usage - modes are automatically passed to all children.
|
|
42
|
+
* // Non-IconButton children (e.g., Button) are auto-stretched to fill.
|
|
42
43
|
* <ActionFooter modes={modes}>
|
|
43
44
|
* <IconButton iconName="ic_split" />
|
|
44
|
-
* <Button label="Request"
|
|
45
|
-
* <Button label="Pay"
|
|
46
|
-
* </ActionFooter>
|
|
47
|
-
*
|
|
48
|
-
* // Children can override with their own modes (merged with parent)
|
|
49
|
-
* <ActionFooter modes={modes}>
|
|
50
|
-
* <IconButton iconName="ic_split" />
|
|
51
|
-
* <Button label="Request" modes={{ Appearance: 'secondary' }} style={{ flex: 1 }} />
|
|
52
|
-
* <Button label="Pay" modes={{ Appearance: 'primary' }} style={{ flex: 1 }} />
|
|
45
|
+
* <Button label="Request" />
|
|
46
|
+
* <Button label="Pay" />
|
|
53
47
|
* </ActionFooter>
|
|
54
48
|
* ```
|
|
55
49
|
*/
|
|
@@ -21,13 +21,15 @@ export type ButtonGroupProps = {
|
|
|
21
21
|
* and handles their layout and styling/theming via design tokens.
|
|
22
22
|
*
|
|
23
23
|
* It passes the provided `modes` to all its immediate React Element children.
|
|
24
|
+
* Non-IconButton children (e.g., Button) are automatically stretched to fill
|
|
25
|
+
* available space. IconButton children keep their natural width.
|
|
24
26
|
*
|
|
25
27
|
* @component
|
|
26
28
|
* @example
|
|
27
29
|
* ```jsx
|
|
28
30
|
* <ButtonGroup modes={{"Appearance": "light"}}>
|
|
29
31
|
* <IconButton iconName="ic_qr_code" />
|
|
30
|
-
* <
|
|
32
|
+
* <Button label="Pay" />
|
|
31
33
|
* </ButtonGroup>
|
|
32
34
|
* ```
|
|
33
35
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Auto-generated from SVG files in src/icons/
|
|
5
5
|
* DO NOT EDIT MANUALLY - Run "npm run icons:generate" to regenerate
|
|
6
6
|
*
|
|
7
|
-
* Generated: 2026-04-
|
|
7
|
+
* Generated: 2026-04-14T20:02:24.340Z
|
|
8
8
|
*/
|
|
9
9
|
export declare const iconRegistry: Record<string, {
|
|
10
10
|
path: string;
|
package/package.json
CHANGED
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
Platform,
|
|
7
7
|
} from 'react-native'
|
|
8
8
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
9
|
-
import { cloneChildrenWithModes } from '../../utils/react-utils'
|
|
9
|
+
import { cloneChildrenWithModes, flattenChildren } from '../../utils/react-utils'
|
|
10
|
+
import IconButton from '../IconButton/IconButton'
|
|
10
11
|
|
|
11
12
|
export type ActionFooterProps = {
|
|
12
13
|
/**
|
|
@@ -47,18 +48,12 @@ export type ActionFooterProps = {
|
|
|
47
48
|
*
|
|
48
49
|
* @example
|
|
49
50
|
* ```tsx
|
|
50
|
-
* // Basic usage - modes are automatically passed to all children
|
|
51
|
+
* // Basic usage - modes are automatically passed to all children.
|
|
52
|
+
* // Non-IconButton children (e.g., Button) are auto-stretched to fill.
|
|
51
53
|
* <ActionFooter modes={modes}>
|
|
52
54
|
* <IconButton iconName="ic_split" />
|
|
53
|
-
* <Button label="Request"
|
|
54
|
-
* <Button label="Pay"
|
|
55
|
-
* </ActionFooter>
|
|
56
|
-
*
|
|
57
|
-
* // Children can override with their own modes (merged with parent)
|
|
58
|
-
* <ActionFooter modes={modes}>
|
|
59
|
-
* <IconButton iconName="ic_split" />
|
|
60
|
-
* <Button label="Request" modes={{ Appearance: 'secondary' }} style={{ flex: 1 }} />
|
|
61
|
-
* <Button label="Pay" modes={{ Appearance: 'primary' }} style={{ flex: 1 }} />
|
|
55
|
+
* <Button label="Request" />
|
|
56
|
+
* <Button label="Pay" />
|
|
62
57
|
* </ActionFooter>
|
|
63
58
|
* ```
|
|
64
59
|
*/
|
|
@@ -113,8 +108,19 @@ function ActionFooter({
|
|
|
113
108
|
? { boxShadow: '0px -12px 24px 0px rgba(12, 13, 16, 0.12), 0px -16px 48px 0px rgba(12, 13, 16, 0.16)' } as any
|
|
114
109
|
: {}
|
|
115
110
|
|
|
116
|
-
|
|
117
|
-
const
|
|
111
|
+
const flatChildren = flattenChildren(children)
|
|
112
|
+
const processedChildren = cloneChildrenWithModes(flatChildren, modes)
|
|
113
|
+
|
|
114
|
+
const enhancedChildren = (processedChildren as React.ReactNode[]).map((child, index) => {
|
|
115
|
+
if (!React.isValidElement(child)) return child
|
|
116
|
+
const element = child as React.ReactElement<any>
|
|
117
|
+
const isIconButton = element.type === IconButton
|
|
118
|
+
const stretchStyle = isIconButton ? undefined : { flex: 1 }
|
|
119
|
+
return React.cloneElement(element, {
|
|
120
|
+
key: element.key ?? index,
|
|
121
|
+
style: [stretchStyle, element.props.style],
|
|
122
|
+
})
|
|
123
|
+
})
|
|
118
124
|
|
|
119
125
|
return (
|
|
120
126
|
<View
|
|
@@ -123,7 +129,7 @@ function ActionFooter({
|
|
|
123
129
|
accessibilityLabel={undefined}
|
|
124
130
|
>
|
|
125
131
|
<View style={slotStyle}>
|
|
126
|
-
{
|
|
132
|
+
{enhancedChildren}
|
|
127
133
|
</View>
|
|
128
134
|
</View>
|
|
129
135
|
)
|
|
@@ -97,7 +97,7 @@ function Button({
|
|
|
97
97
|
const lineHeight = getVariableByName('button/lineHeight', modes) || 19
|
|
98
98
|
const fontSize = getVariableByName('button/fontSize', modes) || 16
|
|
99
99
|
const textColor = getVariableByName('button/foreground', modes) || '#0f0d0a'
|
|
100
|
-
const iconColor =
|
|
100
|
+
const iconColor = textColor
|
|
101
101
|
const iconSize = getVariableByName('button/icon/size', modes) ?? 18
|
|
102
102
|
|
|
103
103
|
const [isHovered, setIsHovered] = useState(false)
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from 'react-native'
|
|
7
7
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
8
8
|
import { flattenChildren } from '../../utils/react-utils'
|
|
9
|
+
import IconButton from '../IconButton/IconButton'
|
|
9
10
|
|
|
10
11
|
export type ButtonGroupProps = {
|
|
11
12
|
/**
|
|
@@ -29,13 +30,15 @@ export type ButtonGroupProps = {
|
|
|
29
30
|
* and handles their layout and styling/theming via design tokens.
|
|
30
31
|
*
|
|
31
32
|
* It passes the provided `modes` to all its immediate React Element children.
|
|
33
|
+
* Non-IconButton children (e.g., Button) are automatically stretched to fill
|
|
34
|
+
* available space. IconButton children keep their natural width.
|
|
32
35
|
*
|
|
33
36
|
* @component
|
|
34
37
|
* @example
|
|
35
38
|
* ```jsx
|
|
36
39
|
* <ButtonGroup modes={{"Appearance": "light"}}>
|
|
37
40
|
* <IconButton iconName="ic_qr_code" />
|
|
38
|
-
* <
|
|
41
|
+
* <Button label="Pay" />
|
|
39
42
|
* </ButtonGroup>
|
|
40
43
|
* ```
|
|
41
44
|
*/
|
|
@@ -45,7 +48,7 @@ function ButtonGroup({
|
|
|
45
48
|
style,
|
|
46
49
|
}: ButtonGroupProps) {
|
|
47
50
|
// Resolve design tokens
|
|
48
|
-
const gap = getVariableByName('buttonGroup/
|
|
51
|
+
const gap = getVariableByName('buttonGroup/gap', modes) ?? 12
|
|
49
52
|
const paddingHorizontal = getVariableByName('buttonGroup/padding/horizontal', modes) ?? 0
|
|
50
53
|
const paddingVertical = getVariableByName('buttonGroup/padding/vertical', modes) ?? 0
|
|
51
54
|
|
|
@@ -59,19 +62,22 @@ function ButtonGroup({
|
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
// Flatten children to handle Fragments properly
|
|
62
|
-
const flatChildren = flattenChildren(children)
|
|
65
|
+
const flatChildren = flattenChildren(children)
|
|
63
66
|
|
|
64
|
-
// Clone children to pass `modes` prop
|
|
65
67
|
const childrenWithModes = React.Children.map(flatChildren, (child) => {
|
|
66
68
|
if (React.isValidElement(child)) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
|
|
69
|
+
const element = child as React.ReactElement<any>
|
|
70
|
+
const childModes = element.props.modes || {}
|
|
71
|
+
const mergedModes = { ...modes, ...childModes }
|
|
72
|
+
const isIconButton = element.type === IconButton
|
|
73
|
+
const stretchStyle = isIconButton
|
|
74
|
+
? undefined
|
|
75
|
+
: { flex: 1 }
|
|
73
76
|
|
|
74
|
-
return React.cloneElement(element, {
|
|
77
|
+
return React.cloneElement(element, {
|
|
78
|
+
modes: mergedModes,
|
|
79
|
+
style: [stretchStyle, element.props.style],
|
|
80
|
+
})
|
|
75
81
|
}
|
|
76
82
|
return child
|
|
77
83
|
})
|
|
@@ -276,7 +276,7 @@ function Drawer({
|
|
|
276
276
|
|
|
277
277
|
// Design Tokens
|
|
278
278
|
const backgroundColor = getVariableByName('drawer/background', modes) || '#f5f5f5'
|
|
279
|
-
const radius = getVariableByName('drawer/radius', modes) || 12
|
|
279
|
+
const radius = getVariableByName('drawer/radius/top', modes) || 12
|
|
280
280
|
|
|
281
281
|
// Handle
|
|
282
282
|
const handleColor = getVariableByName('drawer/handlebar/background', modes) || '#e0e0e3'
|
|
@@ -1,10 +1,87 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
1
|
+
import React, { useState, useRef, useCallback, useEffect } from 'react'
|
|
2
2
|
import { View, Text, Pressable, Platform, type StyleProp, type ViewStyle, type PressableStateCallbackType } from 'react-native'
|
|
3
3
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
4
|
import NavArrow from '../NavArrow/NavArrow'
|
|
5
5
|
import { usePressableWebSupport, type WebAccessibilityProps } from '../../utils/web-platform-utils'
|
|
6
6
|
import { cloneChildrenWithModes, flattenChildren } from '../../utils/react-utils'
|
|
7
7
|
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Shared grid layout: measures widest child, enforces uniform width,
|
|
10
|
+
// chunks into fixed rows of up to maxColumns, space-between per row.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
const SLOT_GRID_MAX_COLUMNS = 4
|
|
13
|
+
|
|
14
|
+
type SlotGridProps = {
|
|
15
|
+
items: React.ReactNode[];
|
|
16
|
+
gap: number;
|
|
17
|
+
maxColumns?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function SlotGrid({ items, gap, maxColumns = SLOT_GRID_MAX_COLUMNS }: SlotGridProps) {
|
|
21
|
+
const [maxItemWidth, setMaxItemWidth] = useState<number | null>(null)
|
|
22
|
+
const itemWidthsRef = useRef<Map<number, number>>(new Map())
|
|
23
|
+
const totalItems = items.length
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
itemWidthsRef.current.clear()
|
|
27
|
+
setMaxItemWidth(null)
|
|
28
|
+
}, [totalItems])
|
|
29
|
+
|
|
30
|
+
const handleItemLayout = useCallback((index: number, width: number) => {
|
|
31
|
+
itemWidthsRef.current.set(index, width)
|
|
32
|
+
if (itemWidthsRef.current.size >= totalItems && totalItems > 0) {
|
|
33
|
+
setMaxItemWidth(Math.max(...itemWidthsRef.current.values()))
|
|
34
|
+
}
|
|
35
|
+
}, [totalItems])
|
|
36
|
+
|
|
37
|
+
const isMeasured = maxItemWidth !== null
|
|
38
|
+
const columns = Math.min(maxColumns, totalItems || 1)
|
|
39
|
+
|
|
40
|
+
const rows: React.ReactNode[][] = []
|
|
41
|
+
for (let i = 0; i < items.length; i += columns) {
|
|
42
|
+
rows.push(items.slice(i, i + columns))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<View style={{ gap, ...(isMeasured ? {} : { opacity: 0 }) }}>
|
|
47
|
+
{rows.map((row, rowIndex) => {
|
|
48
|
+
const spacersNeeded = row.length < columns ? columns - row.length : 0
|
|
49
|
+
return (
|
|
50
|
+
<View
|
|
51
|
+
key={rowIndex}
|
|
52
|
+
style={{
|
|
53
|
+
flexDirection: 'row' as const,
|
|
54
|
+
justifyContent: 'space-between' as const,
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
{row.map((child, colIndex) => {
|
|
58
|
+
const itemIndex = rowIndex * columns + colIndex
|
|
59
|
+
return (
|
|
60
|
+
<View
|
|
61
|
+
key={itemIndex}
|
|
62
|
+
onLayout={
|
|
63
|
+
!isMeasured
|
|
64
|
+
? (e) => handleItemLayout(itemIndex, e.nativeEvent.layout.width)
|
|
65
|
+
: undefined
|
|
66
|
+
}
|
|
67
|
+
style={isMeasured ? { width: maxItemWidth } : undefined}
|
|
68
|
+
>
|
|
69
|
+
{child}
|
|
70
|
+
</View>
|
|
71
|
+
)
|
|
72
|
+
})}
|
|
73
|
+
{isMeasured &&
|
|
74
|
+
spacersNeeded > 0 &&
|
|
75
|
+
Array.from({ length: spacersNeeded }, (_, i) => (
|
|
76
|
+
<View key={`spacer-${i}`} style={{ width: maxItemWidth }} />
|
|
77
|
+
))}
|
|
78
|
+
</View>
|
|
79
|
+
)
|
|
80
|
+
})}
|
|
81
|
+
</View>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
8
85
|
type SectionProps = {
|
|
9
86
|
title?: string;
|
|
10
87
|
supportText?: string;
|
|
@@ -233,8 +310,14 @@ function Section({
|
|
|
233
310
|
{headerContent}
|
|
234
311
|
</View>
|
|
235
312
|
)}
|
|
236
|
-
{slot && (
|
|
237
|
-
<
|
|
313
|
+
{slot && slotDirection === 'row' && (
|
|
314
|
+
<SlotGrid
|
|
315
|
+
items={cloneChildrenWithModes(flattenChildren(slot), modes)}
|
|
316
|
+
gap={sectionGap}
|
|
317
|
+
/>
|
|
318
|
+
)}
|
|
319
|
+
{slot && slotDirection === 'column' && (
|
|
320
|
+
<View style={{ flexDirection: 'column' as const, gap: slotGap }}>
|
|
238
321
|
{cloneChildrenWithModes(flattenChildren(slot), modes)}
|
|
239
322
|
</View>
|
|
240
323
|
)}
|
|
@@ -301,8 +384,6 @@ function SectionBento({
|
|
|
301
384
|
gap,
|
|
302
385
|
}
|
|
303
386
|
|
|
304
|
-
|
|
305
|
-
|
|
306
387
|
const processedNavSlot = navSlot
|
|
307
388
|
? cloneChildrenWithModes(flattenChildren(navSlot), modes)
|
|
308
389
|
: null
|
|
@@ -320,9 +401,10 @@ function SectionBento({
|
|
|
320
401
|
{...rest}
|
|
321
402
|
>
|
|
322
403
|
{processedNavSlot && (
|
|
323
|
-
<
|
|
324
|
-
{processedNavSlot}
|
|
325
|
-
|
|
404
|
+
<SlotGrid
|
|
405
|
+
items={processedNavSlot as React.ReactNode[]}
|
|
406
|
+
gap={gap}
|
|
407
|
+
/>
|
|
326
408
|
)}
|
|
327
409
|
{processedUpiSlot && (
|
|
328
410
|
<View style={{ flexDirection: 'row' as const, gap: 8 }}>
|
|
@@ -58,12 +58,12 @@ function Tabs({
|
|
|
58
58
|
const paddingLeft = (getVariableByName('tabs/padding/left', modes) ?? 0) as number
|
|
59
59
|
const paddingRight = (getVariableByName('tabs/padding/right', modes) ?? 0) as number
|
|
60
60
|
|
|
61
|
-
// Forward modes to all TabItem children
|
|
62
61
|
const enhancedChildren = React.Children.map(children, (child) => {
|
|
63
62
|
if (React.isValidElement(child) && child.type === TabItem) {
|
|
64
|
-
const childElement = child as React.ReactElement<any
|
|
63
|
+
const childElement = child as React.ReactElement<any>
|
|
65
64
|
return React.cloneElement(childElement, {
|
|
66
65
|
modes: { ...modes, ...(childElement.props.modes ?? {}) },
|
|
66
|
+
...(scrollable ? {} : { style: [{ flex: 1 }, childElement.props.style] }),
|
|
67
67
|
})
|
|
68
68
|
}
|
|
69
69
|
return child
|
package/src/icons/registry.ts
CHANGED