@one-am/react-native-simple-image-slider 0.15.0 → 0.16.0
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 -132
- 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 -133
- 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 +1 -1
- 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 +118 -150
- 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
|
/**
|
|
@@ -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,
|
|
@@ -228,30 +185,55 @@ const BaseSimpleImageSlider = forwardRef<
|
|
|
228
185
|
);
|
|
229
186
|
}
|
|
230
187
|
|
|
231
|
-
const ActualPageCounterComponent = PageCounterComponent
|
|
232
|
-
? makeStyledPageCounter(PageCounterComponent)
|
|
233
|
-
: StyledPageCounter;
|
|
234
|
-
|
|
235
188
|
const listRef = useRef<FlashList<SimpleImageSliderItem>>(null);
|
|
236
|
-
const [currentItem, setCurrentItem] = useState(0);
|
|
237
189
|
|
|
190
|
+
const styles = useMemo(
|
|
191
|
+
() => makeStyles({ imageAspectRatio, imageWidth, imageHeight }),
|
|
192
|
+
[imageAspectRatio, imageHeight, imageWidth]
|
|
193
|
+
);
|
|
238
194
|
const slicedData = useMemo(
|
|
239
195
|
() => (maxItems !== undefined ? (data?.slice(0, maxItems) ?? []) : (data ?? [])),
|
|
240
196
|
[data, maxItems]
|
|
241
197
|
);
|
|
198
|
+
const estimatedItemSize = useMemo(() => {
|
|
199
|
+
return imageWidth ?? (imageHeight ? imageHeight * (imageAspectRatio ?? 4 / 3) : 350);
|
|
200
|
+
}, [imageAspectRatio, imageHeight, imageWidth]);
|
|
242
201
|
|
|
243
|
-
|
|
244
|
-
|
|
202
|
+
const [currentItem, setCurrentItem] = useState(0);
|
|
203
|
+
const [scrollEnabled, setScrollEnabled] = useState(true);
|
|
204
|
+
const [snapToInterval, setSnapToInterval] = useState<number | undefined>(undefined);
|
|
245
205
|
|
|
246
|
-
|
|
247
|
-
|
|
206
|
+
const handleScaleChange = useCallback(() => {
|
|
207
|
+
setScrollEnabled(false);
|
|
208
|
+
}, []);
|
|
248
209
|
|
|
249
|
-
const
|
|
210
|
+
const handleScaleReset = useCallback(() => {
|
|
211
|
+
setScrollEnabled(true);
|
|
212
|
+
}, []);
|
|
213
|
+
|
|
214
|
+
const handleViewableItemsChanged = useCallback(
|
|
215
|
+
({ viewableItems }: { viewableItems: ViewToken[]; changed: ViewToken[] }) => {
|
|
216
|
+
const newIndex = viewableItems[0]?.index;
|
|
217
|
+
if (newIndex !== undefined && newIndex !== null) {
|
|
218
|
+
setCurrentItem(newIndex);
|
|
219
|
+
onViewableItemChange?.(newIndex);
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
[onViewableItemChange]
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const handlePinchToZoomStatusChange = useCallback(
|
|
226
|
+
(status: PinchToZoomStatus) => {
|
|
227
|
+
listRef.current?.recordInteraction();
|
|
228
|
+
onPinchToZoomStatusChange?.(status);
|
|
229
|
+
},
|
|
230
|
+
[onPinchToZoomStatusChange]
|
|
231
|
+
);
|
|
250
232
|
|
|
251
233
|
const renderItem = useCallback(
|
|
252
234
|
({ item, index }: ListRenderItemInfo<SimpleImageSliderItem>) => {
|
|
253
235
|
const ImageComponent =
|
|
254
|
-
sharedTransitionTag && index === currentItem ?
|
|
236
|
+
sharedTransitionTag && index === currentItem ? AnimatedImage : Image;
|
|
255
237
|
|
|
256
238
|
return (
|
|
257
239
|
<Pressable
|
|
@@ -263,78 +245,53 @@ const BaseSimpleImageSlider = forwardRef<
|
|
|
263
245
|
<ImageComponent
|
|
264
246
|
sharedTransitionTag={sharedTransitionTag}
|
|
265
247
|
transition={200}
|
|
248
|
+
// https://github.com/expo/expo/issues/34810
|
|
249
|
+
/* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment */
|
|
266
250
|
placeholder={item.placeholder}
|
|
267
251
|
placeholderContentFit={'cover'}
|
|
268
|
-
imageWidth={imageWidth}
|
|
269
|
-
imageHeight={imageHeight}
|
|
270
|
-
imageAspectRatio={imageAspectRatio}
|
|
271
252
|
recyclingKey={item.key}
|
|
253
|
+
// https://github.com/expo/expo/issues/34810
|
|
254
|
+
/* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment */
|
|
272
255
|
source={item.source}
|
|
273
256
|
contentFit={'cover'}
|
|
274
257
|
contentPosition={'center'}
|
|
275
|
-
style={imageStyle}
|
|
258
|
+
style={[styles.image, imageStyle]}
|
|
276
259
|
/>
|
|
277
260
|
</Pressable>
|
|
278
261
|
);
|
|
279
262
|
},
|
|
280
|
-
[
|
|
281
|
-
currentItem,
|
|
282
|
-
imageAspectRatio,
|
|
283
|
-
imageHeight,
|
|
284
|
-
imageStyle,
|
|
285
|
-
imageWidth,
|
|
286
|
-
onItemPress,
|
|
287
|
-
sharedTransitionTag,
|
|
288
|
-
]
|
|
263
|
+
[currentItem, imageStyle, onItemPress, sharedTransitionTag, styles.image]
|
|
289
264
|
);
|
|
290
265
|
|
|
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
|
-
}, []);
|
|
266
|
+
const keyExtractor = useCallback((item: SimpleImageSliderItem) => item.key, []);
|
|
307
267
|
|
|
308
|
-
const
|
|
309
|
-
|
|
268
|
+
const handleListLayout = useCallback((event: LayoutChangeEvent) => {
|
|
269
|
+
if (Platform.OS === 'ios') {
|
|
270
|
+
// temporary workaround for iOS scrolling a bit more than expected
|
|
271
|
+
setSnapToInterval(event.nativeEvent.layout.width - 0.5);
|
|
272
|
+
}
|
|
310
273
|
}, []);
|
|
311
274
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}, [imageAspectRatio, imageHeight, imageWidth]);
|
|
275
|
+
useEffect(() => {
|
|
276
|
+
setCurrentItem(indexOverride ?? 0);
|
|
315
277
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
listRef.current?.recordInteraction();
|
|
319
|
-
onPinchToZoomStatusChange?.(status);
|
|
320
|
-
},
|
|
321
|
-
[onPinchToZoomStatusChange]
|
|
322
|
-
);
|
|
278
|
+
listRef.current?.scrollToIndex({ index: indexOverride ?? 0, animated: false });
|
|
279
|
+
}, [indexOverride, slicedData]);
|
|
323
280
|
|
|
324
281
|
const list = (
|
|
325
282
|
<FlashList
|
|
326
|
-
// @ts-expect-error - there's just a small inconsistency with hitSlop typing
|
|
327
283
|
renderScrollComponent={ScrollView}
|
|
328
284
|
scrollEnabled={scrollEnabled}
|
|
329
285
|
disableScrollViewPanResponder={enablePinchToZoom ? !scrollEnabled : false}
|
|
330
286
|
ref={mergeRefs(ref, listRef)}
|
|
331
287
|
initialScrollIndex={indexOverride ?? currentItem ?? 0}
|
|
332
|
-
onViewableItemsChanged={
|
|
288
|
+
onViewableItemsChanged={handleViewableItemsChanged}
|
|
333
289
|
viewabilityConfig={{
|
|
334
290
|
itemVisiblePercentThreshold: 55,
|
|
335
291
|
}}
|
|
336
292
|
decelerationRate={'fast'}
|
|
337
|
-
pagingEnabled={
|
|
293
|
+
pagingEnabled={snapToInterval === undefined}
|
|
294
|
+
snapToInterval={snapToInterval}
|
|
338
295
|
showsHorizontalScrollIndicator={false}
|
|
339
296
|
showsVerticalScrollIndicator={false}
|
|
340
297
|
horizontal={true}
|
|
@@ -346,22 +303,24 @@ const BaseSimpleImageSlider = forwardRef<
|
|
|
346
303
|
width: estimatedItemSize,
|
|
347
304
|
height: imageHeight ?? estimatedItemSize / imageAspectRatio,
|
|
348
305
|
}}
|
|
306
|
+
onLayout={handleListLayout}
|
|
349
307
|
/>
|
|
350
308
|
);
|
|
351
309
|
|
|
352
310
|
return (
|
|
353
|
-
<
|
|
311
|
+
<GestureHandlerRootView style={[styles.container, style]}>
|
|
354
312
|
{enablePinchToZoom ? (
|
|
355
|
-
<
|
|
313
|
+
<PinchToZoom
|
|
314
|
+
style={styles.pinchToZoom}
|
|
356
315
|
onDismiss={onPinchToZoomRequestClose}
|
|
357
|
-
onStatusChange={
|
|
358
|
-
onScaleChange={
|
|
359
|
-
onScaleReset={
|
|
316
|
+
onStatusChange={handlePinchToZoomStatusChange}
|
|
317
|
+
onScaleChange={handleScaleChange}
|
|
318
|
+
onScaleReset={handleScaleReset}
|
|
360
319
|
maximumZoomScale={5}
|
|
361
320
|
minimumZoomScale={1}
|
|
362
321
|
>
|
|
363
322
|
{list}
|
|
364
|
-
</
|
|
323
|
+
</PinchToZoom>
|
|
365
324
|
) : (
|
|
366
325
|
list
|
|
367
326
|
)}
|
|
@@ -369,54 +328,63 @@ const BaseSimpleImageSlider = forwardRef<
|
|
|
369
328
|
renderPageCounter ? (
|
|
370
329
|
renderPageCounter(currentItem + 1, slicedData.length)
|
|
371
330
|
) : (
|
|
372
|
-
<
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
331
|
+
<AbsoluteComponentContainer position={pageCounterPosition}>
|
|
332
|
+
<PageCounterComponent
|
|
333
|
+
totalPages={slicedData.length}
|
|
334
|
+
currentPage={currentItem + 1}
|
|
335
|
+
style={pageCounterStyle}
|
|
336
|
+
textStyle={pageCounterTextStyle}
|
|
337
|
+
/>
|
|
338
|
+
</AbsoluteComponentContainer>
|
|
379
339
|
)
|
|
380
340
|
) : null}
|
|
381
341
|
{TopRightComponent ? (
|
|
382
|
-
<
|
|
342
|
+
<AbsoluteComponentContainer position={'top-right'}>
|
|
383
343
|
{renderProp(TopRightComponent)}
|
|
384
|
-
</
|
|
344
|
+
</AbsoluteComponentContainer>
|
|
385
345
|
) : null}
|
|
386
346
|
{TopLeftComponent ? (
|
|
387
|
-
<
|
|
347
|
+
<AbsoluteComponentContainer position={'top-left'}>
|
|
388
348
|
{renderProp(TopLeftComponent)}
|
|
389
|
-
</
|
|
349
|
+
</AbsoluteComponentContainer>
|
|
390
350
|
) : null}
|
|
391
351
|
{BottomRightComponent ? (
|
|
392
|
-
<
|
|
352
|
+
<AbsoluteComponentContainer position={'bottom-right'}>
|
|
393
353
|
{renderProp(BottomRightComponent)}
|
|
394
|
-
</
|
|
354
|
+
</AbsoluteComponentContainer>
|
|
395
355
|
) : null}
|
|
396
356
|
{BottomLeftComponent ? (
|
|
397
|
-
<
|
|
357
|
+
<AbsoluteComponentContainer position={'bottom-left'}>
|
|
398
358
|
{renderProp(BottomLeftComponent)}
|
|
399
|
-
</
|
|
359
|
+
</AbsoluteComponentContainer>
|
|
400
360
|
) : null}
|
|
401
|
-
</
|
|
361
|
+
</GestureHandlerRootView>
|
|
402
362
|
);
|
|
403
363
|
});
|
|
404
364
|
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
365
|
+
const makeStyles = ({
|
|
366
|
+
imageAspectRatio,
|
|
367
|
+
imageWidth,
|
|
368
|
+
imageHeight,
|
|
369
|
+
}: {
|
|
370
|
+
imageAspectRatio?: number;
|
|
371
|
+
imageWidth?: number;
|
|
372
|
+
imageHeight?: number;
|
|
373
|
+
}) => {
|
|
374
|
+
return StyleSheet.create({
|
|
375
|
+
container: {
|
|
376
|
+
width: '100%',
|
|
377
|
+
aspectRatio: imageAspectRatio ?? 4 / 3,
|
|
378
|
+
},
|
|
379
|
+
pinchToZoom: {
|
|
380
|
+
zIndex: 1000,
|
|
381
|
+
},
|
|
382
|
+
image: {
|
|
383
|
+
width: imageWidth ?? '100%',
|
|
384
|
+
height: imageHeight ?? '100%',
|
|
385
|
+
aspectRatio: imageAspectRatio ?? 4 / 3,
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
};
|
|
421
389
|
|
|
422
390
|
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
|
},
|