@one-am/react-native-simple-image-slider 0.2.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.
- package/LICENSE +20 -0
- package/README.md +74 -0
- package/lib/commonjs/@types/styled.d.js +4 -0
- package/lib/commonjs/@types/styled.d.js.map +1 -0
- package/lib/commonjs/BaseSimpleImageSlider.js +194 -0
- package/lib/commonjs/BaseSimpleImageSlider.js.map +1 -0
- package/lib/commonjs/FullScreenImageSlider.js +126 -0
- package/lib/commonjs/FullScreenImageSlider.js.map +1 -0
- package/lib/commonjs/PageCounter.js +39 -0
- package/lib/commonjs/PageCounter.js.map +1 -0
- package/lib/commonjs/PinchToZoom.js +173 -0
- package/lib/commonjs/PinchToZoom.js.map +1 -0
- package/lib/commonjs/SimpleImageSlider.js +59 -0
- package/lib/commonjs/SimpleImageSlider.js.map +1 -0
- package/lib/commonjs/StyledComponentsThemeProvider.js +51 -0
- package/lib/commonjs/StyledComponentsThemeProvider.js.map +1 -0
- package/lib/commonjs/index.js +35 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/utils/clamp.js +13 -0
- package/lib/commonjs/utils/clamp.js.map +1 -0
- package/lib/commonjs/utils/renderProp.js +17 -0
- package/lib/commonjs/utils/renderProp.js.map +1 -0
- package/lib/module/@types/styled.d.js +2 -0
- package/lib/module/@types/styled.d.js.map +1 -0
- package/lib/module/BaseSimpleImageSlider.js +185 -0
- package/lib/module/BaseSimpleImageSlider.js.map +1 -0
- package/lib/module/FullScreenImageSlider.js +117 -0
- package/lib/module/FullScreenImageSlider.js.map +1 -0
- package/lib/module/PageCounter.js +31 -0
- package/lib/module/PageCounter.js.map +1 -0
- package/lib/module/PinchToZoom.js +165 -0
- package/lib/module/PinchToZoom.js.map +1 -0
- package/lib/module/SimpleImageSlider.js +50 -0
- package/lib/module/SimpleImageSlider.js.map +1 -0
- package/lib/module/StyledComponentsThemeProvider.js +43 -0
- package/lib/module/StyledComponentsThemeProvider.js.map +1 -0
- package/lib/module/index.js +6 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/utils/clamp.js +6 -0
- package/lib/module/utils/clamp.js.map +1 -0
- package/lib/module/utils/renderProp.js +9 -0
- package/lib/module/utils/renderProp.js.map +1 -0
- package/lib/typescript/src/BaseSimpleImageSlider.d.ts +33 -0
- package/lib/typescript/src/BaseSimpleImageSlider.d.ts.map +1 -0
- package/lib/typescript/src/FullScreenImageSlider.d.ts +15 -0
- package/lib/typescript/src/FullScreenImageSlider.d.ts.map +1 -0
- package/lib/typescript/src/PageCounter.d.ts +10 -0
- package/lib/typescript/src/PageCounter.d.ts.map +1 -0
- package/lib/typescript/src/PinchToZoom.d.ts +18 -0
- package/lib/typescript/src/PinchToZoom.d.ts.map +1 -0
- package/lib/typescript/src/SimpleImageSlider.d.ts +11 -0
- package/lib/typescript/src/SimpleImageSlider.d.ts.map +1 -0
- package/lib/typescript/src/StyledComponentsThemeProvider.d.ts +3 -0
- package/lib/typescript/src/StyledComponentsThemeProvider.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/utils/clamp.d.ts +2 -0
- package/lib/typescript/src/utils/clamp.d.ts.map +1 -0
- package/lib/typescript/src/utils/renderProp.d.ts +4 -0
- package/lib/typescript/src/utils/renderProp.d.ts.map +1 -0
- package/package.json +170 -0
- package/src/@types/styled.d.ts +39 -0
- package/src/BaseSimpleImageSlider.tsx +281 -0
- package/src/FullScreenImageSlider.tsx +164 -0
- package/src/PageCounter.tsx +39 -0
- package/src/PinchToZoom.tsx +323 -0
- package/src/SimpleImageSlider.tsx +72 -0
- package/src/StyledComponentsThemeProvider.tsx +48 -0
- package/src/index.tsx +14 -0
- package/src/utils/clamp.ts +4 -0
- package/src/utils/renderProp.tsx +22 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,6BAA6B,MAAM,iCAAiC,CAAC;AAC5E,OAAO,mBAAmB,EAAE,EAAE,KAAK,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAC/F,OAAO,iBAAiB,EAAE,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACrF,OAAO,qBAAqB,EAAE,EAAE,KAAK,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAEjG,OAAO,EACH,6BAA6B,IAAI,8BAA8B,EAC/D,mBAAmB,EACnB,KAAK,0BAA0B,EAC/B,iBAAiB,EACjB,KAAK,sBAAsB,EAC3B,qBAAqB,EACrB,KAAK,0BAA0B,GAClC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clamp.d.ts","sourceRoot":"","sources":["../../../../src/utils/clamp.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,KAAK,UAAW,MAAM,OAAO,MAAM,OAAO,MAAM,WAG5D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderProp.d.ts","sourceRoot":"","sources":["../../../../src/utils/renderProp.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyB,MAAM,OAAO,CAAC;AAE9C,MAAM,MAAM,UAAU,GAChB,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,GAC5B,KAAK,CAAC,YAAY,GAClB,MAAM,GACN,SAAS,GACT,IAAI,CAAC;AAEX,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,SAAS,EAAE,UAAU,qCAYvD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@one-am/react-native-simple-image-slider",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "A simple and performant image slider made with FlashList. Includes a full screen gallery component with gesture support.",
|
|
5
|
+
"main": "lib/commonjs/index",
|
|
6
|
+
"module": "lib/module/index",
|
|
7
|
+
"types": "lib/typescript/src/index.d.ts",
|
|
8
|
+
"react-native": "src/index",
|
|
9
|
+
"source": "src/index",
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"lib",
|
|
13
|
+
"android",
|
|
14
|
+
"ios",
|
|
15
|
+
"cpp",
|
|
16
|
+
"*.podspec",
|
|
17
|
+
"!ios/build",
|
|
18
|
+
"!android/build",
|
|
19
|
+
"!android/gradle",
|
|
20
|
+
"!android/gradlew",
|
|
21
|
+
"!android/gradlew.bat",
|
|
22
|
+
"!android/local.properties",
|
|
23
|
+
"!**/__tests__",
|
|
24
|
+
"!**/__fixtures__",
|
|
25
|
+
"!**/__mocks__",
|
|
26
|
+
"!**/.*"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"example": "yarn workspace @one-am/react-native-simple-image-slider-example",
|
|
30
|
+
"test": "jest",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
33
|
+
"clean": "del-cli lib",
|
|
34
|
+
"prepare": "bob build",
|
|
35
|
+
"release": "release-it"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"react-native",
|
|
39
|
+
"ios",
|
|
40
|
+
"android"
|
|
41
|
+
],
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/one-am-it/react-native-simple-image-slider.git"
|
|
45
|
+
},
|
|
46
|
+
"author": "one-am <info@oneam.it> (https://github.com/one-am-it)",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/one-am-it/react-native-simple-image-slider/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/one-am-it/react-native-simple-image-slider#readme",
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"registry": "https://registry.npmjs.org/",
|
|
54
|
+
"access": "public"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@commitlint/config-conventional": "^17.0.2",
|
|
58
|
+
"@evilmartians/lefthook": "^1.5.0",
|
|
59
|
+
"@react-native/eslint-config": "^0.73.1",
|
|
60
|
+
"@release-it/conventional-changelog": "^5.0.0",
|
|
61
|
+
"@shopify/flash-list": "^1.6.3",
|
|
62
|
+
"@types/jest": "^29.5.5",
|
|
63
|
+
"@types/react": "^18.2.44",
|
|
64
|
+
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
|
65
|
+
"@typescript-eslint/parser": "^5.62.0",
|
|
66
|
+
"babel-plugin-styled-components": "^2.1.4",
|
|
67
|
+
"commitlint": "^17.0.2",
|
|
68
|
+
"del-cli": "^5.1.0",
|
|
69
|
+
"eslint": "^8.56.0",
|
|
70
|
+
"eslint-config-prettier": "^9.1.0",
|
|
71
|
+
"eslint-config-standard-with-typescript": "^37.0.0",
|
|
72
|
+
"eslint-plugin-import": "^2.29.1",
|
|
73
|
+
"eslint-plugin-n": "^16.6.2",
|
|
74
|
+
"eslint-plugin-prettier": "^5.0.1",
|
|
75
|
+
"eslint-plugin-promise": "^6.1.1",
|
|
76
|
+
"eslint-plugin-react": "^7.33.2",
|
|
77
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
78
|
+
"eslint-plugin-react-native": "^4.1.0",
|
|
79
|
+
"expo-haptics": "^12.8.1",
|
|
80
|
+
"expo-image": "^1.10.6",
|
|
81
|
+
"expo-status-bar": "^1.11.1",
|
|
82
|
+
"jest": "^29.7.0",
|
|
83
|
+
"prettier": "^3.2.5",
|
|
84
|
+
"react": "18.2.0",
|
|
85
|
+
"react-native": "0.73.5",
|
|
86
|
+
"react-native-builder-bob": "^0.20.0",
|
|
87
|
+
"react-native-gesture-handler": "^2.15.0",
|
|
88
|
+
"react-native-reanimated": "^3.3.0",
|
|
89
|
+
"react-native-safe-area-context": "^4.9.0",
|
|
90
|
+
"react-native-svg": "^15.1.0",
|
|
91
|
+
"release-it": "^15.0.0",
|
|
92
|
+
"styled-components": "^6.1.8",
|
|
93
|
+
"tabler-icons-react-native": "^2.42.1",
|
|
94
|
+
"typescript": "^5.2.2",
|
|
95
|
+
"typescript-plugin-styled-components": "^3.0.0"
|
|
96
|
+
},
|
|
97
|
+
"resolutions": {
|
|
98
|
+
"@types/react": "^18.2.44",
|
|
99
|
+
"styled-components": "^6"
|
|
100
|
+
},
|
|
101
|
+
"peerDependencies": {
|
|
102
|
+
"@babel/core": "^7.24.0",
|
|
103
|
+
"@babel/runtime": "^7.24.0",
|
|
104
|
+
"expo-haptics": "*",
|
|
105
|
+
"expo-image": "*",
|
|
106
|
+
"expo-status-bar": "*",
|
|
107
|
+
"react": "*",
|
|
108
|
+
"react-dom": "*",
|
|
109
|
+
"react-native": "*",
|
|
110
|
+
"react-native-gesture-handler": "^2.15.0",
|
|
111
|
+
"react-native-reanimated": "~3.6.2",
|
|
112
|
+
"react-native-safe-area-context": "*",
|
|
113
|
+
"styled-components": "^6.1.8",
|
|
114
|
+
"tabler-icons-react-native": "*",
|
|
115
|
+
"react-native-svg": "*"
|
|
116
|
+
},
|
|
117
|
+
"workspaces": [
|
|
118
|
+
"example"
|
|
119
|
+
],
|
|
120
|
+
"packageManager": "yarn@3.6.1",
|
|
121
|
+
"jest": {
|
|
122
|
+
"preset": "react-native",
|
|
123
|
+
"modulePathIgnorePatterns": [
|
|
124
|
+
"<rootDir>/example/node_modules",
|
|
125
|
+
"<rootDir>/lib/"
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
"commitlint": {
|
|
129
|
+
"extends": [
|
|
130
|
+
"@commitlint/config-conventional"
|
|
131
|
+
]
|
|
132
|
+
},
|
|
133
|
+
"release-it": {
|
|
134
|
+
"git": {
|
|
135
|
+
"commitMessage": "chore: release ${version}",
|
|
136
|
+
"tagName": "v${version}"
|
|
137
|
+
},
|
|
138
|
+
"npm": {
|
|
139
|
+
"publish": true
|
|
140
|
+
},
|
|
141
|
+
"github": {
|
|
142
|
+
"release": true
|
|
143
|
+
},
|
|
144
|
+
"gitlab": {
|
|
145
|
+
"release": true
|
|
146
|
+
},
|
|
147
|
+
"plugins": {
|
|
148
|
+
"@release-it/conventional-changelog": {
|
|
149
|
+
"preset": "angular"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
"react-native-builder-bob": {
|
|
154
|
+
"source": "src",
|
|
155
|
+
"output": "lib",
|
|
156
|
+
"targets": [
|
|
157
|
+
"commonjs",
|
|
158
|
+
"module",
|
|
159
|
+
[
|
|
160
|
+
"typescript",
|
|
161
|
+
{
|
|
162
|
+
"project": "tsconfig.build.json"
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
]
|
|
166
|
+
},
|
|
167
|
+
"dependencies": {
|
|
168
|
+
"merge-refs": "^1.2.2"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import 'styled-components/native';
|
|
2
|
+
|
|
3
|
+
export type StyledComponentsTheme = {
|
|
4
|
+
colors: {
|
|
5
|
+
pageCounterBackground: string;
|
|
6
|
+
pageCounterBorder: string;
|
|
7
|
+
fullScreenCloseButton: string;
|
|
8
|
+
descriptionContainerBorder: string;
|
|
9
|
+
};
|
|
10
|
+
styles: {
|
|
11
|
+
spacing: {
|
|
12
|
+
xxs: number;
|
|
13
|
+
xs: number;
|
|
14
|
+
s: number;
|
|
15
|
+
m: number;
|
|
16
|
+
l: number;
|
|
17
|
+
xl: number;
|
|
18
|
+
};
|
|
19
|
+
borderRadius: {
|
|
20
|
+
xs: number;
|
|
21
|
+
s: number;
|
|
22
|
+
m: number;
|
|
23
|
+
l: number;
|
|
24
|
+
xl: number;
|
|
25
|
+
};
|
|
26
|
+
borderWidth: {
|
|
27
|
+
xs: number;
|
|
28
|
+
s: number;
|
|
29
|
+
m: number;
|
|
30
|
+
l: number;
|
|
31
|
+
xl: number;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
declare module 'styled-components/native' {
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
38
|
+
export interface DefaultTheme extends StyledComponentsTheme {}
|
|
39
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
type ReactElement,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { FlashList, type ListRenderItemInfo } from '@shopify/flash-list';
|
|
11
|
+
import mergeRefs from 'merge-refs';
|
|
12
|
+
import { Image, type ImageProps } from 'expo-image';
|
|
13
|
+
import { Pressable, type StyleProp, type ViewStyle } from 'react-native';
|
|
14
|
+
import type ViewToken from '@shopify/flash-list/src/viewability/ViewToken';
|
|
15
|
+
import styled from 'styled-components/native';
|
|
16
|
+
import PageCounter from './PageCounter';
|
|
17
|
+
import PinchToZoom from './PinchToZoom';
|
|
18
|
+
import { type SharedValue } from 'react-native-reanimated';
|
|
19
|
+
import { GestureHandlerRootView, ScrollView } from 'react-native-gesture-handler';
|
|
20
|
+
import renderProp, { type RenderProp } from './utils/renderProp';
|
|
21
|
+
|
|
22
|
+
export type SimpleImageSliderItem = ImageProps & {
|
|
23
|
+
key: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type BaseSimpleImageSliderProps = {
|
|
27
|
+
data: SimpleImageSliderItem[] | undefined;
|
|
28
|
+
style?: StyleProp<ViewStyle>;
|
|
29
|
+
imageWidth?: number;
|
|
30
|
+
imageHeight?: number;
|
|
31
|
+
imageAspectRatio?: number;
|
|
32
|
+
onItemPress?: (item: SimpleImageSliderItem, index: number) => void;
|
|
33
|
+
maxPreviewItems?: number;
|
|
34
|
+
showPageCounter?: boolean;
|
|
35
|
+
pageCounterPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
36
|
+
renderPageCounter?: (currentPage: number, totalPages: number) => ReactElement;
|
|
37
|
+
TopRightComponent?: RenderProp;
|
|
38
|
+
TopLeftComponent?: RenderProp;
|
|
39
|
+
BottomRightComponent?: RenderProp;
|
|
40
|
+
BottomLeftComponent?: RenderProp;
|
|
41
|
+
indexOverride?: number;
|
|
42
|
+
onViewableItemChange?: (index: number) => void;
|
|
43
|
+
enablePinchToZoom?: boolean;
|
|
44
|
+
onPinchToZoomTranslationChange?: (
|
|
45
|
+
x: SharedValue<number>,
|
|
46
|
+
y: SharedValue<number>,
|
|
47
|
+
scale: SharedValue<number>
|
|
48
|
+
) => void;
|
|
49
|
+
onPinchToZoomRequestClose?: () => void;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const StyledAbsoluteComponentContainer = styled.View<{
|
|
53
|
+
position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
54
|
+
}>`
|
|
55
|
+
z-index: 1000;
|
|
56
|
+
position: absolute;
|
|
57
|
+
bottom: ${({ position, theme }) =>
|
|
58
|
+
position === 'bottom-left' || position === 'bottom-right'
|
|
59
|
+
? `${theme.styles.spacing.m}px`
|
|
60
|
+
: undefined};
|
|
61
|
+
top: ${({ position, theme }) =>
|
|
62
|
+
position === 'top-left' || position === 'top-right'
|
|
63
|
+
? `${theme.styles.spacing.m}px`
|
|
64
|
+
: undefined};
|
|
65
|
+
left: ${({ position, theme }) =>
|
|
66
|
+
position === 'top-left' || position === 'bottom-left'
|
|
67
|
+
? `${theme.styles.spacing.m}px`
|
|
68
|
+
: undefined};
|
|
69
|
+
right: ${({ position, theme }) =>
|
|
70
|
+
position === 'top-right' || position === 'bottom-right'
|
|
71
|
+
? `${theme.styles.spacing.m}px`
|
|
72
|
+
: undefined};
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
const StyledPageCounter = styled(PageCounter)<{
|
|
76
|
+
position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
77
|
+
}>`
|
|
78
|
+
z-index: 1000;
|
|
79
|
+
position: absolute;
|
|
80
|
+
bottom: ${({ position, theme }) =>
|
|
81
|
+
position === 'bottom-left' || position === 'bottom-right'
|
|
82
|
+
? `${theme.styles.spacing.m}px`
|
|
83
|
+
: undefined};
|
|
84
|
+
top: ${({ position, theme }) =>
|
|
85
|
+
position === 'top-left' || position === 'top-right'
|
|
86
|
+
? `${theme.styles.spacing.m}px`
|
|
87
|
+
: undefined};
|
|
88
|
+
left: ${({ position, theme }) =>
|
|
89
|
+
position === 'top-left' || position === 'bottom-left'
|
|
90
|
+
? `${theme.styles.spacing.m}px`
|
|
91
|
+
: undefined};
|
|
92
|
+
right: ${({ position, theme }) =>
|
|
93
|
+
position === 'top-right' || position === 'bottom-right'
|
|
94
|
+
? `${theme.styles.spacing.m}px`
|
|
95
|
+
: undefined};
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
const StyledContainer = styled(GestureHandlerRootView)<{ aspectRatio?: number }>`
|
|
99
|
+
aspect-ratio: ${({ aspectRatio }) => aspectRatio ?? 4 / 3};
|
|
100
|
+
width: 100%;
|
|
101
|
+
`;
|
|
102
|
+
|
|
103
|
+
const StyledImage = styled(Image)<
|
|
104
|
+
ImageProps & {
|
|
105
|
+
imageWidth?: number;
|
|
106
|
+
imageHeight?: number;
|
|
107
|
+
imageAspectRatio?: number;
|
|
108
|
+
}
|
|
109
|
+
>`
|
|
110
|
+
width: ${({ imageWidth }) => (imageWidth ? `${imageWidth}px` : '100%')};
|
|
111
|
+
height: ${({ imageHeight }) => (imageHeight ? `${imageHeight}px` : '100%')};
|
|
112
|
+
aspect-ratio: ${({ imageAspectRatio }) => (imageAspectRatio ? imageAspectRatio : 4 / 3)};
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
const StyledPinchToZoom = styled(PinchToZoom)`
|
|
116
|
+
z-index: 1000;
|
|
117
|
+
`;
|
|
118
|
+
|
|
119
|
+
const BaseListImageSlider = forwardRef<
|
|
120
|
+
FlashList<SimpleImageSliderItem>,
|
|
121
|
+
BaseSimpleImageSliderProps
|
|
122
|
+
>(function BaseListImageSlider(
|
|
123
|
+
{
|
|
124
|
+
data,
|
|
125
|
+
style,
|
|
126
|
+
imageWidth,
|
|
127
|
+
imageHeight,
|
|
128
|
+
imageAspectRatio,
|
|
129
|
+
onItemPress,
|
|
130
|
+
maxPreviewItems,
|
|
131
|
+
showPageCounter = true,
|
|
132
|
+
pageCounterPosition = 'bottom-left',
|
|
133
|
+
renderPageCounter,
|
|
134
|
+
TopRightComponent,
|
|
135
|
+
TopLeftComponent,
|
|
136
|
+
BottomRightComponent,
|
|
137
|
+
BottomLeftComponent,
|
|
138
|
+
indexOverride,
|
|
139
|
+
onViewableItemChange,
|
|
140
|
+
enablePinchToZoom = false,
|
|
141
|
+
onPinchToZoomTranslationChange,
|
|
142
|
+
onPinchToZoomRequestClose,
|
|
143
|
+
},
|
|
144
|
+
ref
|
|
145
|
+
) {
|
|
146
|
+
const listRef = useRef<FlashList<SimpleImageSliderItem>>(null);
|
|
147
|
+
const [currentItem, setCurrentItem] = useState(0);
|
|
148
|
+
|
|
149
|
+
const slicedData = useMemo(
|
|
150
|
+
() => (maxPreviewItems !== undefined ? data?.slice(0, maxPreviewItems) ?? [] : data ?? []),
|
|
151
|
+
[data, maxPreviewItems]
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
setCurrentItem(indexOverride ?? 0);
|
|
156
|
+
|
|
157
|
+
listRef.current?.scrollToIndex({ index: indexOverride ?? 0, animated: false });
|
|
158
|
+
}, [indexOverride, slicedData]);
|
|
159
|
+
|
|
160
|
+
const keyExtractor = useCallback((item: SimpleImageSliderItem) => item.key, []);
|
|
161
|
+
|
|
162
|
+
const renderItem = useCallback(
|
|
163
|
+
({ item, index }: ListRenderItemInfo<SimpleImageSliderItem>) => {
|
|
164
|
+
return (
|
|
165
|
+
<Pressable onPress={() => onItemPress?.(item, index)}>
|
|
166
|
+
<StyledImage
|
|
167
|
+
transition={200}
|
|
168
|
+
placeholder={item.placeholder}
|
|
169
|
+
placeholderContentFit={'cover'}
|
|
170
|
+
imageWidth={imageWidth}
|
|
171
|
+
imageHeight={imageHeight}
|
|
172
|
+
imageAspectRatio={imageAspectRatio}
|
|
173
|
+
recyclingKey={item.key}
|
|
174
|
+
source={item.source}
|
|
175
|
+
contentFit={'cover'}
|
|
176
|
+
contentPosition={'center'}
|
|
177
|
+
/>
|
|
178
|
+
</Pressable>
|
|
179
|
+
);
|
|
180
|
+
},
|
|
181
|
+
[imageAspectRatio, imageHeight, imageWidth, onItemPress]
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const onViewableItemsChanged = useCallback(
|
|
185
|
+
({ viewableItems }: { viewableItems: ViewToken[]; changed: ViewToken[] }) => {
|
|
186
|
+
const newIndex = viewableItems[0]?.index;
|
|
187
|
+
if (newIndex !== undefined && newIndex !== null) {
|
|
188
|
+
setCurrentItem(newIndex);
|
|
189
|
+
onViewableItemChange?.(newIndex);
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
[onViewableItemChange]
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const [scrollEnabled, setScrollEnabled] = useState(true);
|
|
196
|
+
|
|
197
|
+
const onScaleChange = useCallback(() => {
|
|
198
|
+
setScrollEnabled(false);
|
|
199
|
+
}, []);
|
|
200
|
+
|
|
201
|
+
const onScaleReset = useCallback(() => {
|
|
202
|
+
setScrollEnabled(true);
|
|
203
|
+
}, []);
|
|
204
|
+
|
|
205
|
+
const list = (
|
|
206
|
+
<FlashList
|
|
207
|
+
renderScrollComponent={ScrollView}
|
|
208
|
+
scrollEnabled={scrollEnabled}
|
|
209
|
+
disableScrollViewPanResponder={enablePinchToZoom ? !scrollEnabled : false}
|
|
210
|
+
ref={mergeRefs(ref, listRef)}
|
|
211
|
+
initialScrollIndex={indexOverride ?? currentItem ?? 0}
|
|
212
|
+
onViewableItemsChanged={onViewableItemsChanged}
|
|
213
|
+
viewabilityConfig={{
|
|
214
|
+
itemVisiblePercentThreshold: 55,
|
|
215
|
+
}}
|
|
216
|
+
decelerationRate={'fast'}
|
|
217
|
+
pagingEnabled={true}
|
|
218
|
+
showsHorizontalScrollIndicator={false}
|
|
219
|
+
showsVerticalScrollIndicator={false}
|
|
220
|
+
horizontal={true}
|
|
221
|
+
keyExtractor={keyExtractor}
|
|
222
|
+
renderItem={renderItem}
|
|
223
|
+
data={slicedData}
|
|
224
|
+
estimatedItemSize={
|
|
225
|
+
imageWidth ?? (imageHeight ? imageHeight * (imageAspectRatio ?? 4 / 3) : 350)
|
|
226
|
+
}
|
|
227
|
+
/>
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<StyledContainer aspectRatio={imageAspectRatio} style={style}>
|
|
232
|
+
{enablePinchToZoom ? (
|
|
233
|
+
<StyledPinchToZoom
|
|
234
|
+
onRequestClose={onPinchToZoomRequestClose}
|
|
235
|
+
onTranslationChange={onPinchToZoomTranslationChange}
|
|
236
|
+
onScaleChange={onScaleChange}
|
|
237
|
+
onScaleReset={onScaleReset}
|
|
238
|
+
maximumZoomScale={5}
|
|
239
|
+
minimumZoomScale={1}
|
|
240
|
+
>
|
|
241
|
+
{list}
|
|
242
|
+
</StyledPinchToZoom>
|
|
243
|
+
) : (
|
|
244
|
+
list
|
|
245
|
+
)}
|
|
246
|
+
{showPageCounter ? (
|
|
247
|
+
renderPageCounter ? (
|
|
248
|
+
renderPageCounter(currentItem + 1, slicedData.length)
|
|
249
|
+
) : (
|
|
250
|
+
<StyledPageCounter
|
|
251
|
+
position={pageCounterPosition}
|
|
252
|
+
totalPages={slicedData.length}
|
|
253
|
+
currentPage={currentItem + 1}
|
|
254
|
+
/>
|
|
255
|
+
)
|
|
256
|
+
) : null}
|
|
257
|
+
{TopRightComponent ? (
|
|
258
|
+
<StyledAbsoluteComponentContainer position={'top-right'}>
|
|
259
|
+
{renderProp(TopRightComponent)}
|
|
260
|
+
</StyledAbsoluteComponentContainer>
|
|
261
|
+
) : null}
|
|
262
|
+
{TopLeftComponent ? (
|
|
263
|
+
<StyledAbsoluteComponentContainer position={'top-left'}>
|
|
264
|
+
{renderProp(TopLeftComponent)}
|
|
265
|
+
</StyledAbsoluteComponentContainer>
|
|
266
|
+
) : null}
|
|
267
|
+
{BottomRightComponent ? (
|
|
268
|
+
<StyledAbsoluteComponentContainer position={'bottom-right'}>
|
|
269
|
+
{renderProp(BottomRightComponent)}
|
|
270
|
+
</StyledAbsoluteComponentContainer>
|
|
271
|
+
) : null}
|
|
272
|
+
{BottomLeftComponent ? (
|
|
273
|
+
<StyledAbsoluteComponentContainer position={'bottom-left'}>
|
|
274
|
+
{renderProp(BottomLeftComponent)}
|
|
275
|
+
</StyledAbsoluteComponentContainer>
|
|
276
|
+
) : null}
|
|
277
|
+
</StyledContainer>
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
export default BaseListImageSlider;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
type ReactNode,
|
|
4
|
+
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useState,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import { Modal, type ScaledSize, StyleSheet, useWindowDimensions } from 'react-native';
|
|
10
|
+
import { IconX } from 'tabler-icons-react-native';
|
|
11
|
+
import Animated, {
|
|
12
|
+
runOnJS,
|
|
13
|
+
type SharedValue,
|
|
14
|
+
useAnimatedStyle,
|
|
15
|
+
useSharedValue,
|
|
16
|
+
withTiming,
|
|
17
|
+
} from 'react-native-reanimated';
|
|
18
|
+
import { setStatusBarStyle } from 'expo-status-bar';
|
|
19
|
+
import styled, { type DefaultTheme, useTheme } from 'styled-components/native';
|
|
20
|
+
import { FlashList } from '@shopify/flash-list';
|
|
21
|
+
import BaseListImageSlider, {
|
|
22
|
+
type BaseSimpleImageSliderProps,
|
|
23
|
+
type SimpleImageSliderItem,
|
|
24
|
+
} from './BaseSimpleImageSlider';
|
|
25
|
+
import { type EdgeInsets, useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
26
|
+
|
|
27
|
+
export type FullScreenImageSliderProps = BaseSimpleImageSliderProps & {
|
|
28
|
+
open?: boolean;
|
|
29
|
+
onRequestClose?: () => void;
|
|
30
|
+
renderDescription?: (index: number) => ReactNode;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const StyledDescriptionContainer = styled.View`
|
|
34
|
+
position: absolute;
|
|
35
|
+
border-top-width: 1px;
|
|
36
|
+
border-top-color: ${({ theme }) => theme.colors.descriptionContainerBorder};
|
|
37
|
+
width: 100%;
|
|
38
|
+
padding-top: ${({ theme }) => theme.styles.spacing.l}px;
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
const StyledModalCloseButton = styled.TouchableOpacity`
|
|
42
|
+
position: absolute;
|
|
43
|
+
z-index: 1000;
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
const StyledModalContentContainer = styled(Animated.View)`
|
|
47
|
+
align-items: center;
|
|
48
|
+
justify-content: center;
|
|
49
|
+
gap: ${({ theme }) => theme.styles.spacing.m}px;
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
const FullScreenImageSlider = forwardRef<
|
|
53
|
+
FlashList<SimpleImageSliderItem>,
|
|
54
|
+
FullScreenImageSliderProps
|
|
55
|
+
>(function FullScreenImageSlider(
|
|
56
|
+
{ open, onRequestClose, renderDescription, onViewableItemChange, ...props },
|
|
57
|
+
ref
|
|
58
|
+
) {
|
|
59
|
+
const windowDimensions = useWindowDimensions();
|
|
60
|
+
const theme = useTheme();
|
|
61
|
+
const safeAreaInsets = useSafeAreaInsets();
|
|
62
|
+
const styles = useMemo(
|
|
63
|
+
() => makeStyles(theme, safeAreaInsets, windowDimensions),
|
|
64
|
+
[theme, safeAreaInsets, windowDimensions]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const [internalIndex, setInternalIndex] = useState<number>(0);
|
|
68
|
+
|
|
69
|
+
const internalOnViewableItemChange = useCallback(
|
|
70
|
+
(index: number) => {
|
|
71
|
+
setInternalIndex(index);
|
|
72
|
+
onViewableItemChange?.(index);
|
|
73
|
+
},
|
|
74
|
+
[onViewableItemChange]
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (open) {
|
|
79
|
+
setStatusBarStyle('light');
|
|
80
|
+
} else {
|
|
81
|
+
setStatusBarStyle('dark');
|
|
82
|
+
}
|
|
83
|
+
}, [open]);
|
|
84
|
+
|
|
85
|
+
const backgroundOpacity = useSharedValue(0);
|
|
86
|
+
|
|
87
|
+
const modalContentStyle = useAnimatedStyle(() => {
|
|
88
|
+
return {
|
|
89
|
+
backgroundColor: `rgba(0, 0, 0, ${backgroundOpacity.value})`,
|
|
90
|
+
};
|
|
91
|
+
}, []);
|
|
92
|
+
|
|
93
|
+
const onPinchToZoomTranslationChange = useCallback(
|
|
94
|
+
(x: SharedValue<number>, y: SharedValue<number>, scale: SharedValue<number>) => {
|
|
95
|
+
if (scale.value <= 1) {
|
|
96
|
+
if (x.value === 0 && y.value === 0) {
|
|
97
|
+
runOnJS(setStatusBarStyle)('light');
|
|
98
|
+
backgroundOpacity.value = withTiming(1);
|
|
99
|
+
} else {
|
|
100
|
+
runOnJS(setStatusBarStyle)('dark');
|
|
101
|
+
backgroundOpacity.value = withTiming(0);
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
backgroundOpacity.value = 1;
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
[backgroundOpacity]
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<Modal
|
|
112
|
+
animationType="fade"
|
|
113
|
+
onRequestClose={onRequestClose}
|
|
114
|
+
transparent={true}
|
|
115
|
+
visible={open}
|
|
116
|
+
>
|
|
117
|
+
<StyledModalContentContainer style={[styles.modalContent, modalContentStyle]}>
|
|
118
|
+
<StyledModalCloseButton style={styles.closeButton} onPress={onRequestClose}>
|
|
119
|
+
<IconX color={theme.colors.fullScreenCloseButton} />
|
|
120
|
+
</StyledModalCloseButton>
|
|
121
|
+
<BaseListImageSlider
|
|
122
|
+
enablePinchToZoom={true}
|
|
123
|
+
onPinchToZoomTranslationChange={onPinchToZoomTranslationChange}
|
|
124
|
+
onPinchToZoomRequestClose={onRequestClose}
|
|
125
|
+
{...props}
|
|
126
|
+
onViewableItemChange={internalOnViewableItemChange}
|
|
127
|
+
showPageCounter={false}
|
|
128
|
+
imageWidth={windowDimensions.width}
|
|
129
|
+
ref={ref}
|
|
130
|
+
/>
|
|
131
|
+
|
|
132
|
+
{renderDescription ? (
|
|
133
|
+
<StyledDescriptionContainer style={styles.descriptionContainer}>
|
|
134
|
+
{renderDescription(internalIndex)}
|
|
135
|
+
</StyledDescriptionContainer>
|
|
136
|
+
) : null}
|
|
137
|
+
</StyledModalContentContainer>
|
|
138
|
+
</Modal>
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
export default FullScreenImageSlider;
|
|
143
|
+
|
|
144
|
+
const makeStyles = (
|
|
145
|
+
theme: DefaultTheme,
|
|
146
|
+
safeAreaInsets: EdgeInsets,
|
|
147
|
+
windowDimensions: ScaledSize
|
|
148
|
+
) => {
|
|
149
|
+
return StyleSheet.create({
|
|
150
|
+
closeButton: {
|
|
151
|
+
top: safeAreaInsets.top,
|
|
152
|
+
right: safeAreaInsets.right + theme.styles.spacing.l,
|
|
153
|
+
},
|
|
154
|
+
modalContent: {
|
|
155
|
+
height: windowDimensions.height,
|
|
156
|
+
width: windowDimensions.width,
|
|
157
|
+
},
|
|
158
|
+
descriptionContainer: {
|
|
159
|
+
bottom: safeAreaInsets.bottom + 100,
|
|
160
|
+
paddingLeft: safeAreaInsets.left + theme.styles.spacing.l,
|
|
161
|
+
paddingRight: safeAreaInsets.right + theme.styles.spacing.l,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
};
|