jfs-components 0.0.61 → 0.0.63
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 +36 -0
- package/lib/commonjs/components/Accordion/Accordion.js +1 -1
- package/lib/commonjs/components/ActionFooter/ActionFooter.js +1 -1
- package/lib/commonjs/components/ActionTile/ActionTile.js +2 -1
- package/lib/commonjs/components/AmountInput/AmountInput.js +2 -1
- package/lib/commonjs/components/AppBar/AppBar.js +1 -1
- package/lib/commonjs/components/Avatar/Avatar.js +184 -162
- package/lib/commonjs/components/AvatarGroup/AvatarGroup.js +1 -1
- package/lib/commonjs/components/Badge/Badge.js +2 -1
- package/lib/commonjs/components/Balance/Balance.js +2 -1
- package/lib/commonjs/components/BottomNav/BottomNav.js +2 -1
- package/lib/commonjs/components/BottomNavItem/BottomNavItem.js +106 -86
- package/lib/commonjs/components/Button/Button.js +190 -93
- package/lib/commonjs/components/ButtonGroup/ButtonGroup.js +1 -1
- package/lib/commonjs/components/Card/Card.js +2 -1
- package/lib/commonjs/components/CardCTA/CardCTA.js +1 -1
- package/lib/commonjs/components/CardProviderInfo/CardProviderInfo.js +1 -1
- package/lib/commonjs/components/Carousel/Carousel.js +3 -2
- package/lib/commonjs/components/Checkbox/Checkbox.js +2 -1
- package/lib/commonjs/components/ChipGroup/ChipGroup.js +1 -1
- package/lib/commonjs/components/ChipSelect/ChipSelect.js +2 -1
- package/lib/commonjs/components/DebitCard/DebitCard.js +1 -1
- package/lib/commonjs/components/Disclaimer/Disclaimer.js +2 -1
- package/lib/commonjs/components/Divider/Divider.js +2 -1
- package/lib/commonjs/components/Drawer/Drawer.js +2 -1
- package/lib/commonjs/components/EmptyState/EmptyState.js +2 -1
- package/lib/commonjs/components/FilterBar/FilterBar.js +1 -1
- package/lib/commonjs/components/Form/Form.js +2 -1
- package/lib/commonjs/components/FormField/FormField.js +3 -2
- package/lib/commonjs/components/HStack/HStack.js +1 -1
- package/lib/commonjs/components/HoldingsCard/HoldingsCard.js +2 -1
- package/lib/commonjs/components/IconButton/IconButton.js +118 -128
- package/lib/commonjs/components/IconCapsule/IconCapsule.js +61 -57
- package/lib/commonjs/components/InputSearch/InputSearch.js +7 -3
- package/lib/commonjs/components/LazyList/LazyList.js +1 -1
- package/lib/commonjs/components/LinearMeter/LinearMeter.js +3 -2
- package/lib/commonjs/components/ListGroup/ListGroup.js +2 -3
- package/lib/commonjs/components/ListItem/ListItem.js +190 -142
- package/lib/commonjs/components/MediaCard/MediaCard.js +3 -3
- package/lib/commonjs/components/MerchantProfile/MerchantProfile.js +2 -1
- package/lib/commonjs/components/MoneyValue/MoneyValue.js +2 -1
- package/lib/commonjs/components/NavArrow/NavArrow.js +82 -59
- package/lib/commonjs/components/NoteInput/NoteInput.js +2 -1
- package/lib/commonjs/components/Nudge/Nudge.js +1 -1
- package/lib/commonjs/components/Numpad/Numpad.js +2 -1
- package/lib/commonjs/components/OTP/OTP.js +1 -1
- package/lib/commonjs/components/PaymentFeedback/PaymentFeedback.js +2 -1
- package/lib/commonjs/components/Popup/Popup.js +2 -1
- package/lib/commonjs/components/ProductLabel/ProductLabel.js +2 -1
- package/lib/commonjs/components/ProgressBadge/ProgressBadge.js +2 -1
- package/lib/commonjs/components/RadioButton/RadioButton.js +2 -1
- package/lib/commonjs/components/RechargeCard/RechargeCard.js +2 -1
- package/lib/commonjs/components/Screen/Screen.js +1 -1
- package/lib/commonjs/components/Section/Section.js +268 -156
- package/lib/commonjs/components/SegmentedControl/SegmentedControl.js +3 -2
- package/lib/commonjs/components/StatItem/StatItem.js +2 -1
- package/lib/commonjs/components/StatusHero/StatusHero.js +2 -1
- package/lib/commonjs/components/Stepper/Step.js +2 -1
- package/lib/commonjs/components/Stepper/StepLabel.js +2 -1
- package/lib/commonjs/components/Stepper/Stepper.js +2 -1
- package/lib/commonjs/components/SupportText/SupportText.js +2 -1
- package/lib/commonjs/components/SupportText/SupportTextIcon.js +2 -1
- package/lib/commonjs/components/SwappableAmount/SwappableAmount.js +2 -1
- package/lib/commonjs/components/Tabs/TabItem.js +2 -1
- package/lib/commonjs/components/Tabs/Tabs.js +2 -1
- package/lib/commonjs/components/Text/Text.js +2 -1
- package/lib/commonjs/components/TextInput/TextInput.js +2 -2
- package/lib/commonjs/components/ThreadHero/ThreadHero.js +2 -1
- package/lib/commonjs/components/Title/Title.js +2 -1
- package/lib/commonjs/components/Toast/Toast.js +2 -1
- package/lib/commonjs/components/Toggle/Toggle.js +2 -1
- package/lib/commonjs/components/Tooltip/Tooltip.js +2 -1
- package/lib/commonjs/components/TransactionBubble/TransactionBubble.js +1 -1
- package/lib/commonjs/components/TransactionDetails/TransactionDetails.js +2 -2
- package/lib/commonjs/components/TransactionStatus/TransactionStatus.js +3 -2
- package/lib/commonjs/components/UpiHandle/UpiHandle.js +144 -110
- package/lib/commonjs/components/VStack/VStack.js +1 -1
- package/lib/commonjs/design-tokens/JFSThemeProvider.js +2 -38
- package/lib/commonjs/design-tokens/figma-variables-resolver.js +21 -3
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/commonjs/utils/react-utils.js +26 -3
- package/lib/module/components/Accordion/Accordion.js +2 -2
- package/lib/module/components/ActionFooter/ActionFooter.js +2 -2
- package/lib/module/components/ActionTile/ActionTile.js +2 -1
- package/lib/module/components/AmountInput/AmountInput.js +2 -1
- package/lib/module/components/AppBar/AppBar.js +2 -2
- package/lib/module/components/Avatar/Avatar.js +184 -162
- package/lib/module/components/AvatarGroup/AvatarGroup.js +2 -2
- package/lib/module/components/Badge/Badge.js +2 -1
- package/lib/module/components/Balance/Balance.js +2 -1
- package/lib/module/components/BottomNav/BottomNav.js +2 -1
- package/lib/module/components/BottomNavItem/BottomNavItem.js +108 -88
- package/lib/module/components/Button/Button.js +192 -95
- package/lib/module/components/ButtonGroup/ButtonGroup.js +2 -2
- package/lib/module/components/Card/Card.js +2 -1
- package/lib/module/components/CardCTA/CardCTA.js +2 -2
- package/lib/module/components/CardProviderInfo/CardProviderInfo.js +2 -2
- package/lib/module/components/Carousel/Carousel.js +3 -2
- package/lib/module/components/Checkbox/Checkbox.js +2 -1
- package/lib/module/components/ChipGroup/ChipGroup.js +2 -2
- package/lib/module/components/ChipSelect/ChipSelect.js +2 -1
- package/lib/module/components/DebitCard/DebitCard.js +2 -2
- package/lib/module/components/Disclaimer/Disclaimer.js +2 -1
- package/lib/module/components/Divider/Divider.js +2 -1
- package/lib/module/components/Drawer/Drawer.js +2 -1
- package/lib/module/components/EmptyState/EmptyState.js +2 -1
- package/lib/module/components/FilterBar/FilterBar.js +2 -2
- package/lib/module/components/Form/Form.js +2 -1
- package/lib/module/components/FormField/FormField.js +3 -2
- package/lib/module/components/HStack/HStack.js +2 -2
- package/lib/module/components/HoldingsCard/HoldingsCard.js +2 -1
- package/lib/module/components/IconButton/IconButton.js +120 -130
- package/lib/module/components/IconCapsule/IconCapsule.js +60 -57
- package/lib/module/components/InputSearch/InputSearch.js +7 -3
- package/lib/module/components/LazyList/LazyList.js +2 -2
- package/lib/module/components/LinearMeter/LinearMeter.js +3 -2
- package/lib/module/components/ListGroup/ListGroup.js +3 -4
- package/lib/module/components/ListItem/ListItem.js +194 -146
- package/lib/module/components/MediaCard/MediaCard.js +4 -2
- package/lib/module/components/MerchantProfile/MerchantProfile.js +2 -1
- package/lib/module/components/MoneyValue/MoneyValue.js +2 -1
- package/lib/module/components/NavArrow/NavArrow.js +82 -58
- package/lib/module/components/NoteInput/NoteInput.js +2 -1
- package/lib/module/components/Nudge/Nudge.js +2 -2
- package/lib/module/components/Numpad/Numpad.js +2 -1
- package/lib/module/components/OTP/OTP.js +2 -2
- package/lib/module/components/PaymentFeedback/PaymentFeedback.js +2 -1
- package/lib/module/components/Popup/Popup.js +2 -1
- package/lib/module/components/ProductLabel/ProductLabel.js +2 -1
- package/lib/module/components/ProgressBadge/ProgressBadge.js +2 -1
- package/lib/module/components/RadioButton/RadioButton.js +2 -1
- package/lib/module/components/RechargeCard/RechargeCard.js +2 -1
- package/lib/module/components/Screen/Screen.js +2 -2
- package/lib/module/components/Section/Section.js +271 -159
- package/lib/module/components/SegmentedControl/SegmentedControl.js +3 -2
- package/lib/module/components/StatItem/StatItem.js +2 -1
- package/lib/module/components/StatusHero/StatusHero.js +2 -1
- package/lib/module/components/Stepper/Step.js +2 -1
- package/lib/module/components/Stepper/StepLabel.js +2 -1
- package/lib/module/components/Stepper/Stepper.js +2 -1
- package/lib/module/components/SupportText/SupportText.js +2 -1
- package/lib/module/components/SupportText/SupportTextIcon.js +2 -1
- package/lib/module/components/SwappableAmount/SwappableAmount.js +2 -1
- package/lib/module/components/Tabs/TabItem.js +2 -1
- package/lib/module/components/Tabs/Tabs.js +2 -1
- package/lib/module/components/Text/Text.js +2 -1
- package/lib/module/components/TextInput/TextInput.js +3 -3
- package/lib/module/components/ThreadHero/ThreadHero.js +2 -1
- package/lib/module/components/Title/Title.js +2 -1
- package/lib/module/components/Toast/Toast.js +2 -1
- package/lib/module/components/Toggle/Toggle.js +2 -1
- package/lib/module/components/Tooltip/Tooltip.js +2 -1
- package/lib/module/components/TransactionBubble/TransactionBubble.js +2 -2
- package/lib/module/components/TransactionDetails/TransactionDetails.js +3 -3
- package/lib/module/components/TransactionStatus/TransactionStatus.js +3 -2
- package/lib/module/components/UpiHandle/UpiHandle.js +147 -113
- package/lib/module/components/VStack/VStack.js +2 -2
- package/lib/module/design-tokens/JFSThemeProvider.js +2 -35
- package/lib/module/design-tokens/figma-variables-resolver.js +21 -3
- package/lib/module/icons/registry.js +1 -1
- package/lib/module/utils/react-utils.js +25 -3
- package/lib/typescript/src/components/Avatar/Avatar.d.ts +11 -17
- package/lib/typescript/src/components/BottomNavItem/BottomNavItem.d.ts +12 -8
- package/lib/typescript/src/components/Button/Button.d.ts +18 -1
- package/lib/typescript/src/components/IconButton/IconButton.d.ts +12 -29
- package/lib/typescript/src/components/IconCapsule/IconCapsule.d.ts +10 -18
- package/lib/typescript/src/components/InputSearch/InputSearch.d.ts +8 -3
- package/lib/typescript/src/components/ListItem/ListItem.d.ts +14 -1
- package/lib/typescript/src/components/NavArrow/NavArrow.d.ts +12 -11
- package/lib/typescript/src/components/Section/Section.d.ts +2 -48
- package/lib/typescript/src/components/UpiHandle/UpiHandle.d.ts +13 -12
- package/lib/typescript/src/design-tokens/JFSThemeProvider.d.ts +0 -15
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/lib/typescript/src/utils/react-utils.d.ts +15 -0
- package/package.json +4 -6
- package/src/components/Accordion/Accordion.tsx +2 -2
- package/src/components/ActionFooter/ActionFooter.tsx +2 -2
- package/src/components/ActionTile/ActionTile.tsx +2 -1
- package/src/components/AmountInput/AmountInput.tsx +2 -1
- package/src/components/AppBar/AppBar.tsx +2 -2
- package/src/components/Avatar/Avatar.tsx +229 -158
- package/src/components/AvatarGroup/AvatarGroup.tsx +2 -2
- package/src/components/Badge/Badge.tsx +2 -1
- package/src/components/Balance/Balance.tsx +2 -1
- package/src/components/BottomNav/BottomNav.tsx +2 -1
- package/src/components/BottomNavItem/BottomNavItem.tsx +159 -88
- package/src/components/Button/Button.tsx +228 -101
- package/src/components/ButtonGroup/ButtonGroup.tsx +2 -2
- package/src/components/Card/Card.tsx +2 -1
- package/src/components/CardCTA/CardCTA.tsx +2 -2
- package/src/components/CardProviderInfo/CardProviderInfo.tsx +2 -2
- package/src/components/Carousel/Carousel.tsx +3 -2
- package/src/components/Checkbox/Checkbox.tsx +2 -1
- package/src/components/ChipGroup/ChipGroup.tsx +2 -2
- package/src/components/ChipSelect/ChipSelect.tsx +2 -1
- package/src/components/DebitCard/DebitCard.tsx +2 -2
- package/src/components/Disclaimer/Disclaimer.tsx +2 -1
- package/src/components/Divider/Divider.tsx +2 -1
- package/src/components/Drawer/Drawer.tsx +2 -1
- package/src/components/EmptyState/EmptyState.tsx +2 -1
- package/src/components/FilterBar/FilterBar.tsx +2 -2
- package/src/components/Form/Form.tsx +2 -1
- package/src/components/FormField/FormField.tsx +3 -2
- package/src/components/HStack/HStack.tsx +2 -2
- package/src/components/HoldingsCard/HoldingsCard.tsx +2 -1
- package/src/components/IconButton/IconButton.tsx +154 -126
- package/src/components/IconCapsule/IconCapsule.tsx +73 -54
- package/src/components/InputSearch/InputSearch.tsx +19 -5
- package/src/components/LazyList/LazyList.tsx +2 -2
- package/src/components/LinearMeter/LinearMeter.tsx +3 -2
- package/src/components/ListGroup/ListGroup.tsx +4 -5
- package/src/components/ListItem/ListItem.tsx +257 -187
- package/src/components/MediaCard/MediaCard.tsx +2 -1
- package/src/components/MerchantProfile/MerchantProfile.tsx +2 -1
- package/src/components/MoneyValue/MoneyValue.tsx +2 -1
- package/src/components/NavArrow/NavArrow.tsx +91 -58
- package/src/components/NoteInput/NoteInput.tsx +2 -1
- package/src/components/Nudge/Nudge.tsx +2 -2
- package/src/components/Numpad/Numpad.tsx +2 -1
- package/src/components/OTP/OTP.tsx +2 -2
- package/src/components/PaymentFeedback/PaymentFeedback.tsx +2 -1
- package/src/components/Popup/Popup.tsx +2 -1
- package/src/components/ProductLabel/ProductLabel.tsx +2 -1
- package/src/components/ProgressBadge/ProgressBadge.tsx +2 -2
- package/src/components/RadioButton/RadioButton.tsx +2 -1
- package/src/components/RechargeCard/RechargeCard.tsx +2 -1
- package/src/components/Screen/Screen.tsx +2 -2
- package/src/components/Section/Section.tsx +323 -167
- package/src/components/SegmentedControl/SegmentedControl.tsx +3 -2
- package/src/components/StatItem/StatItem.tsx +2 -1
- package/src/components/StatusHero/StatusHero.tsx +2 -1
- package/src/components/Stepper/Step.tsx +2 -1
- package/src/components/Stepper/StepLabel.tsx +2 -1
- package/src/components/Stepper/Stepper.tsx +2 -1
- package/src/components/SupportText/SupportText.tsx +2 -1
- package/src/components/SupportText/SupportTextIcon.tsx +2 -1
- package/src/components/SwappableAmount/SwappableAmount.tsx +2 -1
- package/src/components/Tabs/TabItem.tsx +2 -1
- package/src/components/Tabs/Tabs.tsx +2 -1
- package/src/components/Text/Text.tsx +2 -1
- package/src/components/TextInput/TextInput.tsx +3 -3
- package/src/components/ThreadHero/ThreadHero.tsx +2 -1
- package/src/components/Title/Title.tsx +2 -1
- package/src/components/Toast/Toast.tsx +2 -1
- package/src/components/Toggle/Toggle.tsx +2 -1
- package/src/components/Tooltip/Tooltip.tsx +2 -1
- package/src/components/TransactionBubble/TransactionBubble.tsx +2 -2
- package/src/components/TransactionDetails/TransactionDetails.tsx +3 -3
- package/src/components/TransactionStatus/TransactionStatus.tsx +3 -2
- package/src/components/UpiHandle/UpiHandle.tsx +193 -125
- package/src/components/VStack/VStack.tsx +2 -2
- package/src/design-tokens/JFSThemeProvider.tsx +1 -37
- package/src/design-tokens/figma-variables-resolver.ts +21 -3
- package/src/icons/registry.ts +1 -1
- package/src/utils/react-utils.ts +29 -3
- package/lib/typescript/App.d.ts +0 -2
- package/lib/typescript/index.d.ts +0 -2
- package/lib/typescript/metro.config.d.ts +0 -78
- package/lib/typescript/react-native.config.d.ts +0 -4
|
@@ -1,13 +1,50 @@
|
|
|
1
|
-
import React, { useState, useRef, useCallback
|
|
1
|
+
import React, { useState, useMemo, useRef, useCallback } 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
|
-
import { cloneChildrenWithModes, flattenChildren } from '../../utils/react-utils'
|
|
6
|
+
import { EMPTY_MODES, cloneChildrenWithModes, flattenChildren } from '../../utils/react-utils'
|
|
7
|
+
|
|
8
|
+
// Match Button: delay the press visual on iOS so a scroll-cancelled touch
|
|
9
|
+
// never applies the "pressed" style. See Button.tsx for the rationale.
|
|
10
|
+
const IS_WEB = Platform.OS === 'web'
|
|
11
|
+
const IS_IOS = Platform.OS === 'ios'
|
|
12
|
+
const HEADER_PRESS_DELAY = IS_IOS ? 130 : 0
|
|
13
|
+
|
|
14
|
+
// Module-scope style constants — never re-allocated per render.
|
|
15
|
+
const headerWrapStyle: ViewStyle = {
|
|
16
|
+
flexDirection: 'row',
|
|
17
|
+
alignItems: 'center',
|
|
18
|
+
justifyContent: 'space-between',
|
|
19
|
+
}
|
|
20
|
+
const headerHoverStyle: ViewStyle = { opacity: 0.95 }
|
|
21
|
+
const headerPressedStyle: ViewStyle = { opacity: 0.85 }
|
|
22
|
+
const headerFocusStyle: ViewStyle = { borderColor: '#222', borderWidth: 1 }
|
|
7
23
|
|
|
8
24
|
// ---------------------------------------------------------------------------
|
|
9
|
-
// Shared grid layout: measures widest child
|
|
10
|
-
//
|
|
25
|
+
// Shared grid layout: measures the widest child once per item-count, then
|
|
26
|
+
// renders uniform-width cells laid out with `justify-content: space-between`.
|
|
27
|
+
// This preserves three visual invariants regardless of viewport width:
|
|
28
|
+
// 1. The first cell hugs the container's left edge.
|
|
29
|
+
// 2. The last cell hugs the container's right edge.
|
|
30
|
+
// 3. Cells in row N column K align with cells in row N+1 column K
|
|
31
|
+
// (uniform cell width across the whole grid).
|
|
32
|
+
// Pure flex sizing (e.g. `flexBasis: 0; flexGrow: 1`) cannot satisfy (1) and
|
|
33
|
+
// (2) on wide viewports — it distributes extra space inside each cell, which
|
|
34
|
+
// makes the icon+label content drift toward each cell's center and produces
|
|
35
|
+
// visible "dead" margins on the left and right of the grid.
|
|
36
|
+
//
|
|
37
|
+
// To avoid the blank-flash that the previous implementation suffered from
|
|
38
|
+
// (it hid the grid with `opacity: 0` until measurement completed, and reset
|
|
39
|
+
// every measurement when the item count changed):
|
|
40
|
+
// * Cells render at their *natural* widths during measurement instead of
|
|
41
|
+
// being hidden. With `space-between`, the first/last cells already hug
|
|
42
|
+
// the edges; only the column alignment can be off by a few pixels for
|
|
43
|
+
// the single frame between mount and the onLayout callback.
|
|
44
|
+
// * On item-count change (e.g. expand / collapse), we clear the per-cell
|
|
45
|
+
// samples but keep the rendered layout visible — we never blank out.
|
|
46
|
+
// * No 500ms safety timeout is needed because the grid is visible from the
|
|
47
|
+
// first frame.
|
|
11
48
|
// ---------------------------------------------------------------------------
|
|
12
49
|
const SLOT_GRID_MAX_COLUMNS = 4
|
|
13
50
|
|
|
@@ -17,32 +54,61 @@ type SlotGridProps = {
|
|
|
17
54
|
maxColumns?: number;
|
|
18
55
|
}
|
|
19
56
|
|
|
20
|
-
|
|
57
|
+
const slotGridRowStyle: ViewStyle = {
|
|
58
|
+
flexDirection: 'row',
|
|
59
|
+
justifyContent: 'space-between',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const SlotGrid = React.memo(function SlotGrid({
|
|
63
|
+
items,
|
|
64
|
+
gap,
|
|
65
|
+
maxColumns = SLOT_GRID_MAX_COLUMNS,
|
|
66
|
+
}: SlotGridProps) {
|
|
67
|
+
const totalItems = items.length
|
|
68
|
+
|
|
21
69
|
const [maxItemWidth, setMaxItemWidth] = useState<number | null>(null)
|
|
22
|
-
|
|
70
|
+
// Tracks the item-count that `maxItemWidth` corresponds to. When the
|
|
71
|
+
// current `totalItems` differs, the existing measurement is considered
|
|
72
|
+
// stale and cells fall back to natural widths until remeasurement.
|
|
73
|
+
const [measuredForCount, setMeasuredForCount] = useState(0)
|
|
23
74
|
const itemWidthsRef = useRef<Map<number, number>>(new Map())
|
|
24
|
-
const totalItems = items.length
|
|
25
75
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const handleItemLayout = useCallback(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
76
|
+
// Synchronously invalidate per-cell samples when the item count changes
|
|
77
|
+
// (e.g. show more / less). We deliberately do NOT touch `maxItemWidth` or
|
|
78
|
+
// `measuredForCount` state here — flipping them would force an extra render
|
|
79
|
+
// pass; instead we let the count mismatch (`measuredForCount !== totalItems`)
|
|
80
|
+
// gate the use of the stale value, and the next onLayout cycle will publish
|
|
81
|
+
// a fresh `maxItemWidth` for the new count.
|
|
82
|
+
const prevTotalRef = useRef(totalItems)
|
|
83
|
+
if (prevTotalRef.current !== totalItems) {
|
|
84
|
+
prevTotalRef.current = totalItems
|
|
85
|
+
itemWidthsRef.current = new Map()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const handleItemLayout = useCallback(
|
|
89
|
+
(index: number, width: number) => {
|
|
90
|
+
const widths = itemWidthsRef.current
|
|
91
|
+
const previous = widths.get(index)
|
|
92
|
+
if (previous !== undefined && Math.abs(previous - width) < 0.5) return
|
|
93
|
+
widths.set(index, width)
|
|
94
|
+
if (widths.size >= totalItems && totalItems > 0) {
|
|
95
|
+
let newMax = 0
|
|
96
|
+
for (const w of widths.values()) {
|
|
97
|
+
if (w > newMax) newMax = w
|
|
98
|
+
}
|
|
99
|
+
setMaxItemWidth((prev) =>
|
|
100
|
+
prev !== null && Math.abs(prev - newMax) < 0.5 ? prev : newMax
|
|
101
|
+
)
|
|
102
|
+
setMeasuredForCount(totalItems)
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
[totalItems]
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const hasFreshMeasurement =
|
|
109
|
+
maxItemWidth !== null && measuredForCount === totalItems
|
|
110
|
+
const cellWidth = hasFreshMeasurement ? maxItemWidth : undefined
|
|
111
|
+
|
|
46
112
|
const columns = Math.min(maxColumns, totalItems || 1)
|
|
47
113
|
|
|
48
114
|
const rows: React.ReactNode[][] = []
|
|
@@ -50,44 +116,59 @@ function SlotGrid({ items, gap, maxColumns = SLOT_GRID_MAX_COLUMNS }: SlotGridPr
|
|
|
50
116
|
rows.push(items.slice(i, i + columns))
|
|
51
117
|
}
|
|
52
118
|
|
|
119
|
+
const containerStyle = useMemo<ViewStyle>(() => ({ gap }), [gap])
|
|
120
|
+
const measuredCellStyle = useMemo<ViewStyle | undefined>(
|
|
121
|
+
() => (cellWidth !== undefined ? { width: cellWidth } : undefined),
|
|
122
|
+
[cellWidth]
|
|
123
|
+
)
|
|
124
|
+
|
|
53
125
|
return (
|
|
54
|
-
<View style={
|
|
126
|
+
<View style={containerStyle}>
|
|
55
127
|
{rows.map((row, rowIndex) => {
|
|
56
128
|
const spacersNeeded = row.length < columns ? columns - row.length : 0
|
|
57
129
|
return (
|
|
58
|
-
<View
|
|
59
|
-
key={rowIndex}
|
|
60
|
-
style={{
|
|
61
|
-
flexDirection: 'row' as const,
|
|
62
|
-
justifyContent: 'space-between' as const,
|
|
63
|
-
}}
|
|
64
|
-
>
|
|
130
|
+
<View key={rowIndex} style={slotGridRowStyle}>
|
|
65
131
|
{row.map((child, colIndex) => {
|
|
66
132
|
const itemIndex = rowIndex * columns + colIndex
|
|
67
133
|
return (
|
|
68
134
|
<View
|
|
69
135
|
key={itemIndex}
|
|
70
136
|
onLayout={
|
|
71
|
-
|
|
72
|
-
?
|
|
73
|
-
:
|
|
137
|
+
hasFreshMeasurement
|
|
138
|
+
? undefined
|
|
139
|
+
: (e) =>
|
|
140
|
+
handleItemLayout(
|
|
141
|
+
itemIndex,
|
|
142
|
+
e.nativeEvent.layout.width
|
|
143
|
+
)
|
|
74
144
|
}
|
|
75
|
-
style={
|
|
145
|
+
style={measuredCellStyle}
|
|
76
146
|
>
|
|
77
147
|
{child}
|
|
78
148
|
</View>
|
|
79
149
|
)
|
|
80
150
|
})}
|
|
81
|
-
{
|
|
151
|
+
{hasFreshMeasurement &&
|
|
82
152
|
spacersNeeded > 0 &&
|
|
83
153
|
Array.from({ length: spacersNeeded }, (_, i) => (
|
|
84
|
-
<View key={`spacer-${i}`} style={
|
|
154
|
+
<View key={`spacer-${i}`} style={measuredCellStyle} />
|
|
85
155
|
))}
|
|
86
156
|
</View>
|
|
87
157
|
)
|
|
88
158
|
})}
|
|
89
159
|
</View>
|
|
90
160
|
)
|
|
161
|
+
}, slotGridPropsAreEqual)
|
|
162
|
+
|
|
163
|
+
function slotGridPropsAreEqual(prev: SlotGridProps, next: SlotGridProps) {
|
|
164
|
+
if (prev.gap !== next.gap) return false
|
|
165
|
+
if ((prev.maxColumns ?? SLOT_GRID_MAX_COLUMNS) !== (next.maxColumns ?? SLOT_GRID_MAX_COLUMNS)) return false
|
|
166
|
+
if (prev.items === next.items) return true
|
|
167
|
+
if (prev.items.length !== next.items.length) return false
|
|
168
|
+
for (let i = 0; i < prev.items.length; i++) {
|
|
169
|
+
if (prev.items[i] !== next.items[i]) return false
|
|
170
|
+
}
|
|
171
|
+
return true
|
|
91
172
|
}
|
|
92
173
|
|
|
93
174
|
type SectionProps = {
|
|
@@ -132,40 +213,27 @@ type SectionProps = {
|
|
|
132
213
|
* @param {string} [props.accessibilityLabel] - Accessibility label for the section. If not provided, uses title
|
|
133
214
|
* @param {string} [props.accessibilityHint] - Additional accessibility hint for screen readers
|
|
134
215
|
*/
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
accessibilityHint,
|
|
146
|
-
webAccessibilityProps,
|
|
147
|
-
...rest
|
|
148
|
-
}: SectionProps) {
|
|
149
|
-
const [isHeaderFocused, setIsHeaderFocused] = useState(false)
|
|
150
|
-
const [isHeaderHovered, setIsHeaderHovered] = useState(false)
|
|
151
|
-
const [isHeaderPressed, setIsHeaderPressed] = useState(false)
|
|
152
|
-
const headerFocusStyle: ViewStyle = isHeaderFocused ? { borderColor: '#222', borderWidth: 1 } : {}
|
|
153
|
-
const headerHoverStyle: ViewStyle = isHeaderHovered ? { opacity: 0.95 } : {}
|
|
154
|
-
const headerPressedStyle: ViewStyle = isHeaderPressed ? { opacity: 0.85 } : {}
|
|
155
|
-
// Resolve section container tokens
|
|
216
|
+
interface SectionTokens {
|
|
217
|
+
containerStyle: ViewStyle;
|
|
218
|
+
headerStyle: ViewStyle;
|
|
219
|
+
titleStyle: ViewStyle;
|
|
220
|
+
supportTextStyle: ViewStyle;
|
|
221
|
+
sectionGap: number;
|
|
222
|
+
slotGap: number;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function resolveSectionTokens(modes: Record<string, any>): SectionTokens {
|
|
156
226
|
const backgroundColor = getVariableByName('section/background/color', modes) || '#ffffff'
|
|
157
|
-
const sectionGap = getVariableByName('section/gap', modes) || 12
|
|
158
|
-
const slotGap = getVariableByName('slot/gap', modes) || 12
|
|
227
|
+
const sectionGap = (getVariableByName('section/gap', modes) || 12) as number
|
|
228
|
+
const slotGap = (getVariableByName('slot/gap', modes) || 12) as number
|
|
159
229
|
const paddingHorizontal = getVariableByName('section/padding/horizontal', modes) || 12
|
|
160
230
|
const paddingVertical = getVariableByName('section/padding/vertical', modes) || 16
|
|
161
231
|
const radius = getVariableByName('section/radius', modes) || 12
|
|
162
232
|
|
|
163
|
-
// Resolve section header tokens
|
|
164
233
|
const headerGap = getVariableByName('section/header/gap', modes) || 8
|
|
165
234
|
const headerPaddingHorizontal = getVariableByName('section/header/padding/horizontal', modes) || 0
|
|
166
235
|
const headerPaddingVertical = getVariableByName('section/header/padding/vertical', modes) || 0
|
|
167
236
|
|
|
168
|
-
// Resolve section title tokens
|
|
169
237
|
const titleColor = getVariableByName('section/title/color', modes) || '#0f0d0a'
|
|
170
238
|
const titleFontSize = getVariableByName('section/title/fontSize', modes) || 18
|
|
171
239
|
const titleLineHeight = getVariableByName('section/title/lineHeight', modes) || 20
|
|
@@ -176,7 +244,6 @@ function Section({
|
|
|
176
244
|
? titleFontWeightRaw.toString()
|
|
177
245
|
: titleFontWeightRaw
|
|
178
246
|
|
|
179
|
-
// Resolve section support text tokens
|
|
180
247
|
const supportTextColor = getVariableByName('section/supportText/color', modes) || '#1f1a14'
|
|
181
248
|
const supportTextFontSize = getVariableByName('section/supportText/fontSize', modes) || 14
|
|
182
249
|
const supportTextLineHeight = getVariableByName('section/supportText/lineHeight', modes) || 18
|
|
@@ -187,46 +254,85 @@ function Section({
|
|
|
187
254
|
? supportTextFontWeightRaw.toString()
|
|
188
255
|
: supportTextFontWeightRaw
|
|
189
256
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
257
|
+
return {
|
|
258
|
+
containerStyle: {
|
|
259
|
+
backgroundColor,
|
|
260
|
+
paddingHorizontal,
|
|
261
|
+
paddingVertical,
|
|
262
|
+
borderRadius: radius,
|
|
263
|
+
gap: sectionGap,
|
|
264
|
+
},
|
|
265
|
+
headerStyle: {
|
|
266
|
+
paddingHorizontal: headerPaddingHorizontal,
|
|
267
|
+
paddingVertical: headerPaddingVertical,
|
|
268
|
+
gap: headerGap,
|
|
269
|
+
},
|
|
270
|
+
titleStyle: {
|
|
271
|
+
flex: 1,
|
|
272
|
+
color: titleColor,
|
|
273
|
+
fontSize: titleFontSize,
|
|
274
|
+
lineHeight: titleLineHeight,
|
|
275
|
+
fontFamily: titleFontFamily,
|
|
276
|
+
fontWeight: titleFontWeight,
|
|
277
|
+
} as ViewStyle,
|
|
278
|
+
supportTextStyle: {
|
|
279
|
+
color: supportTextColor,
|
|
280
|
+
fontSize: supportTextFontSize,
|
|
281
|
+
lineHeight: supportTextLineHeight,
|
|
282
|
+
fontFamily: supportTextFontFamily,
|
|
283
|
+
fontWeight: supportTextFontWeight,
|
|
284
|
+
} as ViewStyle,
|
|
285
|
+
sectionGap,
|
|
286
|
+
slotGap,
|
|
217
287
|
}
|
|
288
|
+
}
|
|
218
289
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
290
|
+
function Section({
|
|
291
|
+
title = 'Section title',
|
|
292
|
+
supportText = 'Section support text',
|
|
293
|
+
showSupportText = true,
|
|
294
|
+
slot,
|
|
295
|
+
slotDirection = 'row',
|
|
296
|
+
modes = EMPTY_MODES,
|
|
297
|
+
onPress,
|
|
298
|
+
style,
|
|
299
|
+
// accessibilityLabel is intentionally accepted on the type for API
|
|
300
|
+
// back-compat, but the inner Pressable/View deliberately pass
|
|
301
|
+
// `accessibilityLabel={undefined}` (the title Text carries the label
|
|
302
|
+
// instead). Prefix to satisfy the unused-var lint while keeping the prop
|
|
303
|
+
// shape unchanged.
|
|
304
|
+
accessibilityLabel: _accessibilityLabel,
|
|
305
|
+
accessibilityHint,
|
|
306
|
+
webAccessibilityProps,
|
|
307
|
+
...rest
|
|
308
|
+
}: SectionProps) {
|
|
309
|
+
// Focus and hover are still mirrored in React because they are visible,
|
|
310
|
+
// sustained states (web-only in practice). The setters are gated so they
|
|
311
|
+
// never fire on native — keeping the component render-free during touch.
|
|
312
|
+
// Press is handled imperatively via the `Pressable` style callback so a
|
|
313
|
+
// scroll-cancelled touch never schedules a React render.
|
|
314
|
+
const [isHeaderFocused, setIsHeaderFocused] = useState(false)
|
|
315
|
+
const [isHeaderHovered, setIsHeaderHovered] = useState(false)
|
|
226
316
|
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
|
|
317
|
+
// Mirror user handlers in a ref so our wrappers can stay referentially
|
|
318
|
+
// stable. Without this, every parent re-render would hand Pressable fresh
|
|
319
|
+
// function identities and re-bind every event.
|
|
320
|
+
const userHandlersRef = useRef<{
|
|
321
|
+
onPressIn?: (e: any) => void
|
|
322
|
+
onPressOut?: (e: any) => void
|
|
323
|
+
onHoverIn?: (e: any) => void
|
|
324
|
+
onHoverOut?: (e: any) => void
|
|
325
|
+
onFocus?: (e: any) => void
|
|
326
|
+
onBlur?: (e: any) => void
|
|
327
|
+
}>({})
|
|
328
|
+
userHandlersRef.current.onPressIn = (rest as any)?.onPressIn
|
|
329
|
+
userHandlersRef.current.onPressOut = (rest as any)?.onPressOut
|
|
330
|
+
userHandlersRef.current.onHoverIn = (rest as any)?.onHoverIn
|
|
331
|
+
userHandlersRef.current.onHoverOut = (rest as any)?.onHoverOut
|
|
332
|
+
userHandlersRef.current.onFocus = (rest as any)?.onFocus
|
|
333
|
+
userHandlersRef.current.onBlur = (rest as any)?.onBlur
|
|
334
|
+
|
|
335
|
+
const tokens = useMemo(() => resolveSectionTokens(modes), [modes])
|
|
230
336
|
|
|
231
337
|
// Get web platform support props (only used when onPress is defined)
|
|
232
338
|
const webProps = usePressableWebSupport({
|
|
@@ -241,7 +347,7 @@ function Section({
|
|
|
241
347
|
<>
|
|
242
348
|
<View style={headerWrapStyle}>
|
|
243
349
|
<Text
|
|
244
|
-
style={titleStyle}
|
|
350
|
+
style={tokens.titleStyle}
|
|
245
351
|
numberOfLines={1}
|
|
246
352
|
accessibilityElementsHidden={true}
|
|
247
353
|
importantForAccessibility="no"
|
|
@@ -254,7 +360,7 @@ function Section({
|
|
|
254
360
|
</View>
|
|
255
361
|
{showSupportText && (
|
|
256
362
|
<Text
|
|
257
|
-
style={supportTextStyle}
|
|
363
|
+
style={tokens.supportTextStyle}
|
|
258
364
|
numberOfLines={2}
|
|
259
365
|
accessibilityElementsHidden={true}
|
|
260
366
|
importantForAccessibility="no"
|
|
@@ -265,10 +371,53 @@ function Section({
|
|
|
265
371
|
</>
|
|
266
372
|
)
|
|
267
373
|
|
|
374
|
+
// Stable handler identities. User handlers are read through the ref so
|
|
375
|
+
// these wrappers don't need new identities each render.
|
|
376
|
+
const handlePressIn = useCallback((e: any) => {
|
|
377
|
+
userHandlersRef.current.onPressIn?.(e)
|
|
378
|
+
}, [])
|
|
379
|
+
const handlePressOut = useCallback((e: any) => {
|
|
380
|
+
userHandlersRef.current.onPressOut?.(e)
|
|
381
|
+
}, [])
|
|
382
|
+
const handleHoverIn = useCallback((e: any) => {
|
|
383
|
+
if (IS_WEB) setIsHeaderHovered(true)
|
|
384
|
+
userHandlersRef.current.onHoverIn?.(e)
|
|
385
|
+
}, [])
|
|
386
|
+
const handleHoverOut = useCallback((e: any) => {
|
|
387
|
+
if (IS_WEB) setIsHeaderHovered(false)
|
|
388
|
+
userHandlersRef.current.onHoverOut?.(e)
|
|
389
|
+
}, [])
|
|
390
|
+
const handleFocus = useCallback((e: any) => {
|
|
391
|
+
if (IS_WEB) setIsHeaderFocused(true)
|
|
392
|
+
userHandlersRef.current.onFocus?.(e)
|
|
393
|
+
}, [])
|
|
394
|
+
const handleBlur = useCallback((e: any) => {
|
|
395
|
+
if (IS_WEB) setIsHeaderFocused(false)
|
|
396
|
+
userHandlersRef.current.onBlur?.(e)
|
|
397
|
+
}, [])
|
|
398
|
+
|
|
399
|
+
// The pressed visual is applied by the host view directly through the
|
|
400
|
+
// Pressable style callback — no React render is scheduled. We still want
|
|
401
|
+
// the (constant) hover style on web so we keep it in the array.
|
|
402
|
+
const headerStyleCallback = useCallback(
|
|
403
|
+
({ pressed }: PressableStateCallbackType): StyleProp<ViewStyle> => [
|
|
404
|
+
tokens.headerStyle,
|
|
405
|
+
pressed ? headerPressedStyle : null,
|
|
406
|
+
isHeaderHovered ? headerHoverStyle : null,
|
|
407
|
+
isHeaderFocused ? headerFocusStyle : null,
|
|
408
|
+
],
|
|
409
|
+
[tokens.headerStyle, isHeaderHovered, isHeaderFocused]
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
const containerStyleArray = useMemo(
|
|
413
|
+
() => [tokens.containerStyle, style],
|
|
414
|
+
[tokens.containerStyle, style]
|
|
415
|
+
)
|
|
416
|
+
|
|
268
417
|
return (
|
|
269
418
|
<View
|
|
270
|
-
style={
|
|
271
|
-
{...(
|
|
419
|
+
style={containerStyleArray}
|
|
420
|
+
{...(IS_WEB ? { accessibilityRole: 'region' as any } : undefined)}
|
|
272
421
|
accessibilityLabel={undefined}
|
|
273
422
|
accessibilityHint={accessibilityHint}
|
|
274
423
|
{...rest}
|
|
@@ -277,62 +426,61 @@ function Section({
|
|
|
277
426
|
<Pressable
|
|
278
427
|
accessibilityRole="button"
|
|
279
428
|
accessibilityLabel={undefined}
|
|
280
|
-
accessibilityHint={accessibilityHint ||
|
|
429
|
+
accessibilityHint={accessibilityHint || 'Opens section details'}
|
|
281
430
|
onPress={onPress}
|
|
282
|
-
onPressIn={
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
onFocus={(e: any) => {
|
|
291
|
-
setIsHeaderFocused(true)
|
|
292
|
-
; (rest as any)?.onFocus?.(e)
|
|
293
|
-
}}
|
|
294
|
-
onBlur={(e: any) => {
|
|
295
|
-
setIsHeaderFocused(false)
|
|
296
|
-
; (rest as any)?.onBlur?.(e)
|
|
297
|
-
}}
|
|
298
|
-
onHoverIn={(e: any) => {
|
|
299
|
-
setIsHeaderHovered(true)
|
|
300
|
-
; (rest as any)?.onHoverIn?.(e)
|
|
301
|
-
}}
|
|
302
|
-
onHoverOut={(e: any) => {
|
|
303
|
-
setIsHeaderHovered(false)
|
|
304
|
-
; (rest as any)?.onHoverOut?.(e)
|
|
305
|
-
}}
|
|
306
|
-
style={({ pressed }: PressableStateCallbackType) => [
|
|
307
|
-
headerStyle,
|
|
308
|
-
pressed ? headerPressedStyle : null,
|
|
309
|
-
headerHoverStyle,
|
|
310
|
-
headerFocusStyle,
|
|
311
|
-
]}
|
|
431
|
+
onPressIn={handlePressIn}
|
|
432
|
+
onPressOut={handlePressOut}
|
|
433
|
+
onFocus={handleFocus}
|
|
434
|
+
onBlur={handleBlur}
|
|
435
|
+
onHoverIn={handleHoverIn}
|
|
436
|
+
onHoverOut={handleHoverOut}
|
|
437
|
+
unstable_pressDelay={HEADER_PRESS_DELAY}
|
|
438
|
+
style={headerStyleCallback}
|
|
312
439
|
{...webProps}
|
|
313
440
|
>
|
|
314
441
|
{headerContent}
|
|
315
442
|
</Pressable>
|
|
316
443
|
) : (
|
|
317
|
-
<View style={headerStyle}>
|
|
444
|
+
<View style={tokens.headerStyle}>
|
|
318
445
|
{headerContent}
|
|
319
446
|
</View>
|
|
320
447
|
)}
|
|
321
|
-
{slot && slotDirection
|
|
322
|
-
<SlotGrid
|
|
323
|
-
items={cloneChildrenWithModes(flattenChildren(slot), modes)}
|
|
324
|
-
gap={sectionGap}
|
|
325
|
-
/>
|
|
326
|
-
)}
|
|
327
|
-
{slot && slotDirection === 'column' && (
|
|
328
|
-
<View style={{ flexDirection: 'column' as const, gap: slotGap }}>
|
|
329
|
-
{cloneChildrenWithModes(flattenChildren(slot), modes)}
|
|
330
|
-
</View>
|
|
331
|
-
)}
|
|
448
|
+
{slot && <SectionSlot slot={slot} modes={modes} direction={slotDirection} rowGap={tokens.sectionGap} columnGap={tokens.slotGap} />}
|
|
332
449
|
</View>
|
|
333
450
|
)
|
|
334
451
|
}
|
|
335
452
|
|
|
453
|
+
type SectionSlotProps = {
|
|
454
|
+
slot: React.ReactNode;
|
|
455
|
+
modes: Record<string, any>;
|
|
456
|
+
direction: 'row' | 'column';
|
|
457
|
+
rowGap: number;
|
|
458
|
+
columnGap: number;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Internal helper that processes the slot children once per (slot, modes) pair
|
|
463
|
+
* and dispatches to the row (SlotGrid) or column layout. Splitting this out of
|
|
464
|
+
* `Section` lets the parent re-render (e.g. for header press/hover state)
|
|
465
|
+
* without re-walking the slot tree via `cloneChildrenWithModes`.
|
|
466
|
+
*/
|
|
467
|
+
function SectionSlot({ slot, modes, direction, rowGap, columnGap }: SectionSlotProps) {
|
|
468
|
+
const processed = useMemo(
|
|
469
|
+
() => cloneChildrenWithModes(flattenChildren(slot), modes),
|
|
470
|
+
[slot, modes]
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
const columnContainerStyle = useMemo<ViewStyle>(
|
|
474
|
+
() => ({ flexDirection: 'column', gap: columnGap }),
|
|
475
|
+
[columnGap]
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
if (direction === 'row') {
|
|
479
|
+
return <SlotGrid items={processed} gap={rowGap} />
|
|
480
|
+
}
|
|
481
|
+
return <View style={columnContainerStyle}>{processed}</View>
|
|
482
|
+
}
|
|
483
|
+
|
|
336
484
|
type SectionBentoProps = {
|
|
337
485
|
navSlot?: React.ReactNode;
|
|
338
486
|
upiSlot?: React.ReactNode;
|
|
@@ -368,12 +516,15 @@ type SectionBentoProps = {
|
|
|
368
516
|
* @param {string} [props.accessibilityLabel] - Accessibility label for the section
|
|
369
517
|
* @param {string} [props.accessibilityHint] - Additional accessibility hint for screen readers
|
|
370
518
|
*/
|
|
519
|
+
const sectionBentoUpiRowStyle: ViewStyle = { flexDirection: 'row', gap: 8 }
|
|
520
|
+
|
|
371
521
|
function SectionBento({
|
|
372
522
|
navSlot,
|
|
373
523
|
upiSlot,
|
|
374
|
-
modes =
|
|
524
|
+
modes = EMPTY_MODES,
|
|
375
525
|
style,
|
|
376
|
-
|
|
526
|
+
// Same rationale as Section: accepted on the type but unused internally.
|
|
527
|
+
accessibilityLabel: _accessibilityLabel,
|
|
377
528
|
accessibilityHint,
|
|
378
529
|
...rest
|
|
379
530
|
}: SectionBentoProps) {
|
|
@@ -384,21 +535,26 @@ function SectionBento({
|
|
|
384
535
|
const paddingVertical = getVariableByName('section/padding/vertical', modes) || 16
|
|
385
536
|
const radius = getVariableByName('section/radius', modes) || 12
|
|
386
537
|
|
|
387
|
-
const containerStyle =
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
538
|
+
const containerStyle = useMemo<ViewStyle>(
|
|
539
|
+
() => ({
|
|
540
|
+
backgroundColor,
|
|
541
|
+
paddingHorizontal,
|
|
542
|
+
paddingVertical,
|
|
543
|
+
borderRadius: radius,
|
|
544
|
+
gap,
|
|
545
|
+
}),
|
|
546
|
+
[backgroundColor, paddingHorizontal, paddingVertical, radius, gap]
|
|
547
|
+
)
|
|
394
548
|
|
|
395
|
-
const processedNavSlot =
|
|
396
|
-
? cloneChildrenWithModes(flattenChildren(navSlot), modes)
|
|
397
|
-
|
|
549
|
+
const processedNavSlot = useMemo(
|
|
550
|
+
() => (navSlot ? cloneChildrenWithModes(flattenChildren(navSlot), modes) : null),
|
|
551
|
+
[navSlot, modes]
|
|
552
|
+
)
|
|
398
553
|
|
|
399
|
-
const processedUpiSlot =
|
|
400
|
-
? cloneChildrenWithModes(flattenChildren(upiSlot), modes)
|
|
401
|
-
|
|
554
|
+
const processedUpiSlot = useMemo(
|
|
555
|
+
() => (upiSlot ? cloneChildrenWithModes(flattenChildren(upiSlot), modes) : null),
|
|
556
|
+
[upiSlot, modes]
|
|
557
|
+
)
|
|
402
558
|
|
|
403
559
|
return (
|
|
404
560
|
<View
|
|
@@ -415,7 +571,7 @@ function SectionBento({
|
|
|
415
571
|
/>
|
|
416
572
|
)}
|
|
417
573
|
{processedUpiSlot && (
|
|
418
|
-
<View style={
|
|
574
|
+
<View style={sectionBentoUpiRowStyle}>
|
|
419
575
|
{processedUpiSlot}
|
|
420
576
|
</View>
|
|
421
577
|
)}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type TextStyle,
|
|
9
9
|
} from 'react-native'
|
|
10
10
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
11
|
+
import { EMPTY_MODES } from '../../utils/react-utils'
|
|
11
12
|
|
|
12
13
|
export type SegmentedControlItem = {
|
|
13
14
|
key: React.Key
|
|
@@ -33,7 +34,7 @@ function SegmentedControlSegment({
|
|
|
33
34
|
label,
|
|
34
35
|
active,
|
|
35
36
|
onPress,
|
|
36
|
-
modes =
|
|
37
|
+
modes = EMPTY_MODES,
|
|
37
38
|
}: {
|
|
38
39
|
label: string
|
|
39
40
|
active: boolean
|
|
@@ -117,7 +118,7 @@ function SegmentedControl({
|
|
|
117
118
|
selectedKey: controlledSelectedKey,
|
|
118
119
|
defaultSelectedKey,
|
|
119
120
|
onSelectionChange,
|
|
120
|
-
modes =
|
|
121
|
+
modes = EMPTY_MODES,
|
|
121
122
|
style,
|
|
122
123
|
}: SegmentedControlProps) {
|
|
123
124
|
const isControlled = controlledSelectedKey !== undefined
|