Skip to content

Commit 97e41f0

Browse files
[TM-3153] Modal content card (#2101)
* [TM-3153] Modal content card * [TM-3153] Fix build * [TM-3153] fix on hover
1 parent 0269164 commit 97e41f0

9 files changed

Lines changed: 148 additions & 94 deletions

File tree

src/pages/project/[uuid]/tabs/LastestImagesSection.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,13 @@ const LastestImagesSectionTab: FC<{ entityUuid: string; entityName: SupportedEnt
2828
}, [entityUuid, entityName])
2929
);
3030

31-
const images = mediaList?.map(media => media.url) ?? [];
32-
return <ImageGalleryCard images={images as string[]} onClickAdd={() => goToTab("gallery")} />;
31+
const images =
32+
mediaList?.map(media => ({
33+
uuid: media.uuid,
34+
src: media.url ?? "",
35+
alt: media.name
36+
})) ?? [];
37+
return <ImageGalleryCard images={images} onClickAdd={() => goToTab("gallery")} />;
3338
};
3439

3540
export default LastestImagesSectionTab;

src/redesignComponents/containers/Modal/Modal.stories.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Image from "next/image";
44
import React, { useState } from "react";
55

66
import ButtonGroup from "@/redesignComponents/actions/Buttons/ButtonGroup/ButtonGroup";
7+
import { GalleryImageType } from "@/redesignComponents/content/ContentCard/ImageGalleryCard/ImageGalleryCard";
78

