overlapping-cards-scroll 0.1.0 → 0.1.2

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.
@@ -0,0 +1,102 @@
1
+ import type { ComponentProps, ComponentType, ReactNode } from 'react'
2
+ import type { StyleProp, TextStyle, ViewStyle } from 'react-native'
3
+ import type {
4
+ CardItem as OverlappingCardsScrollWebCardItem,
5
+ OverlappingCardsScroll,
6
+ OverlappingCardsScrollFocusTriggerProps as OverlappingCardsScrollWebFocusTriggerProps,
7
+ } from '../lib/OverlappingCardsScroll'
8
+
9
+ type OverlappingCardsScrollWebProps = ComponentProps<typeof OverlappingCardsScroll>
10
+
11
+ type OverlappingCardsScrollRNSharedProps = Omit<
12
+ OverlappingCardsScrollWebProps,
13
+ 'children' | 'items' | 'cardContainerStyle' | 'tabsComponent' | 'tabsContainerComponent'
14
+ >
15
+
16
+ export type OverlappingCardsScrollRNPageDotsPosition = NonNullable<
17
+ OverlappingCardsScrollWebProps['pageDotsPosition']
18
+ >
19
+
20
+ export type OverlappingCardsScrollRNFocusTriggerBehavior =
21
+ OverlappingCardsScrollWebFocusTriggerProps['behavior']
22
+
23
+ export type OverlappingCardsScrollRNFocusTransitionMode = NonNullable<
24
+ OverlappingCardsScrollWebFocusTriggerProps['transitionMode']
25
+ >
26
+
27
+ export type OverlappingCardsScrollRNSnapDecelerationRate = 'normal' | 'fast' | number
28
+
29
+ export type OverlappingCardsScrollRNItem = OverlappingCardsScrollWebCardItem
30
+
31
+ export type OverlappingCardsScrollRNTabsPosition = 'above' | 'below'
32
+
33
+ export interface OverlappingCardsScrollRNTabProps {
34
+ name: string
35
+ index: number
36
+ position: OverlappingCardsScrollRNTabsPosition
37
+ isPrincipal: boolean
38
+ influence: number
39
+ animate: {
40
+ opacity: number
41
+ }
42
+ className: string
43
+ style: StyleProp<ViewStyle>
44
+ textStyle: StyleProp<TextStyle>
45
+ ariaLabel: string
46
+ ariaCurrent?: 'page'
47
+ accessibilityLabel: string
48
+ accessibilityState?: {
49
+ selected?: boolean
50
+ }
51
+ onPress: () => void
52
+ onClick: () => void
53
+ }
54
+
55
+ export interface OverlappingCardsScrollRNTabsContainerProps {
56
+ children: ReactNode
57
+ position: OverlappingCardsScrollRNTabsPosition
58
+ className: string
59
+ style: StyleProp<ViewStyle>
60
+ ariaLabel: string
61
+ cardNames: string[]
62
+ activeIndex: number
63
+ progress: number
64
+ }
65
+
66
+ type OverlappingCardsScrollRNWithChildren = OverlappingCardsScrollRNSharedProps & {
67
+ children: ReactNode
68
+ items?: never
69
+ }
70
+
71
+ type OverlappingCardsScrollRNWithItems = OverlappingCardsScrollRNSharedProps & {
72
+ items: OverlappingCardsScrollRNItem[]
73
+ children?: never
74
+ }
75
+
76
+ type OverlappingCardsScrollRNContentProps =
77
+ | OverlappingCardsScrollRNWithChildren
78
+ | OverlappingCardsScrollRNWithItems
79
+
80
+ export type OverlappingCardsScrollRNProps = OverlappingCardsScrollRNContentProps & {
81
+ style?: StyleProp<ViewStyle>
82
+ cardContainerStyle?: StyleProp<ViewStyle>
83
+ tabsComponent?: ComponentType<OverlappingCardsScrollRNTabProps>
84
+ tabsContainerComponent?: ComponentType<OverlappingCardsScrollRNTabsContainerProps>
85
+ showsHorizontalScrollIndicator?: boolean
86
+ snapDecelerationRate?: OverlappingCardsScrollRNSnapDecelerationRate
87
+ snapDisableIntervalMomentum?: boolean
88
+ }
89
+
90
+ export interface OverlappingCardsScrollRNFocusTriggerProps {
91
+ children?: ReactNode
92
+ className?: string
93
+ style?: StyleProp<ViewStyle>
94
+ textStyle?: StyleProp<TextStyle>
95
+ behavior?: OverlappingCardsScrollRNFocusTriggerBehavior
96
+ transitionMode?: OverlappingCardsScrollRNFocusTransitionMode
97
+ disabled?: boolean
98
+ accessibilityLabel?: string
99
+ testID?: string
100
+ onPress?: (event: unknown) => void
101
+ onClick?: (event: unknown) => void
102
+ }
@@ -0,0 +1,90 @@
1
+ import type { ComponentProps } from 'react'
2
+ import { StyleSheet, View } from 'react-native'
3
+ import {
4
+ OverlappingCardsScroll,
5
+ OverlappingCardsScrollFocusTrigger,
6
+ } from '../lib/OverlappingCardsScroll'
7
+ import type {
8
+ OverlappingCardsScrollRNFocusTriggerProps,
9
+ OverlappingCardsScrollRNProps,
10
+ } from './OverlappingCardsScrollRN.types'
11
+
12
+ export type {
13
+ OverlappingCardsScrollRNFocusTransitionMode,
14
+ OverlappingCardsScrollRNFocusTriggerBehavior,
15
+ OverlappingCardsScrollRNFocusTriggerProps,
16
+ OverlappingCardsScrollRNItem,
17
+ OverlappingCardsScrollRNPageDotsPosition,
18
+ OverlappingCardsScrollRNProps,
19
+ OverlappingCardsScrollRNSnapDecelerationRate,
20
+ OverlappingCardsScrollRNTabsContainerProps,
21
+ OverlappingCardsScrollRNTabProps,
22
+ OverlappingCardsScrollRNTabsPosition,
23
+ } from './OverlappingCardsScrollRN.types'
24
+
25
+ export function OverlappingCardsScrollRNFocusTrigger({
26
+ children = 'Make principal',
27
+ className = '',
28
+ style = undefined,
29
+ textStyle = undefined,
30
+ behavior = 'smooth',
31
+ transitionMode = 'swoop',
32
+ disabled = false,
33
+ accessibilityLabel = undefined,
34
+ testID = undefined,
35
+ onPress = undefined,
36
+ onClick = undefined,
37
+ ...buttonProps
38
+ }: OverlappingCardsScrollRNFocusTriggerProps) {
39
+ void style
40
+ void textStyle
41
+
42
+ const handleClick = (event: unknown) => {
43
+ onClick?.(event)
44
+ onPress?.(event)
45
+ }
46
+
47
+ return (
48
+ <OverlappingCardsScrollFocusTrigger
49
+ className={className}
50
+ behavior={behavior}
51
+ transitionMode={transitionMode}
52
+ disabled={disabled}
53
+ aria-label={accessibilityLabel}
54
+ data-testid={testID}
55
+ onClick={handleClick}
56
+ {...buttonProps}
57
+ >
58
+ {children}
59
+ </OverlappingCardsScrollFocusTrigger>
60
+ )
61
+ }
62
+
63
+ export function OverlappingCardsScrollRN({
64
+ style = undefined,
65
+ showsHorizontalScrollIndicator = true,
66
+ snapDecelerationRate = 'normal',
67
+ snapDisableIntervalMomentum = false,
68
+ ...overlappingCardsScrollProps
69
+ }: OverlappingCardsScrollRNProps) {
70
+ void showsHorizontalScrollIndicator
71
+ void snapDecelerationRate
72
+ void snapDisableIntervalMomentum
73
+
74
+ return (
75
+ <View style={[styles.root, style]}>
76
+ <OverlappingCardsScroll
77
+ {...(overlappingCardsScrollProps as ComponentProps<typeof OverlappingCardsScroll>)}
78
+ />
79
+ </View>
80
+ )
81
+ }
82
+
83
+ const styles = StyleSheet.create({
84
+ root: {
85
+ width: '100%',
86
+ minWidth: 0,
87
+ flex: 1,
88
+ minHeight: 0,
89
+ },
90
+ })
@@ -0,0 +1,241 @@
1
+ import { useState } from 'react'
2
+ import { Pressable, StyleSheet, Text, View } from 'react-native'
3
+ import {
4
+ OverlappingCardsScrollRN,
5
+ OverlappingCardsScrollRNFocusTrigger,
6
+ } from './OverlappingCardsScrollRN.web'
7
+ import type {
8
+ OverlappingCardsScrollRNTabProps,
9
+ OverlappingCardsScrollRNTabsContainerProps,
10
+ } from './OverlappingCardsScrollRN.web'
11
+
12
+ const RN_DEMO_CARDS = [
13
+ {
14
+ id: 'rn-1',
15
+ tag: 'Card 01',
16
+ title: 'Native Layout',
17
+ body: 'Card width is 1/3 of the viewport measured from onLayout.',
18
+ color: '#0d9488',
19
+ },
20
+ {
21
+ id: 'rn-2',
22
+ tag: 'Card 02',
23
+ title: 'Stacked Ordering',
24
+ body: 'Higher index cards remain above lower index cards in the stack.',
25
+ color: '#f97316',
26
+ },
27
+ {
28
+ id: 'rn-3',
29
+ tag: 'Card 03',
30
+ title: 'Scroll Driven',
31
+ body: 'ScrollView offset drives a single incoming card transition each step.',
32
+ color: '#2563eb',
33
+ },
34
+ {
35
+ id: 'rn-4',
36
+ tag: 'Card 04',
37
+ title: 'Expo Ready',
38
+ body: 'The component relies only on react-native primitives for portability.',
39
+ color: '#7c3aed',
40
+ },
41
+ {
42
+ id: 'rn-5',
43
+ tag: 'Card 05',
44
+ title: 'Fanned Edges',
45
+ body: 'All cards keep a visible leading edge while focus transitions right.',
46
+ color: '#be123c',
47
+ },
48
+ ]
49
+
50
+ function RNCard({ tag, title, body, color }) {
51
+ const [clickCount, setClickCount] = useState(0)
52
+
53
+ return (
54
+ <View style={styles.card}>
55
+ <View style={[styles.bar, { backgroundColor: color }]} />
56
+ <Text style={styles.tag}>{tag}</Text>
57
+ <Pressable
58
+ style={styles.counter}
59
+ onPress={() => setClickCount((count) => count + 1)}
60
+ >
61
+ <Text style={styles.counterText}>Clicks: {clickCount}</Text>
62
+ </Pressable>
63
+ <OverlappingCardsScrollRNFocusTrigger>Make principal</OverlappingCardsScrollRNFocusTrigger>
64
+ <Text style={styles.title}>{title}</Text>
65
+ <Text style={styles.body}>{body}</Text>
66
+ </View>
67
+ )
68
+ }
69
+
70
+ function RNWebTabsContainer({
71
+ children,
72
+ style,
73
+ ariaLabel,
74
+ }: OverlappingCardsScrollRNTabsContainerProps) {
75
+ return (
76
+ <View style={[styles.tabsContainer, style]} accessibilityRole="tablist" accessibilityLabel={ariaLabel}>
77
+ {children}
78
+ </View>
79
+ )
80
+ }
81
+
82
+ function RNWebTab({
83
+ name,
84
+ isPrincipal,
85
+ style,
86
+ textStyle,
87
+ accessibilityLabel,
88
+ accessibilityState,
89
+ onPress,
90
+ onClick,
91
+ }: OverlappingCardsScrollRNTabProps) {
92
+ const handlePress = () => {
93
+ onClick?.()
94
+ onPress?.()
95
+ }
96
+
97
+ return (
98
+ <Pressable
99
+ accessibilityRole="tab"
100
+ accessibilityLabel={accessibilityLabel}
101
+ accessibilityState={accessibilityState}
102
+ onPress={handlePress}
103
+ style={({ pressed }) => [
104
+ styles.tab,
105
+ isPrincipal && styles.tabActive,
106
+ pressed && styles.tabPressed,
107
+ style,
108
+ ]}
109
+ >
110
+ <Text style={[styles.tabText, isPrincipal && styles.tabTextActive, textStyle]}>{name}</Text>
111
+ </Pressable>
112
+ )
113
+ }
114
+
115
+ export function RNWebDemo() {
116
+ return (
117
+ <View style={styles.root}>
118
+ <Text style={styles.helperText}>React Native Web Prototype (View/Text/ScrollView)</Text>
119
+ <OverlappingCardsScrollRN
120
+ cardHeight={260}
121
+ basePeek={58}
122
+ showPageDots
123
+ pageDotsPosition="below"
124
+ pageDotsOffset={10}
125
+ showTabs
126
+ tabsPosition="above"
127
+ tabsOffset={10}
128
+ tabsComponent={RNWebTab}
129
+ tabsContainerComponent={RNWebTabsContainer}
130
+ cardContainerStyle={styles.cardContainer}
131
+ showsHorizontalScrollIndicator
132
+ items={RN_DEMO_CARDS.map((card) => ({
133
+ id: card.id,
134
+ name: card.title,
135
+ jsx: <RNCard {...card} />,
136
+ }))}
137
+ />
138
+ </View>
139
+ )
140
+ }
141
+
142
+ const styles = StyleSheet.create({
143
+ root: {
144
+ width: '100%',
145
+ },
146
+ helperText: {
147
+ color: '#2f4d65',
148
+ fontSize: 14,
149
+ fontWeight: '600',
150
+ marginBottom: 10,
151
+ },
152
+ tabsContainer: {
153
+ flexDirection: 'row',
154
+ justifyContent: 'center',
155
+ flexWrap: 'wrap',
156
+ },
157
+ tab: {
158
+ borderRadius: 999,
159
+ borderWidth: 1,
160
+ borderColor: 'rgba(31, 70, 102, 0.26)',
161
+ backgroundColor: '#eef6ff',
162
+ paddingHorizontal: 10,
163
+ paddingVertical: 5,
164
+ marginHorizontal: 4,
165
+ marginVertical: 4,
166
+ },
167
+ tabActive: {
168
+ backgroundColor: '#1f4666',
169
+ borderColor: '#1f4666',
170
+ },
171
+ tabPressed: {
172
+ opacity: 0.82,
173
+ },
174
+ tabText: {
175
+ color: '#1f4666',
176
+ fontSize: 12,
177
+ fontWeight: '700',
178
+ },
179
+ tabTextActive: {
180
+ color: '#f4f9ff',
181
+ },
182
+ cardContainer: {
183
+ borderRadius: 18,
184
+ overflow: 'hidden',
185
+ },
186
+ card: {
187
+ flex: 1,
188
+ borderRadius: 16,
189
+ borderWidth: 1,
190
+ borderColor: 'rgba(17, 43, 69, 0.13)',
191
+ backgroundColor: '#ffffff',
192
+ padding: 12,
193
+ shadowColor: '#102841',
194
+ shadowOpacity: 0.16,
195
+ shadowRadius: 12,
196
+ shadowOffset: { width: 0, height: 8 },
197
+ elevation: 3,
198
+ },
199
+ bar: {
200
+ width: '100%',
201
+ height: 5,
202
+ borderRadius: 99,
203
+ marginBottom: 8,
204
+ },
205
+ tag: {
206
+ fontSize: 11,
207
+ letterSpacing: 2,
208
+ textTransform: 'uppercase',
209
+ color: '#4a6b84',
210
+ fontWeight: '700',
211
+ marginBottom: 4,
212
+ },
213
+ counter: {
214
+ alignSelf: 'flex-start',
215
+ borderRadius: 99,
216
+ borderWidth: 1,
217
+ borderColor: 'rgba(30, 67, 99, 0.25)',
218
+ backgroundColor: '#f3f8ff',
219
+ paddingHorizontal: 10,
220
+ paddingVertical: 5,
221
+ marginBottom: 6,
222
+ },
223
+ counterText: {
224
+ color: '#1f4666',
225
+ fontSize: 12,
226
+ fontWeight: '700',
227
+ letterSpacing: 0.3,
228
+ },
229
+ title: {
230
+ color: '#173047',
231
+ fontSize: 24,
232
+ lineHeight: 28,
233
+ fontWeight: '700',
234
+ marginBottom: 6,
235
+ },
236
+ body: {
237
+ color: '#2f4d65',
238
+ fontSize: 16,
239
+ lineHeight: 23,
240
+ },
241
+ })