@one-am/react-native-simple-image-slider 0.15.0 → 0.16.1
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/README.md +12 -6
- package/lib/commonjs/@types/pinch-to-zoom.js +0 -4
- package/lib/commonjs/AbsoluteComponentContainer.js +34 -0
- package/lib/commonjs/AbsoluteComponentContainer.js.map +1 -0
- package/lib/commonjs/BaseSimpleImageSlider.js +98 -135
- package/lib/commonjs/BaseSimpleImageSlider.js.map +1 -1
- package/lib/commonjs/FullScreenImageSlider.js +20 -29
- package/lib/commonjs/FullScreenImageSlider.js.map +1 -1
- package/lib/commonjs/PageCounter.js +24 -20
- package/lib/commonjs/PageCounter.js.map +1 -1
- package/lib/commonjs/PinchToZoom.js +5 -5
- package/lib/commonjs/PinchToZoom.js.map +1 -1
- package/lib/commonjs/SimpleImageSliderThemeProvider.js +19 -11
- package/lib/commonjs/SimpleImageSliderThemeProvider.js.map +1 -1
- package/lib/commonjs/index.js +9 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/@types/pinch-to-zoom.js +0 -2
- package/lib/module/AbsoluteComponentContainer.js +28 -0
- package/lib/module/AbsoluteComponentContainer.js.map +1 -0
- package/lib/module/BaseSimpleImageSlider.js +99 -136
- package/lib/module/BaseSimpleImageSlider.js.map +1 -1
- package/lib/module/FullScreenImageSlider.js +21 -30
- package/lib/module/FullScreenImageSlider.js.map +1 -1
- package/lib/module/PageCounter.js +23 -20
- package/lib/module/PageCounter.js.map +1 -1
- package/lib/module/PinchToZoom.js +5 -5
- package/lib/module/PinchToZoom.js.map +1 -1
- package/lib/module/SimpleImageSliderThemeProvider.js +19 -12
- package/lib/module/SimpleImageSliderThemeProvider.js.map +1 -1
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/@types/pinch-to-zoom.d.ts +3 -4
- package/lib/typescript/src/@types/pinch-to-zoom.d.ts.map +1 -1
- package/lib/typescript/src/AbsoluteComponentContainer.d.ts +7 -0
- package/lib/typescript/src/AbsoluteComponentContainer.d.ts.map +1 -0
- package/lib/typescript/src/BaseSimpleImageSlider.d.ts +2 -2
- package/lib/typescript/src/BaseSimpleImageSlider.d.ts.map +1 -1
- package/lib/typescript/src/FullScreenImageSlider.d.ts.map +1 -1
- package/lib/typescript/src/PageCounter.d.ts.map +1 -1
- package/lib/typescript/src/PinchToZoom.d.ts.map +1 -1
- package/lib/typescript/src/SimpleImageSliderThemeProvider.d.ts +10 -2
- package/lib/typescript/src/SimpleImageSliderThemeProvider.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +161 -160
- package/src/@types/pinch-to-zoom.ts +3 -5
- package/src/AbsoluteComponentContainer.tsx +30 -0
- package/src/BaseSimpleImageSlider.tsx +119 -157
- package/src/FullScreenImageSlider.tsx +39 -34
- package/src/PageCounter.tsx +35 -17
- package/src/PinchToZoom.tsx +10 -9
- package/src/SimpleImageSliderThemeProvider.tsx +32 -13
- package/src/index.tsx +7 -2
- package/lib/commonjs/@types/styled.d.js +0 -4
- package/lib/commonjs/@types/styled.d.js.map +0 -1
- package/lib/module/@types/styled.d.js +0 -4
- package/lib/module/@types/styled.d.js.map +0 -1
- package/src/@types/styled.d.ts +0 -17
|
@@ -9,10 +9,17 @@ import React, {
|
|
|
9
9
|
} from 'react';
|
|
10
10
|
import { FlashList, type ListRenderItemInfo } from '@shopify/flash-list';
|
|
11
11
|
import mergeRefs from 'merge-refs';
|
|
12
|
-
import { Image, type
|
|
13
|
-
import {
|
|
12
|
+
import { Image, type ImageStyle } from 'expo-image';
|
|
13
|
+
import {
|
|
14
|
+
type LayoutChangeEvent,
|
|
15
|
+
Platform,
|
|
16
|
+
Pressable,
|
|
17
|
+
type StyleProp,
|
|
18
|
+
StyleSheet,
|
|
19
|
+
type TextStyle,
|
|
20
|
+
type ViewStyle,
|
|
21
|
+
} from 'react-native';
|
|
14
22
|
import type ViewToken from '@shopify/flash-list/src/viewability/ViewToken';
|
|
15
|
-
import styled from 'styled-components/native';
|
|
16
23
|
import PageCounter, { type PageCounterProps } from './PageCounter';
|
|
17
24
|
import PinchToZoom, { type PinchToZoomProps } from './PinchToZoom';
|
|
18
25
|
import { GestureHandlerRootView, ScrollView } from 'react-native-gesture-handler';
|
|
@@ -20,6 +27,7 @@ import renderProp, { type RenderProp } from './utils/renderProp';
|
|
|
20
27
|
import type { SimpleImageSliderItem } from './@types/slider';
|
|
21
28
|
import type { PinchToZoomStatus } from './@types/pinch-to-zoom';
|
|
22
29
|
import Animated from 'react-native-reanimated';
|
|
30
|
+
import { AbsoluteComponentContainer } from './AbsoluteComponentContainer';
|
|
23
31
|
|
|
24
32
|
export type BaseSimpleImageSliderProps = {
|
|
25
33
|
/**
|
|
@@ -78,7 +86,7 @@ export type BaseSimpleImageSliderProps = {
|
|
|
78
86
|
*/
|
|
79
87
|
PageCounterComponent?: React.FunctionComponent<PageCounterProps>;
|
|
80
88
|
/**
|
|
81
|
-
* @description Callback that renders the page counter.
|
|
89
|
+
* @description Callback that renders the page counter. Overrides `PageCounterComponent` if provided.
|
|
82
90
|
* @param currentPage The current page number.
|
|
83
91
|
* @param totalPages The total number of pages.
|
|
84
92
|
*/
|
|
@@ -127,63 +135,12 @@ export type BaseSimpleImageSliderProps = {
|
|
|
127
135
|
*/
|
|
128
136
|
sharedTransitionTag?: string;
|
|
129
137
|
/**
|
|
130
|
-
* @description Style that will be applied to
|
|
138
|
+
* @description Style that will be applied to every image in the slider.
|
|
131
139
|
*/
|
|
132
140
|
imageStyle?: StyleProp<ImageStyle>;
|
|
133
141
|
};
|
|
134
142
|
|
|
135
|
-
const
|
|
136
|
-
position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
137
|
-
}>`
|
|
138
|
-
z-index: 1000;
|
|
139
|
-
position: absolute;
|
|
140
|
-
bottom: ${({ position }) =>
|
|
141
|
-
position === 'bottom-left' || position === 'bottom-right' ? `16px` : 'auto'};
|
|
142
|
-
top: ${({ position }) =>
|
|
143
|
-
position === 'top-left' || position === 'top-right' ? `16px` : 'auto'};
|
|
144
|
-
left: ${({ position }) =>
|
|
145
|
-
position === 'top-left' || position === 'bottom-left' ? `16px` : 'auto'};
|
|
146
|
-
right: ${({ position }) =>
|
|
147
|
-
position === 'top-right' || position === 'bottom-right' ? `16px` : 'auto'};
|
|
148
|
-
`;
|
|
149
|
-
|
|
150
|
-
const StyledPageCounter = styled(PageCounter)<{
|
|
151
|
-
position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
152
|
-
}>`
|
|
153
|
-
z-index: 1000;
|
|
154
|
-
position: absolute;
|
|
155
|
-
bottom: ${({ position }) =>
|
|
156
|
-
position === 'bottom-left' || position === 'bottom-right' ? `16px` : 'auto'};
|
|
157
|
-
top: ${({ position }) =>
|
|
158
|
-
position === 'top-left' || position === 'top-right' ? `16px` : 'auto'};
|
|
159
|
-
left: ${({ position }) =>
|
|
160
|
-
position === 'top-left' || position === 'bottom-left' ? `16px` : 'auto'};
|
|
161
|
-
right: ${({ position }) =>
|
|
162
|
-
position === 'top-right' || position === 'bottom-right' ? `16px` : 'auto'};
|
|
163
|
-
`;
|
|
164
|
-
|
|
165
|
-
const StyledContainer = styled(GestureHandlerRootView)<{ aspectRatio: number }>`
|
|
166
|
-
aspect-ratio: ${({ aspectRatio }) => aspectRatio ?? 4 / 3};
|
|
167
|
-
width: 100%;
|
|
168
|
-
`;
|
|
169
|
-
|
|
170
|
-
const StyledImage = styled(Image)<
|
|
171
|
-
ImageProps & {
|
|
172
|
-
imageWidth?: number;
|
|
173
|
-
imageHeight?: number;
|
|
174
|
-
imageAspectRatio: number;
|
|
175
|
-
}
|
|
176
|
-
>`
|
|
177
|
-
width: ${({ imageWidth }) => (imageWidth ? `${imageWidth}px` : '100%')};
|
|
178
|
-
height: ${({ imageHeight }) => (imageHeight ? `${imageHeight}px` : '100%')};
|
|
179
|
-
aspect-ratio: ${({ imageAspectRatio }) => imageAspectRatio};
|
|
180
|
-
`;
|
|
181
|
-
|
|
182
|
-
const AnimatedStyledImage = Animated.createAnimatedComponent(StyledImage);
|
|
183
|
-
|
|
184
|
-
const StyledPinchToZoom = styled(PinchToZoom)`
|
|
185
|
-
z-index: 1000;
|
|
186
|
-
`;
|
|
143
|
+
const AnimatedImage = Animated.createAnimatedComponent(Image);
|
|
187
144
|
|
|
188
145
|
/**
|
|
189
146
|
* @description A simple image slider that displays a list of images. This is the component
|
|
@@ -206,7 +163,7 @@ const BaseSimpleImageSlider = forwardRef<
|
|
|
206
163
|
pageCounterPosition = 'bottom-left',
|
|
207
164
|
pageCounterStyle,
|
|
208
165
|
pageCounterTextStyle,
|
|
209
|
-
PageCounterComponent,
|
|
166
|
+
PageCounterComponent = PageCounter,
|
|
210
167
|
renderPageCounter,
|
|
211
168
|
TopRightComponent,
|
|
212
169
|
TopLeftComponent,
|
|
@@ -222,36 +179,55 @@ const BaseSimpleImageSlider = forwardRef<
|
|
|
222
179
|
},
|
|
223
180
|
ref
|
|
224
181
|
) {
|
|
225
|
-
if (renderPageCounter !== undefined && PageCounterComponent !== undefined) {
|
|
226
|
-
throw new Error(
|
|
227
|
-
'You should provide either `renderPageCounter` or `PageCounterComponent`, not both.'
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const ActualPageCounterComponent = PageCounterComponent
|
|
232
|
-
? makeStyledPageCounter(PageCounterComponent)
|
|
233
|
-
: StyledPageCounter;
|
|
234
|
-
|
|
235
182
|
const listRef = useRef<FlashList<SimpleImageSliderItem>>(null);
|
|
236
|
-
const [currentItem, setCurrentItem] = useState(0);
|
|
237
183
|
|
|
184
|
+
const styles = useMemo(
|
|
185
|
+
() => makeStyles({ imageAspectRatio, imageWidth, imageHeight }),
|
|
186
|
+
[imageAspectRatio, imageHeight, imageWidth]
|
|
187
|
+
);
|
|
238
188
|
const slicedData = useMemo(
|
|
239
189
|
() => (maxItems !== undefined ? (data?.slice(0, maxItems) ?? []) : (data ?? [])),
|
|
240
190
|
[data, maxItems]
|
|
241
191
|
);
|
|
192
|
+
const estimatedItemSize = useMemo(() => {
|
|
193
|
+
return imageWidth ?? (imageHeight ? imageHeight * (imageAspectRatio ?? 4 / 3) : 350);
|
|
194
|
+
}, [imageAspectRatio, imageHeight, imageWidth]);
|
|
242
195
|
|
|
243
|
-
|
|
244
|
-
|
|
196
|
+
const [currentItem, setCurrentItem] = useState(0);
|
|
197
|
+
const [scrollEnabled, setScrollEnabled] = useState(true);
|
|
198
|
+
const [snapToInterval, setSnapToInterval] = useState<number | undefined>(undefined);
|
|
245
199
|
|
|
246
|
-
|
|
247
|
-
|
|
200
|
+
const handleScaleChange = useCallback(() => {
|
|
201
|
+
setScrollEnabled(false);
|
|
202
|
+
}, []);
|
|
248
203
|
|
|
249
|
-
const
|
|
204
|
+
const handleScaleReset = useCallback(() => {
|
|
205
|
+
setScrollEnabled(true);
|
|
206
|
+
}, []);
|
|
207
|
+
|
|
208
|
+
const handleViewableItemsChanged = useCallback(
|
|
209
|
+
({ viewableItems }: { viewableItems: ViewToken[]; changed: ViewToken[] }) => {
|
|
210
|
+
const newIndex = viewableItems[0]?.index;
|
|
211
|
+
if (newIndex !== undefined && newIndex !== null) {
|
|
212
|
+
setCurrentItem(newIndex);
|
|
213
|
+
onViewableItemChange?.(newIndex);
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
[onViewableItemChange]
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
const handlePinchToZoomStatusChange = useCallback(
|
|
220
|
+
(status: PinchToZoomStatus) => {
|
|
221
|
+
listRef.current?.recordInteraction();
|
|
222
|
+
onPinchToZoomStatusChange?.(status);
|
|
223
|
+
},
|
|
224
|
+
[onPinchToZoomStatusChange]
|
|
225
|
+
);
|
|
250
226
|
|
|
251
227
|
const renderItem = useCallback(
|
|
252
228
|
({ item, index }: ListRenderItemInfo<SimpleImageSliderItem>) => {
|
|
253
229
|
const ImageComponent =
|
|
254
|
-
sharedTransitionTag && index === currentItem ?
|
|
230
|
+
sharedTransitionTag && index === currentItem ? AnimatedImage : Image;
|
|
255
231
|
|
|
256
232
|
return (
|
|
257
233
|
<Pressable
|
|
@@ -263,78 +239,53 @@ const BaseSimpleImageSlider = forwardRef<
|
|
|
263
239
|
<ImageComponent
|
|
264
240
|
sharedTransitionTag={sharedTransitionTag}
|
|
265
241
|
transition={200}
|
|
242
|
+
// https://github.com/expo/expo/issues/34810
|
|
243
|
+
/* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment */
|
|
266
244
|
placeholder={item.placeholder}
|
|
267
245
|
placeholderContentFit={'cover'}
|
|
268
|
-
imageWidth={imageWidth}
|
|
269
|
-
imageHeight={imageHeight}
|
|
270
|
-
imageAspectRatio={imageAspectRatio}
|
|
271
246
|
recyclingKey={item.key}
|
|
247
|
+
// https://github.com/expo/expo/issues/34810
|
|
248
|
+
/* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment */
|
|
272
249
|
source={item.source}
|
|
273
250
|
contentFit={'cover'}
|
|
274
251
|
contentPosition={'center'}
|
|
275
|
-
style={imageStyle}
|
|
252
|
+
style={[styles.image, imageStyle]}
|
|
276
253
|
/>
|
|
277
254
|
</Pressable>
|
|
278
255
|
);
|
|
279
256
|
},
|
|
280
|
-
[
|
|
281
|
-
currentItem,
|
|
282
|
-
imageAspectRatio,
|
|
283
|
-
imageHeight,
|
|
284
|
-
imageStyle,
|
|
285
|
-
imageWidth,
|
|
286
|
-
onItemPress,
|
|
287
|
-
sharedTransitionTag,
|
|
288
|
-
]
|
|
257
|
+
[currentItem, imageStyle, onItemPress, sharedTransitionTag, styles.image]
|
|
289
258
|
);
|
|
290
259
|
|
|
291
|
-
const
|
|
292
|
-
({ viewableItems }: { viewableItems: ViewToken[]; changed: ViewToken[] }) => {
|
|
293
|
-
const newIndex = viewableItems[0]?.index;
|
|
294
|
-
if (newIndex !== undefined && newIndex !== null) {
|
|
295
|
-
setCurrentItem(newIndex);
|
|
296
|
-
onViewableItemChange?.(newIndex);
|
|
297
|
-
}
|
|
298
|
-
},
|
|
299
|
-
[onViewableItemChange]
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
const [scrollEnabled, setScrollEnabled] = useState(true);
|
|
303
|
-
|
|
304
|
-
const onScaleChange = useCallback(() => {
|
|
305
|
-
setScrollEnabled(false);
|
|
306
|
-
}, []);
|
|
260
|
+
const keyExtractor = useCallback((item: SimpleImageSliderItem) => item.key, []);
|
|
307
261
|
|
|
308
|
-
const
|
|
309
|
-
|
|
262
|
+
const handleListLayout = useCallback((event: LayoutChangeEvent) => {
|
|
263
|
+
if (Platform.OS === 'ios') {
|
|
264
|
+
// temporary workaround for iOS scrolling a bit more than expected
|
|
265
|
+
setSnapToInterval(event.nativeEvent.layout.width - 0.5);
|
|
266
|
+
}
|
|
310
267
|
}, []);
|
|
311
268
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}, [imageAspectRatio, imageHeight, imageWidth]);
|
|
269
|
+
useEffect(() => {
|
|
270
|
+
setCurrentItem(indexOverride ?? 0);
|
|
315
271
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
listRef.current?.recordInteraction();
|
|
319
|
-
onPinchToZoomStatusChange?.(status);
|
|
320
|
-
},
|
|
321
|
-
[onPinchToZoomStatusChange]
|
|
322
|
-
);
|
|
272
|
+
listRef.current?.scrollToIndex({ index: indexOverride ?? 0, animated: false });
|
|
273
|
+
}, [indexOverride, slicedData]);
|
|
323
274
|
|
|
324
275
|
const list = (
|
|
325
276
|
<FlashList
|
|
326
|
-
// @ts-expect-error - there's just a small inconsistency with hitSlop typing
|
|
327
277
|
renderScrollComponent={ScrollView}
|
|
328
278
|
scrollEnabled={scrollEnabled}
|
|
329
279
|
disableScrollViewPanResponder={enablePinchToZoom ? !scrollEnabled : false}
|
|
330
280
|
ref={mergeRefs(ref, listRef)}
|
|
331
281
|
initialScrollIndex={indexOverride ?? currentItem ?? 0}
|
|
332
|
-
onViewableItemsChanged={
|
|
282
|
+
onViewableItemsChanged={handleViewableItemsChanged}
|
|
333
283
|
viewabilityConfig={{
|
|
334
284
|
itemVisiblePercentThreshold: 55,
|
|
335
285
|
}}
|
|
336
286
|
decelerationRate={'fast'}
|
|
337
|
-
pagingEnabled={
|
|
287
|
+
pagingEnabled={snapToInterval === undefined}
|
|
288
|
+
snapToInterval={snapToInterval}
|
|
338
289
|
showsHorizontalScrollIndicator={false}
|
|
339
290
|
showsVerticalScrollIndicator={false}
|
|
340
291
|
horizontal={true}
|
|
@@ -346,22 +297,24 @@ const BaseSimpleImageSlider = forwardRef<
|
|
|
346
297
|
width: estimatedItemSize,
|
|
347
298
|
height: imageHeight ?? estimatedItemSize / imageAspectRatio,
|
|
348
299
|
}}
|
|
300
|
+
onLayout={handleListLayout}
|
|
349
301
|
/>
|
|
350
302
|
);
|
|
351
303
|
|
|
352
304
|
return (
|
|
353
|
-
<
|
|
305
|
+
<GestureHandlerRootView style={[styles.container, style]}>
|
|
354
306
|
{enablePinchToZoom ? (
|
|
355
|
-
<
|
|
307
|
+
<PinchToZoom
|
|
308
|
+
style={styles.pinchToZoom}
|
|
356
309
|
onDismiss={onPinchToZoomRequestClose}
|
|
357
|
-
onStatusChange={
|
|
358
|
-
onScaleChange={
|
|
359
|
-
onScaleReset={
|
|
310
|
+
onStatusChange={handlePinchToZoomStatusChange}
|
|
311
|
+
onScaleChange={handleScaleChange}
|
|
312
|
+
onScaleReset={handleScaleReset}
|
|
360
313
|
maximumZoomScale={5}
|
|
361
314
|
minimumZoomScale={1}
|
|
362
315
|
>
|
|
363
316
|
{list}
|
|
364
|
-
</
|
|
317
|
+
</PinchToZoom>
|
|
365
318
|
) : (
|
|
366
319
|
list
|
|
367
320
|
)}
|
|
@@ -369,54 +322,63 @@ const BaseSimpleImageSlider = forwardRef<
|
|
|
369
322
|
renderPageCounter ? (
|
|
370
323
|
renderPageCounter(currentItem + 1, slicedData.length)
|
|
371
324
|
) : (
|
|
372
|
-
<
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
325
|
+
<AbsoluteComponentContainer position={pageCounterPosition}>
|
|
326
|
+
<PageCounterComponent
|
|
327
|
+
totalPages={slicedData.length}
|
|
328
|
+
currentPage={currentItem + 1}
|
|
329
|
+
style={pageCounterStyle}
|
|
330
|
+
textStyle={pageCounterTextStyle}
|
|
331
|
+
/>
|
|
332
|
+
</AbsoluteComponentContainer>
|
|
379
333
|
)
|
|
380
334
|
) : null}
|
|
381
335
|
{TopRightComponent ? (
|
|
382
|
-
<
|
|
336
|
+
<AbsoluteComponentContainer position={'top-right'}>
|
|
383
337
|
{renderProp(TopRightComponent)}
|
|
384
|
-
</
|
|
338
|
+
</AbsoluteComponentContainer>
|
|
385
339
|
) : null}
|
|
386
340
|
{TopLeftComponent ? (
|
|
387
|
-
<
|
|
341
|
+
<AbsoluteComponentContainer position={'top-left'}>
|
|
388
342
|
{renderProp(TopLeftComponent)}
|
|
389
|
-
</
|
|
343
|
+
</AbsoluteComponentContainer>
|
|
390
344
|
) : null}
|
|
391
345
|
{BottomRightComponent ? (
|
|
392
|
-
<
|
|
346
|
+
<AbsoluteComponentContainer position={'bottom-right'}>
|
|
393
347
|
{renderProp(BottomRightComponent)}
|
|
394
|
-
</
|
|
348
|
+
</AbsoluteComponentContainer>
|
|
395
349
|
) : null}
|
|
396
350
|
{BottomLeftComponent ? (
|
|
397
|
-
<
|
|
351
|
+
<AbsoluteComponentContainer position={'bottom-left'}>
|
|
398
352
|
{renderProp(BottomLeftComponent)}
|
|
399
|
-
</
|
|
353
|
+
</AbsoluteComponentContainer>
|
|
400
354
|
) : null}
|
|
401
|
-
</
|
|
355
|
+
</GestureHandlerRootView>
|
|
402
356
|
);
|
|
403
357
|
});
|
|
404
358
|
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
359
|
+
const makeStyles = ({
|
|
360
|
+
imageAspectRatio,
|
|
361
|
+
imageWidth,
|
|
362
|
+
imageHeight,
|
|
363
|
+
}: {
|
|
364
|
+
imageAspectRatio?: number;
|
|
365
|
+
imageWidth?: number;
|
|
366
|
+
imageHeight?: number;
|
|
367
|
+
}) => {
|
|
368
|
+
return StyleSheet.create({
|
|
369
|
+
container: {
|
|
370
|
+
width: '100%',
|
|
371
|
+
aspectRatio: imageAspectRatio ?? 4 / 3,
|
|
372
|
+
},
|
|
373
|
+
pinchToZoom: {
|
|
374
|
+
zIndex: 1000,
|
|
375
|
+
},
|
|
376
|
+
image: {
|
|
377
|
+
width: imageWidth ?? '100%',
|
|
378
|
+
height: imageHeight ?? '100%',
|
|
379
|
+
aspectRatio: imageAspectRatio ?? 4 / 3,
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
};
|
|
421
383
|
|
|
422
384
|
export default BaseSimpleImageSlider;
|
|
@@ -6,7 +6,14 @@ import React, {
|
|
|
6
6
|
useMemo,
|
|
7
7
|
useState,
|
|
8
8
|
} from 'react';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
Modal,
|
|
11
|
+
type ScaledSize,
|
|
12
|
+
StyleSheet,
|
|
13
|
+
TouchableOpacity,
|
|
14
|
+
useWindowDimensions,
|
|
15
|
+
View,
|
|
16
|
+
} from 'react-native';
|
|
10
17
|
import IconX from './icons/IconX';
|
|
11
18
|
import Animated, {
|
|
12
19
|
runOnJS,
|
|
@@ -15,13 +22,16 @@ import Animated, {
|
|
|
15
22
|
withTiming,
|
|
16
23
|
} from 'react-native-reanimated';
|
|
17
24
|
import { setStatusBarStyle } from 'expo-status-bar';
|
|
18
|
-
import styled, { useTheme } from 'styled-components/native';
|
|
19
25
|
import { FlashList } from '@shopify/flash-list';
|
|
20
26
|
import BaseListImageSlider, { type BaseSimpleImageSliderProps } from './BaseSimpleImageSlider';
|
|
21
27
|
import { type EdgeInsets, useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
22
28
|
import type { PinchToZoomStatus } from './@types/pinch-to-zoom';
|
|
23
29
|
import type { SimpleImageSliderItem } from './@types/slider';
|
|
24
30
|
import renderProp, { type RenderProp } from './utils/renderProp';
|
|
31
|
+
import {
|
|
32
|
+
type SimpleImageSliderTheme,
|
|
33
|
+
useSimpleImageSliderTheme,
|
|
34
|
+
} from './SimpleImageSliderThemeProvider';
|
|
25
35
|
|
|
26
36
|
export type FullScreenImageSliderProps = Omit<BaseSimpleImageSliderProps, 'imageWidth'> & {
|
|
27
37
|
/**
|
|
@@ -48,25 +58,6 @@ export type FullScreenImageSliderProps = Omit<BaseSimpleImageSliderProps, 'image
|
|
|
48
58
|
onFadeOut?: () => void;
|
|
49
59
|
};
|
|
50
60
|
|
|
51
|
-
const StyledDescriptionContainer = styled.View`
|
|
52
|
-
position: absolute;
|
|
53
|
-
border-top-width: 1px;
|
|
54
|
-
border-top-color: ${({ theme }) => theme.colors.simpleImageSlider.descriptionContainerBorder};
|
|
55
|
-
width: 100%;
|
|
56
|
-
padding-top: 20px;
|
|
57
|
-
`;
|
|
58
|
-
|
|
59
|
-
const StyledModalCloseButton = styled.TouchableOpacity`
|
|
60
|
-
position: absolute;
|
|
61
|
-
z-index: 1000;
|
|
62
|
-
`;
|
|
63
|
-
|
|
64
|
-
const StyledModalContentContainer = styled(Animated.View)`
|
|
65
|
-
align-items: center;
|
|
66
|
-
justify-content: center;
|
|
67
|
-
gap: 16px;
|
|
68
|
-
`;
|
|
69
|
-
|
|
70
61
|
/**
|
|
71
62
|
* @description A full screen image slider that displays images in a modal.
|
|
72
63
|
*/
|
|
@@ -87,11 +78,11 @@ const FullScreenImageSlider = forwardRef<
|
|
|
87
78
|
ref
|
|
88
79
|
) {
|
|
89
80
|
const windowDimensions = useWindowDimensions();
|
|
90
|
-
const theme =
|
|
81
|
+
const theme = useSimpleImageSliderTheme();
|
|
91
82
|
const safeAreaInsets = useSafeAreaInsets();
|
|
92
83
|
const styles = useMemo(
|
|
93
|
-
() => makeStyles(safeAreaInsets, windowDimensions),
|
|
94
|
-
[safeAreaInsets, windowDimensions]
|
|
84
|
+
() => makeStyles(safeAreaInsets, windowDimensions, theme),
|
|
85
|
+
[safeAreaInsets, windowDimensions, theme]
|
|
95
86
|
);
|
|
96
87
|
|
|
97
88
|
const [internalIndex, setInternalIndex] = useState<number>(0);
|
|
@@ -122,8 +113,8 @@ const FullScreenImageSlider = forwardRef<
|
|
|
122
113
|
|
|
123
114
|
const onPinchToZoomStatusChange = useCallback(
|
|
124
115
|
({ translation, scale }: PinchToZoomStatus) => {
|
|
125
|
-
if (scale
|
|
126
|
-
if (translation.x
|
|
116
|
+
if (scale <= 1) {
|
|
117
|
+
if (translation.x === 0 && translation.y === 0) {
|
|
127
118
|
runOnJS(setStatusBarStyle)('light');
|
|
128
119
|
backgroundOpacity.value = withTiming(1);
|
|
129
120
|
} else {
|
|
@@ -147,14 +138,14 @@ const FullScreenImageSlider = forwardRef<
|
|
|
147
138
|
transparent={true}
|
|
148
139
|
visible={open}
|
|
149
140
|
>
|
|
150
|
-
<
|
|
151
|
-
<
|
|
141
|
+
<Animated.View style={[styles.modalContent, modalContentStyle]}>
|
|
142
|
+
<TouchableOpacity style={styles.closeButton} onPress={onRequestClose}>
|
|
152
143
|
{CloseButtonIcon ? (
|
|
153
144
|
renderProp(CloseButtonIcon)
|
|
154
145
|
) : (
|
|
155
|
-
<IconX color={theme.colors.
|
|
146
|
+
<IconX color={theme.colors.fullScreenCloseButton} />
|
|
156
147
|
)}
|
|
157
|
-
</
|
|
148
|
+
</TouchableOpacity>
|
|
158
149
|
<BaseListImageSlider
|
|
159
150
|
data={data}
|
|
160
151
|
enablePinchToZoom={true}
|
|
@@ -168,28 +159,42 @@ const FullScreenImageSlider = forwardRef<
|
|
|
168
159
|
/>
|
|
169
160
|
|
|
170
161
|
{renderDescription && data[internalIndex] ? (
|
|
171
|
-
<
|
|
162
|
+
<View style={styles.descriptionContainer}>
|
|
172
163
|
{renderDescription(data[internalIndex], internalIndex)}
|
|
173
|
-
</
|
|
164
|
+
</View>
|
|
174
165
|
) : null}
|
|
175
|
-
</
|
|
166
|
+
</Animated.View>
|
|
176
167
|
</Modal>
|
|
177
168
|
);
|
|
178
169
|
});
|
|
179
170
|
|
|
180
171
|
export default FullScreenImageSlider;
|
|
181
172
|
|
|
182
|
-
const makeStyles = (
|
|
173
|
+
const makeStyles = (
|
|
174
|
+
safeAreaInsets: EdgeInsets,
|
|
175
|
+
windowDimensions: ScaledSize,
|
|
176
|
+
theme: SimpleImageSliderTheme
|
|
177
|
+
) => {
|
|
183
178
|
return StyleSheet.create({
|
|
184
179
|
closeButton: {
|
|
180
|
+
position: 'absolute',
|
|
181
|
+
zIndex: 1000,
|
|
185
182
|
top: safeAreaInsets.top,
|
|
186
183
|
right: safeAreaInsets.right + 20,
|
|
187
184
|
},
|
|
188
185
|
modalContent: {
|
|
186
|
+
alignItems: 'center',
|
|
187
|
+
justifyContent: 'center',
|
|
188
|
+
gap: 16,
|
|
189
189
|
height: windowDimensions.height,
|
|
190
190
|
width: windowDimensions.width,
|
|
191
191
|
},
|
|
192
192
|
descriptionContainer: {
|
|
193
|
+
position: 'absolute',
|
|
194
|
+
borderTopWidth: 1,
|
|
195
|
+
borderTopColor: theme.colors.descriptionContainerBorder,
|
|
196
|
+
width: '100%',
|
|
197
|
+
paddingTop: 20,
|
|
193
198
|
bottom: safeAreaInsets.bottom + 100,
|
|
194
199
|
paddingLeft: safeAreaInsets.left + 20,
|
|
195
200
|
paddingRight: safeAreaInsets.right + 20,
|
package/src/PageCounter.tsx
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
type StyleProp,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
Text,
|
|
6
|
+
type TextStyle,
|
|
7
|
+
View,
|
|
8
|
+
type ViewStyle,
|
|
9
|
+
} from 'react-native';
|
|
10
|
+
import {
|
|
11
|
+
type SimpleImageSliderTheme,
|
|
12
|
+
useSimpleImageSliderTheme,
|
|
13
|
+
} from './SimpleImageSliderThemeProvider';
|
|
4
14
|
|
|
5
15
|
export type PageCounterProps = {
|
|
6
16
|
/**
|
|
@@ -21,29 +31,37 @@ export type PageCounterProps = {
|
|
|
21
31
|
textStyle?: StyleProp<TextStyle>;
|
|
22
32
|
};
|
|
23
33
|
|
|
24
|
-
const StyledContainer = styled.View`
|
|
25
|
-
background-color: ${({ theme }) => theme.colors.simpleImageSlider.pageCounterBackground};
|
|
26
|
-
border-width: 1px;
|
|
27
|
-
border-color: ${({ theme }) => theme.colors.simpleImageSlider.pageCounterBorder};
|
|
28
|
-
border-radius: 8px;
|
|
29
|
-
padding: 6px 5px;
|
|
30
|
-
width: 75px;
|
|
31
|
-
flex-direction: row;
|
|
32
|
-
align-items: center;
|
|
33
|
-
justify-content: center;
|
|
34
|
-
`;
|
|
35
|
-
|
|
36
34
|
export default function PageCounter({
|
|
37
35
|
currentPage,
|
|
38
36
|
totalPages,
|
|
39
37
|
style,
|
|
40
38
|
textStyle,
|
|
41
39
|
}: PageCounterProps) {
|
|
40
|
+
const theme = useSimpleImageSliderTheme();
|
|
41
|
+
const styles = useMemo(() => makeStyles(theme), [theme]);
|
|
42
|
+
|
|
42
43
|
return (
|
|
43
|
-
<
|
|
44
|
+
<View style={[styles.container, style]}>
|
|
44
45
|
<Text style={textStyle}>
|
|
45
46
|
{currentPage} / {totalPages}
|
|
46
47
|
</Text>
|
|
47
|
-
</
|
|
48
|
+
</View>
|
|
48
49
|
);
|
|
49
50
|
}
|
|
51
|
+
|
|
52
|
+
const makeStyles = (theme: SimpleImageSliderTheme) => {
|
|
53
|
+
return StyleSheet.create({
|
|
54
|
+
container: {
|
|
55
|
+
backgroundColor: theme.colors.pageCounterBackground,
|
|
56
|
+
borderWidth: 1,
|
|
57
|
+
borderColor: theme.colors.pageCounterBorder,
|
|
58
|
+
borderRadius: 8,
|
|
59
|
+
paddingVertical: 6,
|
|
60
|
+
paddingHorizontal: 5,
|
|
61
|
+
width: 75,
|
|
62
|
+
flexDirection: 'row',
|
|
63
|
+
alignItems: 'center',
|
|
64
|
+
justifyContent: 'center',
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
};
|
package/src/PinchToZoom.tsx
CHANGED
|
@@ -126,6 +126,7 @@ export default function PinchToZoom({
|
|
|
126
126
|
(-viewWidth.value * (scale.value - minimumZoomScale)) / 2,
|
|
127
127
|
(viewWidth.value * (scale.value - minimumZoomScale)) / 2
|
|
128
128
|
);
|
|
129
|
+
|
|
129
130
|
translationY.value = clamp(
|
|
130
131
|
prevTranslationY.value +
|
|
131
132
|
-1 *
|
|
@@ -173,8 +174,8 @@ export default function PinchToZoom({
|
|
|
173
174
|
originY,
|
|
174
175
|
prevTranslationX,
|
|
175
176
|
prevTranslationY,
|
|
176
|
-
viewWidth
|
|
177
|
-
viewHeight
|
|
177
|
+
viewWidth,
|
|
178
|
+
viewHeight,
|
|
178
179
|
minimumZoomScale,
|
|
179
180
|
onDismiss,
|
|
180
181
|
onScaleReset,
|
|
@@ -247,15 +248,15 @@ export default function PinchToZoom({
|
|
|
247
248
|
disabled,
|
|
248
249
|
minimumZoomScale,
|
|
249
250
|
onDismiss,
|
|
250
|
-
prevScale
|
|
251
|
+
prevScale,
|
|
251
252
|
prevTranslationX,
|
|
252
253
|
prevTranslationY,
|
|
253
|
-
scale
|
|
254
|
+
scale,
|
|
254
255
|
windowHeight,
|
|
255
256
|
translationX,
|
|
256
257
|
translationY,
|
|
257
|
-
viewHeight
|
|
258
|
-
viewWidth
|
|
258
|
+
viewHeight,
|
|
259
|
+
viewWidth,
|
|
259
260
|
]
|
|
260
261
|
);
|
|
261
262
|
|
|
@@ -298,10 +299,10 @@ export default function PinchToZoom({
|
|
|
298
299
|
useAnimatedReaction(
|
|
299
300
|
() => {
|
|
300
301
|
return {
|
|
301
|
-
scale: scale,
|
|
302
|
+
scale: scale.value,
|
|
302
303
|
translation: {
|
|
303
|
-
x: translationX,
|
|
304
|
-
y: translationY,
|
|
304
|
+
x: translationX.value,
|
|
305
|
+
y: translationY.value,
|
|
305
306
|
},
|
|
306
307
|
};
|
|
307
308
|
},
|