@trackunit/react-compound-components 0.0.230 → 0.0.232
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/index.cjs.js
CHANGED
|
@@ -10,6 +10,7 @@ var cssClassVarianceUtilities = require('@trackunit/css-class-variance-utilities
|
|
|
10
10
|
var ImageGallery = require('react-image-gallery');
|
|
11
11
|
var tailwindMerge = require('tailwind-merge');
|
|
12
12
|
var reactFormComponents = require('@trackunit/react-form-components');
|
|
13
|
+
var dateAndTimeUtils = require('@trackunit/date-and-time-utils');
|
|
13
14
|
|
|
14
15
|
function _interopNamespaceDefault(e) {
|
|
15
16
|
var n = Object.create(null);
|
|
@@ -178,6 +179,7 @@ const cvaGallery = cssClassVarianceUtilities.cvaMerge([
|
|
|
178
179
|
// This centers thumbnails and makes the clickable area independent of image size
|
|
179
180
|
"[&_.image-gallery-thumbnail]:h-[100px]",
|
|
180
181
|
]);
|
|
182
|
+
const titleStyle = "absolute top-3 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-white bg-black/50 text-sm px-1 rounded-md backdrop-blur-sm";
|
|
181
183
|
/**
|
|
182
184
|
* Show and manage images
|
|
183
185
|
*
|
|
@@ -186,25 +188,28 @@ const cvaGallery = cssClassVarianceUtilities.cvaMerge([
|
|
|
186
188
|
* Reduces bandwidth usage by lazy loading thumbnails and loading smaller versions of images depending on screen size.
|
|
187
189
|
*/
|
|
188
190
|
const ImageCollection = (props) => {
|
|
189
|
-
var _a;
|
|
190
|
-
const {
|
|
191
|
-
const [openImageId, setOpenImageId] = React.useState(
|
|
191
|
+
var _a, _b;
|
|
192
|
+
const { imagesData, actions, emptyPlaceholderActionLabel, additionalItemClassName, uploading } = props;
|
|
193
|
+
const [openImageId, setOpenImageId] = React.useState((_a = imagesData[0]) === null || _a === void 0 ? void 0 : _a.id);
|
|
192
194
|
const [isDeleting, setIsDeleting] = React.useState(false);
|
|
193
195
|
const { isLg } = reactComponents.useViewportSize();
|
|
194
196
|
const fileInputRef = React.useRef(null);
|
|
195
197
|
const imageGalleryRef = React.useRef(null);
|
|
196
198
|
const uploadButton = React.useMemo(() => (actions === null || actions === void 0 ? void 0 : actions.upload) ? (jsxRuntime.jsx("div", { className: "flex justify-end", children: jsxRuntime.jsx(reactComponents.Button, { loading: uploading, onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: actions.upload.label }) })) : null, [actions === null || actions === void 0 ? void 0 : actions.upload, uploading]);
|
|
197
|
-
const items = React.useMemo(() =>
|
|
198
|
-
id:
|
|
199
|
-
original: createOriginalUrl(
|
|
200
|
-
thumbnail: createThumbnailUrl(
|
|
201
|
-
srcSet: createSrcSet(
|
|
202
|
-
|
|
199
|
+
const items = React.useMemo(() => imagesData.map(image => ({
|
|
200
|
+
id: image.id,
|
|
201
|
+
original: createOriginalUrl(image.id),
|
|
202
|
+
thumbnail: createThumbnailUrl(image.id),
|
|
203
|
+
srcSet: createSrcSet(image.id),
|
|
204
|
+
name: image.name,
|
|
205
|
+
timestamp: dateAndTimeUtils.formatDateUtil(new Date(image.timestamp), { dateFormat: "medium", timeFormat: "medium" })
|
|
206
|
+
})), [imagesData]);
|
|
203
207
|
React.useEffect(() => {
|
|
204
|
-
|
|
205
|
-
|
|
208
|
+
var _a;
|
|
209
|
+
if ((!openImageId || !imagesData.map(x => x.id).includes(openImageId)) && imagesData.length > 0) {
|
|
210
|
+
setOpenImageId((_a = imagesData[0]) === null || _a === void 0 ? void 0 : _a.id);
|
|
206
211
|
}
|
|
207
|
-
}, [
|
|
212
|
+
}, [imagesData, openImageId]);
|
|
208
213
|
const onRemove = React.useCallback(async () => {
|
|
209
214
|
var _a;
|
|
210
215
|
if (!(((_a = actions === null || actions === void 0 ? void 0 : actions.remove) === null || _a === void 0 ? void 0 : _a.onRemove) && openImageId)) {
|
|
@@ -212,9 +217,9 @@ const ImageCollection = (props) => {
|
|
|
212
217
|
}
|
|
213
218
|
try {
|
|
214
219
|
let nextIndex;
|
|
215
|
-
const indexToDelete =
|
|
216
|
-
const deletingLastImage = indexToDelete ===
|
|
217
|
-
if (
|
|
220
|
+
const indexToDelete = imagesData.map(x => x.id).indexOf(openImageId);
|
|
221
|
+
const deletingLastImage = indexToDelete === imagesData.length - 1;
|
|
222
|
+
if (imagesData.length === 1) {
|
|
218
223
|
nextIndex = undefined;
|
|
219
224
|
}
|
|
220
225
|
else if (deletingLastImage) {
|
|
@@ -233,20 +238,24 @@ const ImageCollection = (props) => {
|
|
|
233
238
|
finally {
|
|
234
239
|
setIsDeleting(false);
|
|
235
240
|
}
|
|
236
|
-
}, [actions === null || actions === void 0 ? void 0 : actions.remove,
|
|
237
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
241
|
+
}, [actions === null || actions === void 0 ? void 0 : actions.remove, imagesData, openImageId]);
|
|
242
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [imagesData.length > 0 ? (jsxRuntime.jsxs("div", { className: "flex h-[100vh] max-h-[100vh] flex-col", "data-testid": "image-collection", children: [jsxRuntime.jsx(ImageGallery, { additionalClass: cvaGallery(), items: items, onBeforeSlide: index => { var _a; return setOpenImageId((_a = items[index]) === null || _a === void 0 ? void 0 : _a.id); },
|
|
238
243
|
// Library typing is wrong, this is fired when a gallery image is clicked (library typing says div)
|
|
239
244
|
onClick: e => window.open(e.target.src, "_blank"), ref: imageGalleryRef, renderItem: ({ original, srcSet,
|
|
240
245
|
// Typing of the library is not flexible enough to add id, which is only used for the unit tests
|
|
241
246
|
// @ts-ignore
|
|
242
|
-
id,
|
|
247
|
+
id,
|
|
248
|
+
// @ts-ignore
|
|
249
|
+
name,
|
|
250
|
+
// @ts-ignore
|
|
251
|
+
timestamp, }) => (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("img", { alt: "full", className: tailwindMerge.twMerge(additionalItemClassName, "w-[100%]", "object-contain"), "data-testid": `image-${id}`, loading: "lazy", sizes: "(max-width: 480px) 480px, 800px", src: original, srcSet: srcSet }), jsxRuntime.jsx("div", { className: titleStyle, "data-testid": `image-title-${id}`, onClick: (e) => e.stopPropagation(), children: `${name} - ${timestamp}` })] })), renderThumbInner: ({ thumbnail,
|
|
243
252
|
// Typing of the library is not flexible enough to add id, which is only used for the unit tests
|
|
244
253
|
// @ts-ignore
|
|
245
254
|
id, }) => jsxRuntime.jsx("img", { alt: "thumbnail", "data-testid": `thumbnail-${id}`, loading: "lazy", src: thumbnail }), showFullscreenButton: false, showPlayButton: false, showThumbnails: isLg,
|
|
246
255
|
// reduce sliding around during deletion
|
|
247
|
-
slideDuration: isDeleting ? 0 : 200, ...props }), uploadButton, (actions === null || actions === void 0 ? void 0 : actions.remove) ? (jsxRuntime.jsx(reactComponents.Button, { className: "absolute right-2 top-2", loading: isDeleting, onClick: onRemove, prefix: jsxRuntime.jsx(reactComponents.Icon, { name: "Trash", size: "small" }), variant: "secondary-danger", children: actions.remove.label })) : null] })) : ((
|
|
256
|
+
slideDuration: isDeleting ? 0 : 200, ...props }), uploadButton, (actions === null || actions === void 0 ? void 0 : actions.remove) ? (jsxRuntime.jsx(reactComponents.Button, { className: "absolute right-2 top-2", loading: isDeleting, onClick: onRemove, prefix: jsxRuntime.jsx(reactComponents.Icon, { name: "Trash", size: "small" }), variant: "secondary-danger", children: actions.remove.label })) : null] })) : ((_b = actions === null || actions === void 0 ? void 0 : actions.upload) === null || _b === void 0 ? void 0 : _b.onUpload) ? (uploading ? (jsxRuntime.jsx(reactComponents.Spinner, {})) : (jsxRuntime.jsx(reactFormComponents.DropZone, { accept: ACCEPT_FOR_IMAGES, className: "h-screen", dataTestId: "site-images-dropzone", filesSelected: actions.upload.onUpload, label: emptyPlaceholderActionLabel, multiple: true }))) : null, jsxRuntime.jsx("input", {
|
|
248
257
|
// Users can still select other files on mobile devices
|
|
249
|
-
accept: ACCEPT_FOR_IMAGES, hidden: true, multiple: true, onChange: async (e) => {
|
|
258
|
+
accept: ACCEPT_FOR_IMAGES, "data-testid": "site-images-input-files", hidden: true, multiple: true, onChange: async (e) => {
|
|
250
259
|
if (!(e.target.files && (actions === null || actions === void 0 ? void 0 : actions.upload))) {
|
|
251
260
|
return;
|
|
252
261
|
}
|
package/index.esm.js
CHANGED
|
@@ -9,6 +9,7 @@ import { cvaMerge } from '@trackunit/css-class-variance-utilities';
|
|
|
9
9
|
import ImageGallery from 'react-image-gallery';
|
|
10
10
|
import { twMerge } from 'tailwind-merge';
|
|
11
11
|
import { DropZone } from '@trackunit/react-form-components';
|
|
12
|
+
import { formatDateUtil } from '@trackunit/date-and-time-utils';
|
|
12
13
|
|
|
13
14
|
var defaultTranslations = {
|
|
14
15
|
"confirmationDialog.default.message": "Are you certain that you wish to proceed with this course of action?",
|
|
@@ -158,6 +159,7 @@ const cvaGallery = cvaMerge([
|
|
|
158
159
|
// This centers thumbnails and makes the clickable area independent of image size
|
|
159
160
|
"[&_.image-gallery-thumbnail]:h-[100px]",
|
|
160
161
|
]);
|
|
162
|
+
const titleStyle = "absolute top-3 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-white bg-black/50 text-sm px-1 rounded-md backdrop-blur-sm";
|
|
161
163
|
/**
|
|
162
164
|
* Show and manage images
|
|
163
165
|
*
|
|
@@ -166,25 +168,28 @@ const cvaGallery = cvaMerge([
|
|
|
166
168
|
* Reduces bandwidth usage by lazy loading thumbnails and loading smaller versions of images depending on screen size.
|
|
167
169
|
*/
|
|
168
170
|
const ImageCollection = (props) => {
|
|
169
|
-
var _a;
|
|
170
|
-
const {
|
|
171
|
-
const [openImageId, setOpenImageId] = useState(
|
|
171
|
+
var _a, _b;
|
|
172
|
+
const { imagesData, actions, emptyPlaceholderActionLabel, additionalItemClassName, uploading } = props;
|
|
173
|
+
const [openImageId, setOpenImageId] = useState((_a = imagesData[0]) === null || _a === void 0 ? void 0 : _a.id);
|
|
172
174
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
173
175
|
const { isLg } = useViewportSize();
|
|
174
176
|
const fileInputRef = useRef(null);
|
|
175
177
|
const imageGalleryRef = useRef(null);
|
|
176
178
|
const uploadButton = useMemo(() => (actions === null || actions === void 0 ? void 0 : actions.upload) ? (jsx("div", { className: "flex justify-end", children: jsx(Button, { loading: uploading, onClick: () => { var _a; return (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click(); }, children: actions.upload.label }) })) : null, [actions === null || actions === void 0 ? void 0 : actions.upload, uploading]);
|
|
177
|
-
const items = useMemo(() =>
|
|
178
|
-
id:
|
|
179
|
-
original: createOriginalUrl(
|
|
180
|
-
thumbnail: createThumbnailUrl(
|
|
181
|
-
srcSet: createSrcSet(
|
|
182
|
-
|
|
179
|
+
const items = useMemo(() => imagesData.map(image => ({
|
|
180
|
+
id: image.id,
|
|
181
|
+
original: createOriginalUrl(image.id),
|
|
182
|
+
thumbnail: createThumbnailUrl(image.id),
|
|
183
|
+
srcSet: createSrcSet(image.id),
|
|
184
|
+
name: image.name,
|
|
185
|
+
timestamp: formatDateUtil(new Date(image.timestamp), { dateFormat: "medium", timeFormat: "medium" })
|
|
186
|
+
})), [imagesData]);
|
|
183
187
|
useEffect(() => {
|
|
184
|
-
|
|
185
|
-
|
|
188
|
+
var _a;
|
|
189
|
+
if ((!openImageId || !imagesData.map(x => x.id).includes(openImageId)) && imagesData.length > 0) {
|
|
190
|
+
setOpenImageId((_a = imagesData[0]) === null || _a === void 0 ? void 0 : _a.id);
|
|
186
191
|
}
|
|
187
|
-
}, [
|
|
192
|
+
}, [imagesData, openImageId]);
|
|
188
193
|
const onRemove = useCallback(async () => {
|
|
189
194
|
var _a;
|
|
190
195
|
if (!(((_a = actions === null || actions === void 0 ? void 0 : actions.remove) === null || _a === void 0 ? void 0 : _a.onRemove) && openImageId)) {
|
|
@@ -192,9 +197,9 @@ const ImageCollection = (props) => {
|
|
|
192
197
|
}
|
|
193
198
|
try {
|
|
194
199
|
let nextIndex;
|
|
195
|
-
const indexToDelete =
|
|
196
|
-
const deletingLastImage = indexToDelete ===
|
|
197
|
-
if (
|
|
200
|
+
const indexToDelete = imagesData.map(x => x.id).indexOf(openImageId);
|
|
201
|
+
const deletingLastImage = indexToDelete === imagesData.length - 1;
|
|
202
|
+
if (imagesData.length === 1) {
|
|
198
203
|
nextIndex = undefined;
|
|
199
204
|
}
|
|
200
205
|
else if (deletingLastImage) {
|
|
@@ -213,20 +218,24 @@ const ImageCollection = (props) => {
|
|
|
213
218
|
finally {
|
|
214
219
|
setIsDeleting(false);
|
|
215
220
|
}
|
|
216
|
-
}, [actions === null || actions === void 0 ? void 0 : actions.remove,
|
|
217
|
-
return (jsxs(Fragment, { children: [
|
|
221
|
+
}, [actions === null || actions === void 0 ? void 0 : actions.remove, imagesData, openImageId]);
|
|
222
|
+
return (jsxs(Fragment, { children: [imagesData.length > 0 ? (jsxs("div", { className: "flex h-[100vh] max-h-[100vh] flex-col", "data-testid": "image-collection", children: [jsx(ImageGallery, { additionalClass: cvaGallery(), items: items, onBeforeSlide: index => { var _a; return setOpenImageId((_a = items[index]) === null || _a === void 0 ? void 0 : _a.id); },
|
|
218
223
|
// Library typing is wrong, this is fired when a gallery image is clicked (library typing says div)
|
|
219
224
|
onClick: e => window.open(e.target.src, "_blank"), ref: imageGalleryRef, renderItem: ({ original, srcSet,
|
|
220
225
|
// Typing of the library is not flexible enough to add id, which is only used for the unit tests
|
|
221
226
|
// @ts-ignore
|
|
222
|
-
id,
|
|
227
|
+
id,
|
|
228
|
+
// @ts-ignore
|
|
229
|
+
name,
|
|
230
|
+
// @ts-ignore
|
|
231
|
+
timestamp, }) => (jsxs(Fragment, { children: [jsx("img", { alt: "full", className: twMerge(additionalItemClassName, "w-[100%]", "object-contain"), "data-testid": `image-${id}`, loading: "lazy", sizes: "(max-width: 480px) 480px, 800px", src: original, srcSet: srcSet }), jsx("div", { className: titleStyle, "data-testid": `image-title-${id}`, onClick: (e) => e.stopPropagation(), children: `${name} - ${timestamp}` })] })), renderThumbInner: ({ thumbnail,
|
|
223
232
|
// Typing of the library is not flexible enough to add id, which is only used for the unit tests
|
|
224
233
|
// @ts-ignore
|
|
225
234
|
id, }) => jsx("img", { alt: "thumbnail", "data-testid": `thumbnail-${id}`, loading: "lazy", src: thumbnail }), showFullscreenButton: false, showPlayButton: false, showThumbnails: isLg,
|
|
226
235
|
// reduce sliding around during deletion
|
|
227
|
-
slideDuration: isDeleting ? 0 : 200, ...props }), uploadButton, (actions === null || actions === void 0 ? void 0 : actions.remove) ? (jsx(Button, { className: "absolute right-2 top-2", loading: isDeleting, onClick: onRemove, prefix: jsx(Icon, { name: "Trash", size: "small" }), variant: "secondary-danger", children: actions.remove.label })) : null] })) : ((
|
|
236
|
+
slideDuration: isDeleting ? 0 : 200, ...props }), uploadButton, (actions === null || actions === void 0 ? void 0 : actions.remove) ? (jsx(Button, { className: "absolute right-2 top-2", loading: isDeleting, onClick: onRemove, prefix: jsx(Icon, { name: "Trash", size: "small" }), variant: "secondary-danger", children: actions.remove.label })) : null] })) : ((_b = actions === null || actions === void 0 ? void 0 : actions.upload) === null || _b === void 0 ? void 0 : _b.onUpload) ? (uploading ? (jsx(Spinner, {})) : (jsx(DropZone, { accept: ACCEPT_FOR_IMAGES, className: "h-screen", dataTestId: "site-images-dropzone", filesSelected: actions.upload.onUpload, label: emptyPlaceholderActionLabel, multiple: true }))) : null, jsx("input", {
|
|
228
237
|
// Users can still select other files on mobile devices
|
|
229
|
-
accept: ACCEPT_FOR_IMAGES, hidden: true, multiple: true, onChange: async (e) => {
|
|
238
|
+
accept: ACCEPT_FOR_IMAGES, "data-testid": "site-images-input-files", hidden: true, multiple: true, onChange: async (e) => {
|
|
230
239
|
if (!(e.target.files && (actions === null || actions === void 0 ? void 0 : actions.upload))) {
|
|
231
240
|
return;
|
|
232
241
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-compound-components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.232",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"dependencies": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"@trackunit/css-class-variance-utilities": "*",
|
|
14
14
|
"@trackunit/react-form-components": "*",
|
|
15
15
|
"tailwind-merge": "^2.0.0",
|
|
16
|
-
"react-image-gallery": "1.3.0"
|
|
16
|
+
"react-image-gallery": "1.3.0",
|
|
17
|
+
"@trackunit/date-and-time-utils": "*"
|
|
17
18
|
},
|
|
18
19
|
"engines": {
|
|
19
20
|
"node": ">=20.x",
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { ComponentProps } from "react";
|
|
2
2
|
import ImageGallery from "react-image-gallery";
|
|
3
3
|
type Props = Omit<ComponentProps<typeof ImageGallery>, "items"> & {
|
|
4
|
-
|
|
4
|
+
imagesData: {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
timestamp: string;
|
|
8
|
+
}[];
|
|
5
9
|
actions?: {
|
|
6
10
|
upload?: {
|
|
7
11
|
label: string;
|
|
@@ -63,6 +63,11 @@ export declare const Default: {
|
|
|
63
63
|
additionalClass?: string | undefined;
|
|
64
64
|
useTranslate3D?: boolean | undefined;
|
|
65
65
|
isRTL?: boolean | undefined;
|
|
66
|
+
imagesData?: {
|
|
67
|
+
id: string;
|
|
68
|
+
name: string;
|
|
69
|
+
timestamp: string;
|
|
70
|
+
}[] | undefined;
|
|
66
71
|
actions?: {
|
|
67
72
|
upload?: {
|
|
68
73
|
label: string;
|
|
@@ -133,6 +138,11 @@ export declare const EmptyState: {
|
|
|
133
138
|
additionalClass?: string | undefined;
|
|
134
139
|
useTranslate3D?: boolean | undefined;
|
|
135
140
|
isRTL?: boolean | undefined;
|
|
141
|
+
imagesData?: {
|
|
142
|
+
id: string;
|
|
143
|
+
name: string;
|
|
144
|
+
timestamp: string;
|
|
145
|
+
}[] | undefined;
|
|
136
146
|
actions?: {
|
|
137
147
|
upload?: {
|
|
138
148
|
label: string;
|