@mgonza02/goal-image-gallery 1.0.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/IMPLEMENTATION_SUMMARY.md +259 -0
- package/LICENSE +21 -0
- package/MIGRATION.md +428 -0
- package/QUICK_REFERENCE.md +235 -0
- package/README.md +479 -0
- package/lib/GoalImageGallery.js +244 -0
- package/lib/components/ImageGalleryEmptyState.js +87 -0
- package/lib/components/ImageGalleryGrid.js +108 -0
- package/lib/components/ImageGalleryItem.js +175 -0
- package/lib/components/ImageGalleryModal.js +149 -0
- package/lib/components/index.js +5 -0
- package/lib/examples.js +503 -0
- package/lib/hooks/use-image-gallery.js +252 -0
- package/lib/index.d.ts +187 -0
- package/lib/index.js +148 -0
- package/lib/styles/styled-components.js +173 -0
- package/lib/utils/image-utils.js +129 -0
- package/package.json +93 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Box } from '@mui/material';
|
|
2
|
+
import { alpha, styled } from '@mui/material/styles';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Styled component for image containers with enhanced hover effects
|
|
6
|
+
*/
|
|
7
|
+
export const StyledImageContainer = styled(Box)(_ref => {
|
|
8
|
+
let {
|
|
9
|
+
theme
|
|
10
|
+
} = _ref;
|
|
11
|
+
return {
|
|
12
|
+
position: 'relative',
|
|
13
|
+
borderRadius: theme.spacing(1.5),
|
|
14
|
+
overflow: 'hidden',
|
|
15
|
+
backgroundColor: theme.palette.background.paper,
|
|
16
|
+
border: `1px solid ${theme.palette.divider}`,
|
|
17
|
+
cursor: 'pointer',
|
|
18
|
+
transition: theme.transitions.create(['border-color', 'box-shadow', 'transform'], {
|
|
19
|
+
duration: theme.transitions.duration.short
|
|
20
|
+
}),
|
|
21
|
+
'&:hover': {
|
|
22
|
+
borderColor: theme.palette.primary.main,
|
|
23
|
+
boxShadow: theme.shadows[4],
|
|
24
|
+
transform: 'translateY(-2px)',
|
|
25
|
+
'& .image-overlay': {
|
|
26
|
+
opacity: 1
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Styled component for image overlay with action buttons
|
|
34
|
+
*/
|
|
35
|
+
export const ImageOverlay = styled(Box)(_ref2 => {
|
|
36
|
+
let {
|
|
37
|
+
theme
|
|
38
|
+
} = _ref2;
|
|
39
|
+
return {
|
|
40
|
+
position: 'absolute',
|
|
41
|
+
top: 0,
|
|
42
|
+
left: 0,
|
|
43
|
+
right: 0,
|
|
44
|
+
bottom: 0,
|
|
45
|
+
background: alpha(theme.palette.common.black, 0.5),
|
|
46
|
+
display: 'flex',
|
|
47
|
+
alignItems: 'center',
|
|
48
|
+
justifyContent: 'center',
|
|
49
|
+
opacity: 0,
|
|
50
|
+
transition: theme.transitions.create('opacity', {
|
|
51
|
+
duration: theme.transitions.duration.short
|
|
52
|
+
}),
|
|
53
|
+
'& .MuiIconButton-root': {
|
|
54
|
+
color: theme.palette.common.white,
|
|
55
|
+
backgroundColor: alpha(theme.palette.common.black, 0.7),
|
|
56
|
+
margin: theme.spacing(0.5),
|
|
57
|
+
'&:hover': {
|
|
58
|
+
backgroundColor: alpha(theme.palette.common.black, 0.9)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Styled component for modal container
|
|
66
|
+
*/
|
|
67
|
+
export const StyledModalContainer = styled(Box)(_ref3 => {
|
|
68
|
+
let {
|
|
69
|
+
theme,
|
|
70
|
+
isMobile
|
|
71
|
+
} = _ref3;
|
|
72
|
+
return {
|
|
73
|
+
position: 'absolute',
|
|
74
|
+
top: '50%',
|
|
75
|
+
left: '50%',
|
|
76
|
+
transform: 'translate(-50%, -50%)',
|
|
77
|
+
width: isMobile ? '95vw' : '90vw',
|
|
78
|
+
maxWidth: 1200,
|
|
79
|
+
height: isMobile ? '95vh' : '90vh',
|
|
80
|
+
backgroundColor: theme.palette.background.paper,
|
|
81
|
+
borderRadius: theme.spacing(2),
|
|
82
|
+
overflow: 'hidden',
|
|
83
|
+
display: 'flex',
|
|
84
|
+
flexDirection: 'column',
|
|
85
|
+
boxShadow: 24
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Styled component for modal header
|
|
91
|
+
*/
|
|
92
|
+
export const StyledModalHeader = styled(Box)(_ref4 => {
|
|
93
|
+
let {
|
|
94
|
+
theme
|
|
95
|
+
} = _ref4;
|
|
96
|
+
return {
|
|
97
|
+
display: 'flex',
|
|
98
|
+
justifyContent: 'space-between',
|
|
99
|
+
alignItems: 'center',
|
|
100
|
+
padding: theme.spacing(2),
|
|
101
|
+
borderBottom: `1px solid ${theme.palette.divider}`,
|
|
102
|
+
backgroundColor: alpha(theme.palette.background.paper, 0.95)
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Styled component for modal body
|
|
108
|
+
*/
|
|
109
|
+
export const StyledModalBody = styled(Box)(_ref5 => {
|
|
110
|
+
let {
|
|
111
|
+
theme
|
|
112
|
+
} = _ref5;
|
|
113
|
+
return {
|
|
114
|
+
flex: 1,
|
|
115
|
+
display: 'flex',
|
|
116
|
+
alignItems: 'center',
|
|
117
|
+
justifyContent: 'center',
|
|
118
|
+
overflow: 'hidden',
|
|
119
|
+
position: 'relative',
|
|
120
|
+
backgroundColor: theme.palette.grey[50]
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Styled component for modal footer
|
|
126
|
+
*/
|
|
127
|
+
export const StyledModalFooter = styled(Box)(_ref6 => {
|
|
128
|
+
let {
|
|
129
|
+
theme
|
|
130
|
+
} = _ref6;
|
|
131
|
+
return {
|
|
132
|
+
display: 'flex',
|
|
133
|
+
justifyContent: 'space-between',
|
|
134
|
+
alignItems: 'center',
|
|
135
|
+
padding: theme.spacing(2),
|
|
136
|
+
borderTop: `1px solid ${theme.palette.divider}`,
|
|
137
|
+
backgroundColor: alpha(theme.palette.background.paper, 0.95)
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Styled component for empty state container
|
|
143
|
+
*/
|
|
144
|
+
export const StyledEmptyStateContainer = styled(Box)(_ref7 => {
|
|
145
|
+
let {
|
|
146
|
+
theme
|
|
147
|
+
} = _ref7;
|
|
148
|
+
return {
|
|
149
|
+
display: 'flex',
|
|
150
|
+
flexDirection: 'column',
|
|
151
|
+
justifyContent: 'center',
|
|
152
|
+
alignItems: 'center',
|
|
153
|
+
padding: theme.spacing(2),
|
|
154
|
+
minHeight: 300
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Styled component for image grid container
|
|
160
|
+
*/
|
|
161
|
+
export const StyledImageGridContainer = styled(Box)(_ref8 => {
|
|
162
|
+
let {
|
|
163
|
+
theme
|
|
164
|
+
} = _ref8;
|
|
165
|
+
return {
|
|
166
|
+
display: 'flex',
|
|
167
|
+
flexDirection: 'column',
|
|
168
|
+
justifyContent: 'center',
|
|
169
|
+
alignItems: 'center',
|
|
170
|
+
padding: theme.spacing(2),
|
|
171
|
+
minHeight: 200
|
|
172
|
+
};
|
|
173
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image gallery utility functions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Handle image errors with fallback logic
|
|
7
|
+
*/
|
|
8
|
+
export const handleImageError = (e, noImageUrl) => {
|
|
9
|
+
if (e.target.src.includes(noImageUrl)) {
|
|
10
|
+
e.target.src = '/static/images/pic-share-logo.png';
|
|
11
|
+
} else {
|
|
12
|
+
e.target.onerror = null;
|
|
13
|
+
e.target.src = noImageUrl;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Download image from URL
|
|
19
|
+
*/
|
|
20
|
+
export const downloadImage = (imageUrl, imageCode) => {
|
|
21
|
+
const link = document.createElement('a');
|
|
22
|
+
link.href = imageUrl;
|
|
23
|
+
link.download = `image-${imageCode}.jpg`;
|
|
24
|
+
link.click();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Process image codes into standardized format
|
|
29
|
+
*/
|
|
30
|
+
export const processImageCodes = imageCodes => {
|
|
31
|
+
if (!imageCodes) return [];
|
|
32
|
+
const splitedImages = Array.isArray(imageCodes) ? imageCodes : imageCodes.split(/[\s,]+/);
|
|
33
|
+
return splitedImages.map((image_code, index) => ({
|
|
34
|
+
id: index,
|
|
35
|
+
width: 1,
|
|
36
|
+
height: 1,
|
|
37
|
+
title: 'Goal Image',
|
|
38
|
+
image_code
|
|
39
|
+
}));
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get responsive grid columns based on screen size and multiple flag
|
|
44
|
+
*/
|
|
45
|
+
export const getGridColumns = (isMobile, isTablet, multiple) => {
|
|
46
|
+
if (isMobile) return 6; // 2 columns on mobile
|
|
47
|
+
if (isTablet) return multiple ? 4 : 6; // 3 or 2 columns on tablet
|
|
48
|
+
return multiple ? 3 : 4; // 4 or 3 columns on desktop
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Validate image file type
|
|
53
|
+
*/
|
|
54
|
+
export const isValidImageFile = file => {
|
|
55
|
+
const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
|
|
56
|
+
return validTypes.includes(file.type);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Format file size for display
|
|
61
|
+
*/
|
|
62
|
+
export const formatFileSize = bytes => {
|
|
63
|
+
if (bytes === 0) return '0 Bytes';
|
|
64
|
+
const k = 1024;
|
|
65
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
66
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
67
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Generate unique ID for components
|
|
72
|
+
*/
|
|
73
|
+
export const generateId = () => {
|
|
74
|
+
return Math.random().toString(36).substr(2, 9);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Debounce function for performance optimization
|
|
79
|
+
*/
|
|
80
|
+
export const debounce = (func, wait) => {
|
|
81
|
+
let timeout;
|
|
82
|
+
return function executedFunction() {
|
|
83
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
84
|
+
args[_key] = arguments[_key];
|
|
85
|
+
}
|
|
86
|
+
const later = () => {
|
|
87
|
+
clearTimeout(timeout);
|
|
88
|
+
func(...args);
|
|
89
|
+
};
|
|
90
|
+
clearTimeout(timeout);
|
|
91
|
+
timeout = setTimeout(later, wait);
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if device supports touch
|
|
97
|
+
*/
|
|
98
|
+
export const isTouchDevice = () => {
|
|
99
|
+
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get image dimensions from URL
|
|
104
|
+
*/
|
|
105
|
+
export const getImageDimensions = url => {
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
const img = new Image();
|
|
108
|
+
img.onload = () => {
|
|
109
|
+
resolve({
|
|
110
|
+
width: img.width,
|
|
111
|
+
height: img.height
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
img.onerror = reject;
|
|
115
|
+
img.src = url;
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Create image preview from file
|
|
121
|
+
*/
|
|
122
|
+
export const createImagePreview = file => {
|
|
123
|
+
return new Promise((resolve, reject) => {
|
|
124
|
+
const reader = new FileReader();
|
|
125
|
+
reader.onload = e => resolve(e.target.result);
|
|
126
|
+
reader.onerror = reject;
|
|
127
|
+
reader.readAsDataURL(file);
|
|
128
|
+
});
|
|
129
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mgonza02/goal-image-gallery",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Enhanced, modular image gallery component for React applications",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"module": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"react",
|
|
10
|
+
"image-gallery",
|
|
11
|
+
"material-ui",
|
|
12
|
+
"component",
|
|
13
|
+
"upload",
|
|
14
|
+
"drag-drop",
|
|
15
|
+
"clipboard",
|
|
16
|
+
"modal",
|
|
17
|
+
"zoom",
|
|
18
|
+
"responsive"
|
|
19
|
+
],
|
|
20
|
+
"author": "Goal Team",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/goal-systems/goal-image-gallery"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/goal-systems/goal-image-gallery#readme",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/goal-systems/goal-image-gallery/issues"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"lib/",
|
|
35
|
+
"README.md",
|
|
36
|
+
"QUICK_REFERENCE.md",
|
|
37
|
+
"MIGRATION.md",
|
|
38
|
+
"IMPLEMENTATION_SUMMARY.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
],
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"react": "^18.0.0",
|
|
43
|
+
"react-dom": "^18.0.0",
|
|
44
|
+
"@mui/material": "^5.0.0",
|
|
45
|
+
"@mui/icons-material": "^5.0.0",
|
|
46
|
+
"@emotion/react": "^11.0.0",
|
|
47
|
+
"@emotion/styled": "^11.0.0"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"prop-types": "^15.8.0"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "npm run clean && npm run build:js && npm run build:types",
|
|
54
|
+
"build:js": "babel src --out-dir lib --copy-files --ignore '**/*.test.js'",
|
|
55
|
+
"build:types": "cp src/index.d.ts lib/index.d.ts",
|
|
56
|
+
"prepublishOnly": "npm run build",
|
|
57
|
+
"test": "jest",
|
|
58
|
+
"lint": "eslint src/**/*.js --fix",
|
|
59
|
+
"lint:check": "eslint src/**/*.js",
|
|
60
|
+
"format": "prettier --write src/**/*.js",
|
|
61
|
+
"format:check": "prettier --check src/**/*.js",
|
|
62
|
+
"clean": "rm -rf lib",
|
|
63
|
+
"validate": "npm run lint:check && npm run format:check",
|
|
64
|
+
"dev": "npm run build -- --watch",
|
|
65
|
+
"prepack": "npm run validate && npm run build"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@babel/cli": "^7.20.0",
|
|
69
|
+
"@babel/core": "^7.20.0",
|
|
70
|
+
"@babel/preset-env": "^7.20.0",
|
|
71
|
+
"@babel/preset-react": "^7.18.0",
|
|
72
|
+
"eslint": "^8.30.0",
|
|
73
|
+
"eslint-plugin-react": "^7.31.0",
|
|
74
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
75
|
+
"jest": "^29.0.0",
|
|
76
|
+
"prettier": "^2.8.0"
|
|
77
|
+
},
|
|
78
|
+
"browserslist": {
|
|
79
|
+
"production": [
|
|
80
|
+
">0.2%",
|
|
81
|
+
"not dead",
|
|
82
|
+
"not op_mini all"
|
|
83
|
+
],
|
|
84
|
+
"development": [
|
|
85
|
+
"last 1 chrome version",
|
|
86
|
+
"last 1 firefox version",
|
|
87
|
+
"last 1 safari version"
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
"engines": {
|
|
91
|
+
"node": ">=16.0.0"
|
|
92
|
+
}
|
|
93
|
+
}
|