jfs-components 0.0.21 → 0.0.25
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/assets/fonts/JioType Var.ttf +0 -0
- package/lib/commonjs/components/Accordion/Accordion.js +1 -1
- package/lib/commonjs/components/Accordion/Accordion.js.map +1 -1
- package/lib/commonjs/components/ActionFooter/ActionFooter.js +2 -2
- package/lib/commonjs/components/ActionFooter/ActionFooter.js.map +1 -1
- package/lib/commonjs/components/AppBar/AppBar.js +2 -2
- package/lib/commonjs/components/AppBar/AppBar.js.map +1 -1
- package/lib/commonjs/components/Avatar/Avatar.js +2 -2
- package/lib/commonjs/components/Avatar/Avatar.js.map +1 -1
- package/lib/commonjs/components/Badge/Badge.js +1 -1
- package/lib/commonjs/components/Badge/Badge.js.map +1 -1
- package/lib/commonjs/components/BottomNav/BottomNav.js +2 -2
- package/lib/commonjs/components/BottomNav/BottomNav.js.map +1 -1
- package/lib/commonjs/components/BottomNavItem/BottomNavItem.js +2 -2
- package/lib/commonjs/components/BottomNavItem/BottomNavItem.js.map +1 -1
- package/lib/commonjs/components/Button/Button.js +1 -1
- package/lib/commonjs/components/Button/Button.js.map +1 -1
- package/lib/commonjs/components/Carousel/Carousel.js +341 -0
- package/lib/commonjs/components/Carousel/Carousel.js.map +1 -0
- package/lib/commonjs/components/Carousel/Carousel.mdx +154 -0
- package/lib/commonjs/components/Disclaimer/Disclaimer.js +1 -1
- package/lib/commonjs/components/Disclaimer/Disclaimer.js.map +1 -1
- package/lib/commonjs/components/Divider/Divider.js +2 -2
- package/lib/commonjs/components/Divider/Divider.js.map +1 -1
- package/lib/commonjs/components/Drawer/Drawer.js +10 -3
- package/lib/commonjs/components/Drawer/Drawer.js.map +1 -1
- package/lib/commonjs/components/FilterBar/FilterBar.js +2 -2
- package/lib/commonjs/components/FilterBar/FilterBar.js.map +1 -1
- package/lib/commonjs/components/IconButton/IconButton.js +2 -2
- package/lib/commonjs/components/IconButton/IconButton.js.map +1 -1
- package/lib/commonjs/components/IconCapsule/IconCapsule.js +2 -2
- package/lib/commonjs/components/IconCapsule/IconCapsule.js.map +1 -1
- package/lib/commonjs/components/LazyList/LazyList.js +2 -2
- package/lib/commonjs/components/LazyList/LazyList.js.map +1 -1
- package/lib/commonjs/components/ListGroup/ListGroup.js +1 -1
- package/lib/commonjs/components/ListGroup/ListGroup.js.map +1 -1
- package/lib/commonjs/components/ListItem/ListItem.js +5 -5
- package/lib/commonjs/components/ListItem/ListItem.js.map +1 -1
- package/lib/commonjs/components/MerchantProfile/MerchantProfile.js +1 -1
- package/lib/commonjs/components/MerchantProfile/MerchantProfile.js.map +1 -1
- package/lib/commonjs/components/MoneyValue/MoneyValue.js +1 -1
- package/lib/commonjs/components/MoneyValue/MoneyValue.js.map +1 -1
- package/lib/commonjs/components/NavArrow/NavArrow.js +1 -1
- package/lib/commonjs/components/NavArrow/NavArrow.js.map +1 -1
- package/lib/commonjs/components/PageTitle/PageTitle.js +1 -1
- package/lib/commonjs/components/PageTitle/PageTitle.js.map +1 -1
- package/lib/commonjs/components/RadioButton/RadioButton.js +194 -0
- package/lib/commonjs/components/RadioButton/RadioButton.js.map +1 -0
- package/lib/commonjs/components/RadioButton/RadioButton.mdx +92 -0
- package/lib/commonjs/components/Section/Section.js +4 -4
- package/lib/commonjs/components/Section/Section.js.map +1 -1
- package/lib/commonjs/components/TextInput/TextInput.js +1 -1
- package/lib/commonjs/components/TextInput/TextInput.js.map +1 -1
- package/lib/commonjs/components/TransactionDetails/TransactionDetails.js +2 -2
- package/lib/commonjs/components/TransactionDetails/TransactionDetails.js.map +1 -1
- package/lib/commonjs/components/UpiHandle/UpiHandle.js +4 -2
- package/lib/commonjs/components/UpiHandle/UpiHandle.js.map +1 -1
- package/lib/commonjs/components/index.js +7 -0
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/commonjs/design-tokens/figma-variables-resolver.js +9 -3
- package/lib/commonjs/design-tokens/figma-variables-resolver.js.map +2 -1
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/assets/fonts/JioType Var.ttf +0 -0
- package/lib/module/components/Accordion/Accordion.js +1 -1
- package/lib/module/components/Accordion/Accordion.js.map +1 -1
- package/lib/module/components/ActionFooter/ActionFooter.js +2 -2
- package/lib/module/components/ActionFooter/ActionFooter.js.map +1 -1
- package/lib/module/components/AppBar/AppBar.js +2 -2
- package/lib/module/components/AppBar/AppBar.js.map +1 -1
- package/lib/module/components/Avatar/Avatar.js +2 -2
- package/lib/module/components/Avatar/Avatar.js.map +1 -1
- package/lib/module/components/Badge/Badge.js +1 -1
- package/lib/module/components/Badge/Badge.js.map +1 -1
- package/lib/module/components/BottomNav/BottomNav.js +2 -2
- package/lib/module/components/BottomNav/BottomNav.js.map +1 -1
- package/lib/module/components/BottomNavItem/BottomNavItem.js +2 -2
- package/lib/module/components/BottomNavItem/BottomNavItem.js.map +1 -1
- package/lib/module/components/Button/Button.js +1 -1
- package/lib/module/components/Button/Button.js.map +1 -1
- package/lib/module/components/Carousel/Carousel.js +333 -0
- package/lib/module/components/Carousel/Carousel.js.map +1 -0
- package/lib/module/components/Carousel/Carousel.mdx +154 -0
- package/lib/module/components/Disclaimer/Disclaimer.js +1 -1
- package/lib/module/components/Disclaimer/Disclaimer.js.map +1 -1
- package/lib/module/components/Divider/Divider.js +2 -2
- package/lib/module/components/Divider/Divider.js.map +1 -1
- package/lib/module/components/Drawer/Drawer.js +11 -4
- package/lib/module/components/Drawer/Drawer.js.map +1 -1
- package/lib/module/components/FilterBar/FilterBar.js +2 -2
- package/lib/module/components/FilterBar/FilterBar.js.map +1 -1
- package/lib/module/components/IconButton/IconButton.js +2 -2
- package/lib/module/components/IconButton/IconButton.js.map +1 -1
- package/lib/module/components/IconCapsule/IconCapsule.js +2 -2
- package/lib/module/components/IconCapsule/IconCapsule.js.map +1 -1
- package/lib/module/components/LazyList/LazyList.js +2 -2
- package/lib/module/components/LazyList/LazyList.js.map +1 -1
- package/lib/module/components/ListGroup/ListGroup.js +1 -1
- package/lib/module/components/ListGroup/ListGroup.js.map +1 -1
- package/lib/module/components/ListItem/ListItem.js +5 -5
- package/lib/module/components/ListItem/ListItem.js.map +1 -1
- package/lib/module/components/MerchantProfile/MerchantProfile.js +1 -1
- package/lib/module/components/MerchantProfile/MerchantProfile.js.map +1 -1
- package/lib/module/components/MoneyValue/MoneyValue.js +1 -1
- package/lib/module/components/MoneyValue/MoneyValue.js.map +1 -1
- package/lib/module/components/NavArrow/NavArrow.js +1 -1
- package/lib/module/components/NavArrow/NavArrow.js.map +1 -1
- package/lib/module/components/PageTitle/PageTitle.js +1 -1
- package/lib/module/components/PageTitle/PageTitle.js.map +1 -1
- package/lib/module/components/RadioButton/RadioButton.js +188 -0
- package/lib/module/components/RadioButton/RadioButton.js.map +1 -0
- package/lib/module/components/RadioButton/RadioButton.mdx +92 -0
- package/lib/module/components/Section/Section.js +4 -4
- package/lib/module/components/Section/Section.js.map +1 -1
- package/lib/module/components/TextInput/TextInput.js +1 -1
- package/lib/module/components/TextInput/TextInput.js.map +1 -1
- package/lib/module/components/TransactionDetails/TransactionDetails.js +2 -2
- package/lib/module/components/TransactionDetails/TransactionDetails.js.map +1 -1
- package/lib/module/components/UpiHandle/UpiHandle.js +4 -2
- package/lib/module/components/UpiHandle/UpiHandle.js.map +1 -1
- package/lib/module/components/index.js +1 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/design-tokens/figma-variables-resolver.js +2 -2
- package/lib/module/design-tokens/figma-variables-resolver.js.map +1 -2
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/components/ActionFooter/ActionFooter.d.ts.map +1 -1
- package/lib/typescript/components/BottomNav/BottomNav.d.ts.map +1 -1
- package/lib/typescript/components/Carousel/Carousel.d.ts +48 -0
- package/lib/typescript/components/Carousel/Carousel.d.ts.map +1 -0
- package/lib/typescript/components/Drawer/Drawer.d.ts.map +1 -1
- package/lib/typescript/components/IconButton/IconButton.d.ts +1 -1
- package/lib/typescript/components/IconCapsule/IconCapsule.d.ts +1 -1
- package/lib/typescript/components/LazyList/LazyList.d.ts.map +1 -1
- package/lib/typescript/components/RadioButton/RadioButton.d.ts +30 -0
- package/lib/typescript/components/RadioButton/RadioButton.d.ts.map +1 -0
- package/lib/typescript/components/Section/Section.d.ts.map +1 -1
- package/lib/typescript/components/UpiHandle/UpiHandle.d.ts +4 -2
- package/lib/typescript/components/UpiHandle/UpiHandle.d.ts.map +1 -1
- package/lib/typescript/components/index.d.ts +1 -0
- package/lib/typescript/components/index.d.ts.map +1 -1
- package/lib/typescript/icons/registry.d.ts +1 -1
- package/package.json +2 -2
- package/src/assets/fonts/JioType Var.ttf +0 -0
- package/src/components/.token-metadata.json +72 -0
- package/src/components/Accordion/Accordion.tsx +1 -1
- package/src/components/ActionFooter/ActionFooter.tsx +2 -2
- package/src/components/AppBar/AppBar.tsx +2 -2
- package/src/components/Avatar/Avatar.tsx +2 -2
- package/src/components/Badge/Badge.tsx +1 -1
- package/src/components/BottomNav/BottomNav.tsx +2 -2
- package/src/components/BottomNavItem/BottomNavItem.tsx +2 -2
- package/src/components/Button/Button.tsx +1 -1
- package/src/components/Carousel/Carousel.mdx +154 -0
- package/src/components/Carousel/Carousel.tsx +472 -0
- package/src/components/Disclaimer/Disclaimer.tsx +1 -1
- package/src/components/Divider/Divider.tsx +2 -2
- package/src/components/Drawer/Drawer.tsx +11 -3
- package/src/components/FilterBar/FilterBar.tsx +2 -2
- package/src/components/IconButton/IconButton.tsx +2 -2
- package/src/components/IconCapsule/IconCapsule.tsx +2 -2
- package/src/components/LazyList/LazyList.tsx +2 -2
- package/src/components/ListGroup/ListGroup.tsx +1 -1
- package/src/components/ListItem/ListItem.tsx +5 -5
- package/src/components/MerchantProfile/MerchantProfile.tsx +1 -1
- package/src/components/MoneyValue/MoneyValue.tsx +1 -1
- package/src/components/NavArrow/NavArrow.tsx +1 -1
- package/src/components/PageTitle/PageTitle.tsx +1 -1
- package/src/components/RadioButton/RadioButton.mdx +92 -0
- package/src/components/RadioButton/RadioButton.tsx +226 -0
- package/src/components/Section/Section.tsx +4 -4
- package/src/components/TextInput/TextInput.tsx +1 -1
- package/src/components/TransactionDetails/TransactionDetails.tsx +2 -2
- package/src/components/UpiHandle/UpiHandle.tsx +6 -3
- package/src/components/index.ts +1 -0
- package/src/icons/registry.ts +1 -1
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
createContext,
|
|
3
|
+
useContext,
|
|
4
|
+
useRef,
|
|
5
|
+
useState,
|
|
6
|
+
useEffect,
|
|
7
|
+
useCallback,
|
|
8
|
+
useMemo,
|
|
9
|
+
} from 'react'
|
|
10
|
+
import {
|
|
11
|
+
View,
|
|
12
|
+
ScrollView,
|
|
13
|
+
Animated,
|
|
14
|
+
Pressable,
|
|
15
|
+
type ViewStyle,
|
|
16
|
+
type StyleProp,
|
|
17
|
+
type NativeSyntheticEvent,
|
|
18
|
+
type NativeScrollEvent,
|
|
19
|
+
type LayoutChangeEvent,
|
|
20
|
+
} from 'react-native'
|
|
21
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Context
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
interface CarouselContextValue {
|
|
28
|
+
modes: Record<string, any>
|
|
29
|
+
activeIndex: number
|
|
30
|
+
totalItems: number
|
|
31
|
+
goTo: (index: number) => void
|
|
32
|
+
goNext: () => void
|
|
33
|
+
goPrev: () => void
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const CarouselContext = createContext<CarouselContextValue>({
|
|
37
|
+
modes: {},
|
|
38
|
+
activeIndex: 0,
|
|
39
|
+
totalItems: 0,
|
|
40
|
+
goTo: () => {},
|
|
41
|
+
goNext: () => {},
|
|
42
|
+
goPrev: () => {},
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Types
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
export interface CarouselProps {
|
|
50
|
+
/** Content items to display (should be Carousel.Item or any React nodes). */
|
|
51
|
+
children?: React.ReactNode
|
|
52
|
+
/** Modes object for design-token resolution. */
|
|
53
|
+
modes?: Record<string, any>
|
|
54
|
+
/** Enable auto-play. Default: false. */
|
|
55
|
+
autoPlay?: boolean
|
|
56
|
+
/** Auto-play interval in ms. Default: 4000. */
|
|
57
|
+
autoPlayInterval?: number
|
|
58
|
+
/** Show pagination dots. Default: true. */
|
|
59
|
+
showPagination?: boolean
|
|
60
|
+
/** Enable looping (jumps back to first after last). Default: true. */
|
|
61
|
+
loop?: boolean
|
|
62
|
+
/** Gap between items in px. Resolved from token `carousel/gap` or defaults to 12. */
|
|
63
|
+
gap?: number
|
|
64
|
+
/** Width of each carousel item. When undefined, items take full container width. */
|
|
65
|
+
itemWidth?: number
|
|
66
|
+
/** Horizontal padding around the carousel track, allowing peek of adjacent items. Defaults to 0. */
|
|
67
|
+
peekOffset?: number
|
|
68
|
+
/** Called when the active index changes. */
|
|
69
|
+
onIndexChange?: (index: number) => void
|
|
70
|
+
/** Style overrides for the outermost container. */
|
|
71
|
+
style?: StyleProp<ViewStyle>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Carousel
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
export function Carousel({
|
|
79
|
+
children,
|
|
80
|
+
modes = {},
|
|
81
|
+
autoPlay = false,
|
|
82
|
+
autoPlayInterval = 4000,
|
|
83
|
+
showPagination = true,
|
|
84
|
+
loop = true,
|
|
85
|
+
gap: gapProp,
|
|
86
|
+
itemWidth: itemWidthProp,
|
|
87
|
+
peekOffset: peekOffsetProp,
|
|
88
|
+
onIndexChange,
|
|
89
|
+
style,
|
|
90
|
+
}: CarouselProps) {
|
|
91
|
+
// ---- Token resolution ----
|
|
92
|
+
const tokenGap = parseFloat(
|
|
93
|
+
getVariableByName('carousel/gap', modes) || '12',
|
|
94
|
+
)
|
|
95
|
+
const gap = gapProp ?? tokenGap
|
|
96
|
+
|
|
97
|
+
const tokenPeekOffset = parseFloat(
|
|
98
|
+
getVariableByName('carousel/peekOffset', modes) || '0',
|
|
99
|
+
)
|
|
100
|
+
const peekOffset = peekOffsetProp ?? tokenPeekOffset
|
|
101
|
+
|
|
102
|
+
const containerPaddingH = parseFloat(
|
|
103
|
+
getVariableByName('carousel/padding/horizontal', modes) || '0',
|
|
104
|
+
)
|
|
105
|
+
const containerPaddingV = parseFloat(
|
|
106
|
+
getVariableByName('carousel/padding/vertical', modes) || '0',
|
|
107
|
+
)
|
|
108
|
+
const paginationGap = parseFloat(
|
|
109
|
+
getVariableByName('carousel/pagination/gap', modes) || '12',
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
// ---- Refs & state ----
|
|
113
|
+
const scrollRef = useRef<ScrollView>(null)
|
|
114
|
+
const [activeIndex, setActiveIndex] = useState(0)
|
|
115
|
+
const [containerWidth, setContainerWidth] = useState(0)
|
|
116
|
+
const autoPlayTimer = useRef<ReturnType<typeof setInterval> | null>(null)
|
|
117
|
+
const userInteracting = useRef(false)
|
|
118
|
+
|
|
119
|
+
// Flatten children so we can count items
|
|
120
|
+
const items = useMemo(
|
|
121
|
+
() => React.Children.toArray(children).filter(React.isValidElement),
|
|
122
|
+
[children],
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
const totalItems = items.length
|
|
126
|
+
|
|
127
|
+
// Effective item width: provided prop, or full container width minus peek offsets
|
|
128
|
+
const effectiveItemWidth = useMemo(() => {
|
|
129
|
+
if (itemWidthProp != null) return itemWidthProp
|
|
130
|
+
if (containerWidth === 0) return 0
|
|
131
|
+
// Full-width minus peekOffset on each side
|
|
132
|
+
return containerWidth - peekOffset * 2 - containerPaddingH * 2
|
|
133
|
+
}, [itemWidthProp, containerWidth, peekOffset, containerPaddingH])
|
|
134
|
+
|
|
135
|
+
// Snap interval = item width + gap
|
|
136
|
+
const snapInterval = effectiveItemWidth + gap
|
|
137
|
+
|
|
138
|
+
// ---- Navigation helpers ----
|
|
139
|
+
const scrollToIndex = useCallback(
|
|
140
|
+
(index: number, animated = true) => {
|
|
141
|
+
if (totalItems === 0 || snapInterval === 0) return
|
|
142
|
+
let targetIndex = index
|
|
143
|
+
if (loop) {
|
|
144
|
+
targetIndex = ((index % totalItems) + totalItems) % totalItems
|
|
145
|
+
} else {
|
|
146
|
+
targetIndex = Math.max(0, Math.min(index, totalItems - 1))
|
|
147
|
+
}
|
|
148
|
+
scrollRef.current?.scrollTo({
|
|
149
|
+
x: targetIndex * snapInterval,
|
|
150
|
+
animated,
|
|
151
|
+
})
|
|
152
|
+
setActiveIndex(targetIndex)
|
|
153
|
+
onIndexChange?.(targetIndex)
|
|
154
|
+
},
|
|
155
|
+
[totalItems, snapInterval, loop, onIndexChange],
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
const goTo = useCallback(
|
|
159
|
+
(index: number) => scrollToIndex(index),
|
|
160
|
+
[scrollToIndex],
|
|
161
|
+
)
|
|
162
|
+
const goNext = useCallback(
|
|
163
|
+
() => scrollToIndex(activeIndex + 1),
|
|
164
|
+
[scrollToIndex, activeIndex],
|
|
165
|
+
)
|
|
166
|
+
const goPrev = useCallback(
|
|
167
|
+
() => scrollToIndex(activeIndex - 1),
|
|
168
|
+
[scrollToIndex, activeIndex],
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
// ---- Scroll event handler ----
|
|
172
|
+
const handleScroll = useCallback(
|
|
173
|
+
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
174
|
+
if (snapInterval === 0) return
|
|
175
|
+
const offsetX = event.nativeEvent.contentOffset.x
|
|
176
|
+
const newIndex = Math.round(offsetX / snapInterval)
|
|
177
|
+
const clampedIndex = Math.max(0, Math.min(newIndex, totalItems - 1))
|
|
178
|
+
if (clampedIndex !== activeIndex) {
|
|
179
|
+
setActiveIndex(clampedIndex)
|
|
180
|
+
onIndexChange?.(clampedIndex)
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
[snapInterval, totalItems, activeIndex, onIndexChange],
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
// ---- Auto-play ----
|
|
187
|
+
const clearAutoPlay = useCallback(() => {
|
|
188
|
+
if (autoPlayTimer.current != null) {
|
|
189
|
+
clearInterval(autoPlayTimer.current)
|
|
190
|
+
autoPlayTimer.current = null
|
|
191
|
+
}
|
|
192
|
+
}, [])
|
|
193
|
+
|
|
194
|
+
const startAutoPlay = useCallback(() => {
|
|
195
|
+
if (!autoPlay || totalItems <= 1) return
|
|
196
|
+
clearAutoPlay()
|
|
197
|
+
autoPlayTimer.current = setInterval(() => {
|
|
198
|
+
if (!userInteracting.current) {
|
|
199
|
+
setActiveIndex((prev) => {
|
|
200
|
+
const next = loop
|
|
201
|
+
? (prev + 1) % totalItems
|
|
202
|
+
: Math.min(prev + 1, totalItems - 1)
|
|
203
|
+
scrollRef.current?.scrollTo({
|
|
204
|
+
x: next * snapInterval,
|
|
205
|
+
animated: true,
|
|
206
|
+
})
|
|
207
|
+
onIndexChange?.(next)
|
|
208
|
+
return next
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
}, autoPlayInterval)
|
|
212
|
+
}, [
|
|
213
|
+
autoPlay,
|
|
214
|
+
totalItems,
|
|
215
|
+
loop,
|
|
216
|
+
snapInterval,
|
|
217
|
+
autoPlayInterval,
|
|
218
|
+
clearAutoPlay,
|
|
219
|
+
onIndexChange,
|
|
220
|
+
])
|
|
221
|
+
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
startAutoPlay()
|
|
224
|
+
return clearAutoPlay
|
|
225
|
+
}, [startAutoPlay, clearAutoPlay])
|
|
226
|
+
|
|
227
|
+
const handleScrollBeginDrag = useCallback(() => {
|
|
228
|
+
userInteracting.current = true
|
|
229
|
+
}, [])
|
|
230
|
+
|
|
231
|
+
const handleScrollEndDrag = useCallback(() => {
|
|
232
|
+
userInteracting.current = false
|
|
233
|
+
// Reset autoplay timer after interaction
|
|
234
|
+
if (autoPlay) startAutoPlay()
|
|
235
|
+
}, [autoPlay, startAutoPlay])
|
|
236
|
+
|
|
237
|
+
// ---- Layout ----
|
|
238
|
+
const handleLayout = useCallback((e: LayoutChangeEvent) => {
|
|
239
|
+
setContainerWidth(e.nativeEvent.layout.width)
|
|
240
|
+
}, [])
|
|
241
|
+
|
|
242
|
+
// ---- Context value ----
|
|
243
|
+
const contextValue = useMemo<CarouselContextValue>(
|
|
244
|
+
() => ({
|
|
245
|
+
modes,
|
|
246
|
+
activeIndex,
|
|
247
|
+
totalItems,
|
|
248
|
+
goTo,
|
|
249
|
+
goNext,
|
|
250
|
+
goPrev,
|
|
251
|
+
}),
|
|
252
|
+
[modes, activeIndex, totalItems, goTo, goNext, goPrev],
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
// ---- Render ----
|
|
256
|
+
const outerStyle: ViewStyle = {
|
|
257
|
+
paddingVertical: containerPaddingV,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const contentContainerStyle: ViewStyle = {
|
|
261
|
+
paddingHorizontal: peekOffset + containerPaddingH,
|
|
262
|
+
gap,
|
|
263
|
+
// Align items to the start so snap works correctly
|
|
264
|
+
alignItems: 'flex-start',
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<CarouselContext.Provider value={contextValue}>
|
|
269
|
+
<View style={[outerStyle, style]} onLayout={handleLayout}>
|
|
270
|
+
<ScrollView
|
|
271
|
+
ref={scrollRef}
|
|
272
|
+
horizontal
|
|
273
|
+
pagingEnabled={false}
|
|
274
|
+
showsHorizontalScrollIndicator={false}
|
|
275
|
+
decelerationRate="fast"
|
|
276
|
+
snapToInterval={snapInterval > 0 ? snapInterval : undefined}
|
|
277
|
+
snapToAlignment="start"
|
|
278
|
+
contentContainerStyle={contentContainerStyle}
|
|
279
|
+
onScroll={handleScroll}
|
|
280
|
+
scrollEventThrottle={16}
|
|
281
|
+
onScrollBeginDrag={handleScrollBeginDrag}
|
|
282
|
+
onScrollEndDrag={handleScrollEndDrag}
|
|
283
|
+
|
|
284
|
+
>
|
|
285
|
+
{items.map((child, index) => {
|
|
286
|
+
const itemStyle: ViewStyle = {
|
|
287
|
+
width: effectiveItemWidth > 0 ? effectiveItemWidth : undefined,
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Pass modes down to children
|
|
291
|
+
const childWithModes = React.isValidElement(child)
|
|
292
|
+
? React.cloneElement(child as React.ReactElement<any>, {
|
|
293
|
+
modes: {
|
|
294
|
+
...((child.props as any)?.modes || {}),
|
|
295
|
+
...modes,
|
|
296
|
+
},
|
|
297
|
+
style: [itemStyle, (child.props as any)?.style],
|
|
298
|
+
})
|
|
299
|
+
: child
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<View key={index} style={itemStyle}>
|
|
303
|
+
{childWithModes}
|
|
304
|
+
</View>
|
|
305
|
+
)
|
|
306
|
+
})}
|
|
307
|
+
</ScrollView>
|
|
308
|
+
|
|
309
|
+
{showPagination && totalItems > 1 && (
|
|
310
|
+
<Pagination
|
|
311
|
+
modes={modes}
|
|
312
|
+
style={{ marginTop: paginationGap }}
|
|
313
|
+
/>
|
|
314
|
+
)}
|
|
315
|
+
</View>
|
|
316
|
+
</CarouselContext.Provider>
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
// Carousel.Item
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
|
|
324
|
+
export interface CarouselItemProps {
|
|
325
|
+
children?: React.ReactNode
|
|
326
|
+
modes?: Record<string, any>
|
|
327
|
+
style?: StyleProp<ViewStyle>
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Optional wrapper for carousel items. Not strictly required — any
|
|
332
|
+
* React node works as a direct child of `<Carousel>`.
|
|
333
|
+
*/
|
|
334
|
+
export function Item({ children, modes: _modes, style }: CarouselItemProps) {
|
|
335
|
+
return <View style={style}>{children}</View>
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
// Carousel.Pagination
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
|
|
342
|
+
export interface PaginationProps {
|
|
343
|
+
modes?: Record<string, any>
|
|
344
|
+
style?: StyleProp<ViewStyle>
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function Pagination({ modes: propModes, style }: PaginationProps) {
|
|
348
|
+
const { modes: ctxModes, activeIndex, totalItems, goTo } =
|
|
349
|
+
useContext(CarouselContext)
|
|
350
|
+
const modes = propModes || ctxModes || {}
|
|
351
|
+
|
|
352
|
+
// Token resolution for dots
|
|
353
|
+
const dotSize = parseFloat(
|
|
354
|
+
getVariableByName('carousel/pagination/dotSize', modes) || '8',
|
|
355
|
+
)
|
|
356
|
+
const dotActiveWidth = parseFloat(
|
|
357
|
+
getVariableByName('carousel/pagination/dotActiveWidth', modes) || '24',
|
|
358
|
+
)
|
|
359
|
+
const dotGap = parseFloat(
|
|
360
|
+
getVariableByName('carousel/pagination/dotGap', modes) || '8',
|
|
361
|
+
)
|
|
362
|
+
const dotColor =
|
|
363
|
+
(getVariableByName('carousel/pagination/dotColor', modes) as string) ||
|
|
364
|
+
'rgba(255,255,255,0.35)'
|
|
365
|
+
const dotActiveColor =
|
|
366
|
+
(getVariableByName(
|
|
367
|
+
'carousel/pagination/dotActiveColor',
|
|
368
|
+
modes,
|
|
369
|
+
) as string) || '#ffffff'
|
|
370
|
+
const dotRadius = parseFloat(
|
|
371
|
+
getVariableByName('carousel/pagination/dotRadius', modes) || '4',
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
const containerStyle: ViewStyle = {
|
|
375
|
+
flexDirection: 'row',
|
|
376
|
+
justifyContent: 'center',
|
|
377
|
+
alignItems: 'center',
|
|
378
|
+
gap: dotGap,
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return (
|
|
382
|
+
<View
|
|
383
|
+
style={[containerStyle, style]}
|
|
384
|
+
accessibilityRole="tablist"
|
|
385
|
+
accessibilityLabel={undefined}
|
|
386
|
+
>
|
|
387
|
+
{Array.from({ length: totalItems }).map((_, i) => {
|
|
388
|
+
const isActive = i === activeIndex
|
|
389
|
+
return (
|
|
390
|
+
<Pressable
|
|
391
|
+
key={i}
|
|
392
|
+
onPress={() => goTo(i)}
|
|
393
|
+
accessibilityRole="tab"
|
|
394
|
+
accessibilityState={{ selected: isActive }}
|
|
395
|
+
accessibilityLabel={undefined}
|
|
396
|
+
>
|
|
397
|
+
<AnimatedDot
|
|
398
|
+
isActive={isActive}
|
|
399
|
+
size={dotSize}
|
|
400
|
+
activeWidth={dotActiveWidth}
|
|
401
|
+
radius={dotRadius}
|
|
402
|
+
color={dotColor}
|
|
403
|
+
activeColor={dotActiveColor}
|
|
404
|
+
/>
|
|
405
|
+
</Pressable>
|
|
406
|
+
)
|
|
407
|
+
})}
|
|
408
|
+
</View>
|
|
409
|
+
)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ---------------------------------------------------------------------------
|
|
413
|
+
// AnimatedDot (internal)
|
|
414
|
+
// ---------------------------------------------------------------------------
|
|
415
|
+
|
|
416
|
+
interface AnimatedDotProps {
|
|
417
|
+
isActive: boolean
|
|
418
|
+
size: number
|
|
419
|
+
activeWidth: number
|
|
420
|
+
radius: number
|
|
421
|
+
color: string
|
|
422
|
+
activeColor: string
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function AnimatedDot({
|
|
426
|
+
isActive,
|
|
427
|
+
size,
|
|
428
|
+
activeWidth,
|
|
429
|
+
radius,
|
|
430
|
+
color,
|
|
431
|
+
activeColor,
|
|
432
|
+
}: AnimatedDotProps) {
|
|
433
|
+
const animValue = useRef(new Animated.Value(isActive ? 1 : 0)).current
|
|
434
|
+
|
|
435
|
+
useEffect(() => {
|
|
436
|
+
Animated.timing(animValue, {
|
|
437
|
+
toValue: isActive ? 1 : 0,
|
|
438
|
+
duration: 250,
|
|
439
|
+
useNativeDriver: false,
|
|
440
|
+
}).start()
|
|
441
|
+
}, [isActive, animValue])
|
|
442
|
+
|
|
443
|
+
const width = animValue.interpolate({
|
|
444
|
+
inputRange: [0, 1],
|
|
445
|
+
outputRange: [size, activeWidth],
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
const backgroundColor = animValue.interpolate({
|
|
449
|
+
inputRange: [0, 1],
|
|
450
|
+
outputRange: [color, activeColor],
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
return (
|
|
454
|
+
<Animated.View
|
|
455
|
+
style={{
|
|
456
|
+
width,
|
|
457
|
+
height: size,
|
|
458
|
+
borderRadius: radius,
|
|
459
|
+
backgroundColor,
|
|
460
|
+
}}
|
|
461
|
+
/>
|
|
462
|
+
)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ---------------------------------------------------------------------------
|
|
466
|
+
// Attach sub-components
|
|
467
|
+
// ---------------------------------------------------------------------------
|
|
468
|
+
|
|
469
|
+
Carousel.Item = Item
|
|
470
|
+
Carousel.Pagination = Pagination
|
|
471
|
+
|
|
472
|
+
export default Carousel
|
|
@@ -58,7 +58,7 @@ function Divider({
|
|
|
58
58
|
direction = 'horizontal',
|
|
59
59
|
modes = {},
|
|
60
60
|
style,
|
|
61
|
-
accessibilityLabel =
|
|
61
|
+
accessibilityLabel = undefined,
|
|
62
62
|
}: DividerProps) {
|
|
63
63
|
// Resolve design tokens
|
|
64
64
|
const size = getVariableByName('divider/size', modes) ?? 1
|
|
@@ -82,7 +82,7 @@ function Divider({
|
|
|
82
82
|
<View
|
|
83
83
|
style={[dividerStyle, style]}
|
|
84
84
|
accessibilityRole="none"
|
|
85
|
-
accessibilityLabel={
|
|
85
|
+
accessibilityLabel={undefined}
|
|
86
86
|
/>
|
|
87
87
|
)
|
|
88
88
|
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from 'react-native-gesture-handler'
|
|
9
9
|
import Animated, {
|
|
10
10
|
runOnJS,
|
|
11
|
+
useAnimatedProps,
|
|
11
12
|
useAnimatedScrollHandler,
|
|
12
13
|
useAnimatedStyle,
|
|
13
14
|
useSharedValue,
|
|
@@ -116,6 +117,13 @@ function Drawer({
|
|
|
116
117
|
const scrollY = useSharedValue(0)
|
|
117
118
|
const scrollRef = useRef(null)
|
|
118
119
|
|
|
120
|
+
// Dynamically disable top bounce so a downward swipe at the top of
|
|
121
|
+
// the content drags the whole drawer instead of rubber-banding the
|
|
122
|
+
// scroll view. Bottom bounce is preserved when the user has scrolled.
|
|
123
|
+
const animatedScrollProps = useAnimatedProps(() => ({
|
|
124
|
+
bounces: scrollY.value > 1,
|
|
125
|
+
}))
|
|
126
|
+
|
|
119
127
|
// We need to know if we are currently dragging the drawer or scrolling the content
|
|
120
128
|
// to prevent conflict.
|
|
121
129
|
const isDrawerActive = useSharedValue(false)
|
|
@@ -287,7 +295,7 @@ function Drawer({
|
|
|
287
295
|
]}
|
|
288
296
|
accessible={true}
|
|
289
297
|
accessibilityRole={'dialog' as any}
|
|
290
|
-
accessibilityLabel={
|
|
298
|
+
accessibilityLabel={undefined}
|
|
291
299
|
accessibilityHint={accessibilityHint || 'Swipe up to expand, swipe down to collapse'}
|
|
292
300
|
>
|
|
293
301
|
{/* Handle Area */}
|
|
@@ -330,8 +338,8 @@ function Drawer({
|
|
|
330
338
|
style={[styles.content, contentStyle]}
|
|
331
339
|
contentContainerStyle={[{ paddingBottom: paddingBottom + bottomInset, gap: drawerGap, flexDirection: 'column', alignItems: 'stretch' }, contentContainerStyle]}
|
|
332
340
|
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
|
|
333
|
-
|
|
334
|
-
alwaysBounceVertical={
|
|
341
|
+
animatedProps={animatedScrollProps}
|
|
342
|
+
alwaysBounceVertical={false}
|
|
335
343
|
overScrollMode="always"
|
|
336
344
|
onScroll={useAnimatedScrollHandler((event) => {
|
|
337
345
|
scrollY.value = event.contentOffset.y
|
|
@@ -140,7 +140,7 @@ function FilterBar({
|
|
|
140
140
|
{...(onChangeText !== undefined && { onChangeText })}
|
|
141
141
|
modes={modes}
|
|
142
142
|
style={fullWidthStyle}
|
|
143
|
-
accessibilityLabel={
|
|
143
|
+
accessibilityLabel={undefined}
|
|
144
144
|
{...(accessibilityHint !== undefined && { accessibilityHint })}
|
|
145
145
|
onFocus={() => setIsFocused(true)}
|
|
146
146
|
onBlur={() => setIsFocused(false)}
|
|
@@ -188,7 +188,7 @@ function FilterBar({
|
|
|
188
188
|
<View
|
|
189
189
|
style={[containerStyle, focusStyle, style]}
|
|
190
190
|
accessibilityRole="search"
|
|
191
|
-
accessibilityLabel={
|
|
191
|
+
accessibilityLabel={undefined}
|
|
192
192
|
accessibilityHint={accessibilityHint}
|
|
193
193
|
{...rest}
|
|
194
194
|
>
|
|
@@ -69,7 +69,7 @@ type IconButtonProps = SafePressableProps & {
|
|
|
69
69
|
* <IconButton modes={{"Appearance": "high"}} onPress={() => console.log('pressed')} />
|
|
70
70
|
*
|
|
71
71
|
* // With accessibility label
|
|
72
|
-
* <IconButton iconName="ic_search" accessibilityLabel=
|
|
72
|
+
* <IconButton iconName="ic_search" accessibilityLabel={undefined} accessibilityHint="Opens the search screen" />
|
|
73
73
|
* ```
|
|
74
74
|
*/
|
|
75
75
|
function IconButton({
|
|
@@ -157,7 +157,7 @@ function IconButton({
|
|
|
157
157
|
return (
|
|
158
158
|
<Pressable
|
|
159
159
|
accessibilityRole="button"
|
|
160
|
-
accessibilityLabel={
|
|
160
|
+
accessibilityLabel={undefined}
|
|
161
161
|
accessibilityHint={accessibilityHint}
|
|
162
162
|
accessibilityState={{
|
|
163
163
|
disabled,
|
|
@@ -36,7 +36,7 @@ type IconCapsuleProps = {
|
|
|
36
36
|
* <IconCapsule modes={{"Appearance": "Secondary"}} />
|
|
37
37
|
*
|
|
38
38
|
* // With accessibility label
|
|
39
|
-
* <IconCapsule iconName="ic_card" accessibilityLabel=
|
|
39
|
+
* <IconCapsule iconName="ic_card" accessibilityLabel={undefined} />
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
42
|
function IconCapsule({
|
|
@@ -80,7 +80,7 @@ function IconCapsule({
|
|
|
80
80
|
<View
|
|
81
81
|
style={containerStyle}
|
|
82
82
|
accessibilityRole={accessibilityRole}
|
|
83
|
-
accessibilityLabel={
|
|
83
|
+
accessibilityLabel={undefined}
|
|
84
84
|
{...rest}
|
|
85
85
|
>
|
|
86
86
|
<Icon
|
|
@@ -74,7 +74,7 @@ function LazyList({
|
|
|
74
74
|
listGroupsSlot,
|
|
75
75
|
modes = {},
|
|
76
76
|
style,
|
|
77
|
-
accessibilityLabel =
|
|
77
|
+
accessibilityLabel = undefined,
|
|
78
78
|
accessibilityHint,
|
|
79
79
|
...rest
|
|
80
80
|
}: LazyListProps) {
|
|
@@ -103,7 +103,7 @@ function LazyList({
|
|
|
103
103
|
<View
|
|
104
104
|
style={[containerStyle, style]}
|
|
105
105
|
accessibilityRole="list"
|
|
106
|
-
accessibilityLabel={
|
|
106
|
+
accessibilityLabel={undefined}
|
|
107
107
|
accessibilityHint={accessibilityHint}
|
|
108
108
|
{...rest}
|
|
109
109
|
>
|
|
@@ -211,7 +211,7 @@ function ListItem({
|
|
|
211
211
|
? processedLeading.length === 1
|
|
212
212
|
? processedLeading[0]
|
|
213
213
|
: processedLeading
|
|
214
|
-
: <IconCapsule modes={modes} accessibilityLabel={
|
|
214
|
+
: <IconCapsule modes={modes} accessibilityLabel={undefined} />
|
|
215
215
|
|
|
216
216
|
const renderSupportContent = () => {
|
|
217
217
|
if (supportSlot) {
|
|
@@ -290,7 +290,7 @@ function ListItem({
|
|
|
290
290
|
return (
|
|
291
291
|
<Pressable
|
|
292
292
|
accessibilityRole="button"
|
|
293
|
-
accessibilityLabel={
|
|
293
|
+
accessibilityLabel={undefined}
|
|
294
294
|
accessibilityHint={accessibilityHint}
|
|
295
295
|
accessibilityState={{
|
|
296
296
|
...accessibilityState
|
|
@@ -312,7 +312,7 @@ function ListItem({
|
|
|
312
312
|
return (
|
|
313
313
|
<View
|
|
314
314
|
accessibilityRole={undefined}
|
|
315
|
-
accessibilityLabel={
|
|
315
|
+
accessibilityLabel={undefined}
|
|
316
316
|
accessibilityHint={accessibilityHint}
|
|
317
317
|
style={[baseContainerStyle, sharedLayoutStyle, style]}
|
|
318
318
|
{...rest}
|
|
@@ -334,7 +334,7 @@ function ListItem({
|
|
|
334
334
|
return (
|
|
335
335
|
<Pressable
|
|
336
336
|
accessibilityRole="button"
|
|
337
|
-
accessibilityLabel={
|
|
337
|
+
accessibilityLabel={undefined}
|
|
338
338
|
accessibilityHint={accessibilityHint}
|
|
339
339
|
accessibilityState={{
|
|
340
340
|
...accessibilityState
|
|
@@ -356,7 +356,7 @@ function ListItem({
|
|
|
356
356
|
return (
|
|
357
357
|
<View
|
|
358
358
|
accessibilityRole={undefined}
|
|
359
|
-
accessibilityLabel={
|
|
359
|
+
accessibilityLabel={undefined}
|
|
360
360
|
accessibilityHint={accessibilityHint}
|
|
361
361
|
style={[baseContainerStyle, sharedLayoutStyle, style]}
|
|
362
362
|
{...rest}
|