89
import { getThemedColor } from "../../../lib/theme";
910
import Button from "../../actions/Buttons/Button/Button";
@@ -378,12 +379,10 @@ export const ModalGalleryImagesStory: Story = {
378379
onClose={() => setShowModal(false)}
379380
images={Array.from(
380381
{ length: 10 },
381-
(_, i): { uuid: string; src: string; alt: string; url: string; name: string } => ({
382+
(_, i): GalleryImageType => ({
382383
uuid: `image-${i}`,
383384
src: `https://i.pravatar.cc/300?img=${i}`,
384-
alt: `Image ${i}`,
385-
url: `https://i.pravatar.cc/300?img=${i}`,
386-
name: `Image ${i}`
385+
alt: `Image ${i}`
387386
})
388387
)}
389388
hasMore={false}

src/redesignComponents/containers/Modal/ModalSelectGalleryImages.tsx

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
import { Flex, Grid, GridItem, Spinner, Text } from "@chakra-ui/react";
1+
import { Flex, Spinner, Text } from "@chakra-ui/react";
22
import React, { FC, UIEvent, useCallback } from "react";
33

4-
import GalleryImage from "@/redesignComponents/content/Images/GalleryImage/GalleryImage";
4+
import ImageGalleryCard, {
5+
GalleryImageType
6+
} from "@/redesignComponents/content/ContentCard/ImageGalleryCard/ImageGalleryCard";
57

68
import Modal from "./Modal";
79

810
interface ModalSelectGalleryImagesProps {
911
open: boolean;
1012
onClose: () => void;
11-
images: { uuid: string; src: string; alt: string; url: string; name: string }[];
13+
images: GalleryImageType[];
1214
hasMore: boolean;
1315
isLoading: boolean;
1416
onLoadMore: () => void;
15-
onSelectImage: (image: { uuid: string; src: string; alt: string; url: string; name: string }) => void;
17+
onSelectImage: (image: GalleryImageType) => void;
1618
}
1719

1820
const ModalSelectGalleryImages: FC<ModalSelectGalleryImagesProps> = ({
@@ -49,26 +51,13 @@ const ModalSelectGalleryImages: FC<ModalSelectGalleryImagesProps> = ({
4951
maxHeight="100vh"
5052
content={
5153
<Flex direction="column" maxHeight="70vh">
52-
<Grid
53-
templateColumns="repeat(3, 1fr)"
54-
gap="4"
55-
alignItems="center"
56-
overflowY="auto"
57-
maxHeight="70vh"
58-
paddingRight="4"
54+
<ImageGalleryCard
55+
className="overflow-y-auto py-0 pr-4 pl-0"
56+
images={images}
57+
columns={3}
5958
onScroll={handleScroll}
60-
>
61-
{images?.map(image => (
62-
<GridItem key={image.uuid} as="button" onClick={() => onSelectImage(image)}>
63-
<GalleryImage
64-
src={image.url ?? ""}
65-
alt={image.name ?? ""}
66-
className="max-h-[140px] min-w-full"
67-
hoverContent=" "
68-
/>
69-
</GridItem>
70-
))}
71-
</Grid>
59+
onSelectImage={onSelectImage}
60+
/>
7261
{isLoading && (
7362
<Flex justifyContent="center" alignItems="center" py={4}>
7463
<Spinner />

src/redesignComponents/content/ContentCard/ImageGalleryCard/ImageGalleryCard.stories.tsx

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Box } from "@chakra-ui/react";
22
import { Meta, StoryObj } from "@storybook/react";
33
import { FC } from "react";
44

5-
import ImageGalleryCard from "./ImageGalleryCard";
5+
import ImageGalleryCard, { GalleryImageType } from "./ImageGalleryCard";
66

77
const meta: Meta<typeof ImageGalleryCard> = {
88
title: "Redesign Components/Content/Content Card/Image Gallery Card",
@@ -19,11 +19,27 @@ const meta: Meta<typeof ImageGalleryCard> = {
1919
export default meta;
2020
type Story = StoryObj<typeof ImageGalleryCard>;
2121

22-
const sampleImages = [
23-
"https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=400",
24-
"https://images.unsplash.com/photo-1472214103451-9374bd1c798e?w=400",
25-
"https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400",
26-
"https://images.unsplash.com/photo-1511497584788-876760111969?w=400"
22+
const sampleImages: GalleryImageType[] = [
23+
{
24+
uuid: "1",
25+
src: "https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=400",
26+
alt: "Image 1"
27+
},
28+
{
29+
uuid: "2",
30+
src: "https://images.unsplash.com/photo-1472214103451-9374bd1c798e?w=400",
31+
alt: "Image 2"
32+
},
33+
{
34+
uuid: "3",
35+
src: "https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400",
36+
alt: "Image 3"
37+
},
38+
{
39+
uuid: "4",
40+
src: "https://images.unsplash.com/photo-1511497584788-876760111969?w=400",
41+
alt: "Image 4"
42+
}
2743
];
2844

2945
const StoryWrapper: FC<{ children: React.ReactNode }> = ({ children }) => (
@@ -39,9 +55,6 @@ const StoryWrapper: FC<{ children: React.ReactNode }> = ({ children }) => (
3955
</Box>
4056
);
4157

42-
/**
43-
* Default image gallery card with multiple images
44-
*/
4558
export const Default: Story = {
4659
args: {
4760
images: sampleImages
@@ -53,9 +66,6 @@ export const Default: Story = {
5366
)
5467
};
5568

56-
/**
57-
* Image gallery card with exactly 3 images
58-
*/
5969
export const ThreeImages: Story = {
6070
args: {
6171
images: sampleImages.slice(0, 3)
@@ -67,9 +77,6 @@ export const ThreeImages: Story = {
6777
)
6878
};
6979

70-
/**
71-
* Image gallery card with a single image
72-
*/
7380
export const SingleImage: Story = {
7481
args: {
7582
images: [sampleImages[0]]
@@ -81,9 +88,6 @@ export const SingleImage: Story = {
8188
)
8289
};
8390

84-
/**
85-
* Image gallery card with no images (empty state)
86-
*/
8791
export const NoImages: Story = {
8892
args: {
8993
images: []
@@ -94,3 +98,15 @@ export const NoImages: Story = {
9498
</StoryWrapper>
9599
)
96100
};
101+
102+
export const ThreeColumns: Story = {
103+
args: {
104+
images: sampleImages,
105+
columns: 3
106+
},
107+
render: args => (
108+
<StoryWrapper>
109+
<ImageGalleryCard {...args} />
110+
</StoryWrapper>
111+
)
112+
};

src/redesignComponents/content/ContentCard/ImageGalleryCard/ImageGalleryCard.tsx

Lines changed: 70 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,85 @@
1-
import { Box, Grid, GridItem } from "@chakra-ui/react";
2-
import { FC } from "react";
1+
import { Grid, GridItem } from "@chakra-ui/react";
2+
import { DetailedHTMLProps, FC, HTMLAttributes } from "react";
3+
import { twMerge } from "tailwind-merge";
34

45
import GalleryImage from "../../Images/GalleryImage/GalleryImage";
5-
import { MIN_ITEMS } from "./constants";
6+
import { MIN_ITEMS, MIN_ROWS } from "./constants";
67

7-
interface IImageGalleryCardProps {
8-
images: string[] | undefined;
8+
export interface GalleryImageType {
9+
uuid: string;
10+
src: string;
11+
alt: string;
12+
}
13+
14+
interface IImageGalleryCardProps extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
15+
images: GalleryImageType[];
916
onClickAdd?: () => void;
17+
columns?: number;
18+
imageSize?: number;
19+
className?: string;
20+
onSelectImage?: (image: GalleryImageType) => void;
1021
}
1122

12-
const ImageGalleryCard: FC<IImageGalleryCardProps> = ({ images, onClickAdd }) => {
23+
const ImageGalleryCard: FC<IImageGalleryCardProps> = ({
24+
images,
25+
onClickAdd,
26+
columns = 2,
27+
imageSize = 164,
28+
onSelectImage,
29+
onScroll,
30+
className
31+
}) => {
1332
const imageCount = images?.length ?? 0;
14-
const itemsToShow = Math.max(MIN_ITEMS, imageCount);
33+
const minimumCapacity = Math.max(MIN_ITEMS, columns * MIN_ROWS);
34+
const roundedCapacity = Math.ceil(Math.max(imageCount, 1) / columns) * columns;
35+
const itemsToShow = Math.max(minimumCapacity, roundedCapacity);
1536
const placeholderCount = itemsToShow - imageCount;
1637
const isEmpty = imageCount === 0;
1738

1839
return (
19-
<Box padding={5} backgroundColor="white" borderRadius="md">
20-
<Grid templateColumns="repeat(2, 1fr)" gapY={5} gapX={5}>
21-
{images?.map((image, index) => (
22-
<GridItem key={`image-${index}-${image}`}>
23-
<GalleryImage
24-
src={image}
25-
alt="Image"
26-
className="bg-theme-neutral-200 h-full min-h-full w-full min-w-full"
27-
/>
28-
</GridItem>
29-
))}
30-
{Array.from({ length: placeholderCount }).map((_, index) => {
31-
const isFirstPlaceholder = index === 0;
32-
const showContent = isEmpty && isFirstPlaceholder;
40+
<Grid
41+
templateColumns={`repeat(${columns}, 1fr)`}
42+
gapY={5}
43+
gapX={5}
44+
onScroll={onScroll}
45+
className={twMerge("bg-theme-neutral-100 rounded-md p-5", className)}
46+
>
47+
{images?.map(image => (
48+
<GridItem key={image.uuid}>
49+
<GalleryImage
50+
onClickEdit={onSelectImage && (() => onSelectImage(image))}
51+
src={image.src}
52+
alt={image.alt}
53+
size={imageSize}
54+
className="bg-theme-neutral-200 min-w-full"
55+
hoverContent={" "}
56+
/>
57+
</GridItem>
58+
))}
59+
{Array.from({ length: placeholderCount }).map((_, index) => {
60+
const isFirstPlaceholder = index === 0;
61+
const showContent = isEmpty && isFirstPlaceholder;
3362

34-
return (
35-
<GridItem key={`placeholder-${index}`}>
36-
{showContent ? (
37-
<GalleryImage alt="No images available" isAdd={true} onClickEdit={onClickAdd} />
38-
) : (
39-
<div className="bg-theme-neutral-200 rounded-md" style={{ width: 164, height: 164 }} />
40-
)}
41-
</GridItem>
42-
);
43-
})}
44-
</Grid>
45-
</Box>
63+
return (
64+
<GridItem key={`placeholder-${index}`}>
65+
{showContent ? (
66+
<GalleryImage
67+
className="min-w-full"
68+
alt="No images available"
69+
isAdd={true}
70+
onClickAdd={onClickAdd}
71+
size={imageSize}
72+
/>
73+
) : (
74+
<div
75+
className="bg-theme-neutral-200 min-w-full rounded-md"
76+
style={{ width: imageSize, height: imageSize }}
77+
/>
78+
)}
79+
</GridItem>
80+
);
81+
})}
82+
</Grid>
4683
);
4784
};
4885

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export const MIN_ITEMS = 4;
2+
export const MIN_ROWS = 2;

src/redesignComponents/content/Images/GalleryImage/GalleryImage.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ export interface GalleryImageProps extends DetailedHTMLProps<HTMLAttributes<HTML
99
className?: string;
1010
isAdd?: boolean;
1111
onClickEdit?: () => void;
12+
onClickAdd?: () => void;
1213
hoverContent?: React.ReactNode;
1314
}
1415

15-
const GalleryImage: FC<GalleryImageProps> = ({ alt, isAdd, onClickEdit, hoverContent, ...rest }) => {
16+
const GalleryImage: FC<GalleryImageProps> = ({ alt, isAdd, onClickEdit, onClickAdd, hoverContent, ...rest }) => {
1617
return (
1718
<BaseImage
1819
{...rest}
@@ -22,6 +23,7 @@ const GalleryImage: FC<GalleryImageProps> = ({ alt, isAdd, onClickEdit, hoverCon
2223
classNamesHover="m-0.5 border border-white w-[calc(100%-0.25rem)] h-[calc(100%-0.25rem)] absolute inset-0 rounded-md"
2324
isAdd={isAdd}
2425
onClickEdit={onClickEdit}
26+
onClickAdd={onClickAdd}
2527
hoverContent={hoverContent}
2628
/>
2729
);

src/redesignComponents/content/Images/Image.tsx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Image from "next/image";
44
import { CSSProperties, DetailedHTMLProps, FC, HTMLAttributes, useEffect, useState } from "react";
55

66
import Text from "@/components/elements/Text/Text";
7+
import Button from "@/redesignComponents/actions/Buttons/Button/Button";
78
import MenuCustom from "@/redesignComponents/actions/Buttons/Menu/MenuCustom";
89
import { EditIcon, PhotoAddIcon, RejectedIcon } from "@/redesignComponents/foundations/Icons";
910
export interface BaseImageProps extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
@@ -17,6 +18,7 @@ export interface BaseImageProps extends DetailedHTMLProps<HTMLAttributes<HTMLDiv
1718
isAdd?: boolean;
1819
hoverContent?: React.ReactNode;
1920
onClickEdit?: () => void;
21+
onClickAdd?: () => void;
2022
menuItems?: {
2123
label: string;
2224
value: string;
@@ -38,6 +40,7 @@ const BaseImage: FC<BaseImageProps> = ({
3840
isAdd = false,
3941
hoverContent,
4042
onClickEdit,
43+
onClickAdd,
4144
menuItems,
4245
menuLabel,
4346
style,
@@ -97,17 +100,12 @@ const BaseImage: FC<BaseImageProps> = ({
97100
)}
98101
>
99102
<PhotoAddIcon className="h-6 w-6" />
100-
<MenuCustom
101-
label={menuLabel ?? "Add Image"}
102-
items={[
103-
...(menuItems?.map(item => ({
104-
label: item.label,
105-
value: item.value,
106-
startIcon: item.startIcon,
107-
onClick: item.onClick
108-
})) ?? [])
109-
]}
110-
/>
103+
{onClickAdd && (
104+
<Button onClick={onClickAdd} variant="borderless" size="small">
105+
{t("Add Image")}
106+
</Button>
107+
)}
108+
{menuItems && <MenuCustom label={menuLabel ?? "Add Image"} items={menuItems} />}
111109
</div>
112110
) : (
113111
<div

0 commit comments

Comments
 (0)