@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 { imageIds, actions, emptyPlaceholderActionLabel, additionalItemClassName, uploading } = props;
191
- const [openImageId, setOpenImageId] = React.useState(imageIds[0]);
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(() => imageIds.map(imageId => ({
198
- id: imageId,
199
- original: createOriginalUrl(imageId),
200
- thumbnail: createThumbnailUrl(imageId),
201
- srcSet: createSrcSet(imageId),
202
- })), [imageIds]);
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
- if ((!openImageId || !imageIds.includes(openImageId)) && imageIds.length > 0) {
205
- setOpenImageId(imageIds[0]);
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
- }, [imageIds, openImageId]);
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 = imageIds.indexOf(openImageId);
216
- const deletingLastImage = indexToDelete === imageIds.length - 1;
217
- if (imageIds.length === 1) {
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, imageIds, openImageId]);
237
- return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [imageIds.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); },
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, }) => (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 })), renderThumbInner: ({ thumbnail,
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] })) : ((_a = actions === null || actions === void 0 ? void 0 : actions.upload) === null || _a === void 0 ? void 0 : _a.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", {
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 { imageIds, actions, emptyPlaceholderActionLabel, additionalItemClassName, uploading } = props;
171
- const [openImageId, setOpenImageId] = useState(imageIds[0]);
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(() => imageIds.map(imageId => ({
178
- id: imageId,
179
- original: createOriginalUrl(imageId),
180
- thumbnail: createThumbnailUrl(imageId),
181
- srcSet: createSrcSet(imageId),
182
- })), [imageIds]);
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
- if ((!openImageId || !imageIds.includes(openImageId)) && imageIds.length > 0) {
185
- setOpenImageId(imageIds[0]);
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
- }, [imageIds, openImageId]);
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 = imageIds.indexOf(openImageId);
196
- const deletingLastImage = indexToDelete === imageIds.length - 1;
197
- if (imageIds.length === 1) {
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, imageIds, openImageId]);
217
- return (jsxs(Fragment, { children: [imageIds.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); },
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, }) => (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 })), renderThumbInner: ({ thumbnail,
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] })) : ((_a = actions === null || actions === void 0 ? void 0 : actions.upload) === null || _a === void 0 ? void 0 : _a.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", {
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.230",
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
- imageIds: string[];
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;