Skip to content

Commit e3f93c3

Browse files
committed
Update gallery page to new layout and add some animation
1 parent dbe65f5 commit e3f93c3

File tree

8 files changed

+250
-46
lines changed

8 files changed

+250
-46
lines changed

package-lock.json

Lines changed: 43 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
},
1212
"dependencies": {
1313
"@reduxjs/toolkit": "^2.2.1",
14-
"clsx": "^2.1.0",
14+
"clsx": "^2.1.1",
15+
"framer-motion": "^11.11.9",
1516
"localforage": "^1.10.0",
1617
"match-sorter": "^6.3.4",
1718
"react": "^18.2.0",
@@ -22,6 +23,7 @@
2223
"react-redux": "^9.1.0",
2324
"react-router-dom": "^6.22.3",
2425
"sort-by": "^1.2.0",
26+
"tailwind-merge": "^2.5.4",
2527
"thumbhash": "^0.1.1"
2628
},
2729
"devDependencies": {

src/assets/img/images.json

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,60 @@
11
{
2+
"3_color_v1": {
3+
"name": "3_color_v1",
4+
"hash": "jPcJLQZ4d594h3hId3GIt3eAhQdY",
5+
"width": 4000,
6+
"height": 6000,
7+
"coordinates": [35.43167669697985, 138.90319717975024]
8+
},
29
"2019_mid_autumn_moon": {
310
"name": "2019_mid_autumn_moon",
411
"hash": "AwgGBIAH2Ed3iHeBfYgHd3FwFw==",
512
"width": 1352,
613
"height": 818,
714
"coordinates": [22.364220754871923, 114.37296101648073]
815
},
16+
"Puffin_dark": {
17+
"name": "Puffin_dark",
18+
"hash": "R9cFDAIopocJqIapl5fYdI889w==",
19+
"width": 1188,
20+
"height": 2017,
21+
"coordinates": [56.18564794208501, -2.559457093813127]
22+
},
923
"2019_sai_kung_east_dam_vibrance_vignette": {
1024
"name": "2019_sai_kung_east_dam_vibrance_vignette",
1125
"hash": "VdcNHIJVeIePeIdZeXZoUFgEgw==",
1226
"width": 5460,
1327
"height": 3071,
1428
"coordinates": [22.364220754871923, 114.37296101648073]
1529
},
16-
"3_color_v1": {
17-
"name": "3_color_v1",
18-
"hash": "jPcJLQZ4d594h3hId3GIt3eAhQdY",
19-
"width": 4000,
20-
"height": 6000,
21-
"coordinates": [35.43167669697985, 138.90319717975024]
22-
},
2330
"DSC02378": {
2431
"name": "DSC02378",
2532
"hash": "V9cNLoR4d4d/d4h5h4iGaIeHhnBoCcc=",
2633
"width": 2160,
2734
"height": 1728,
2835
"coordinates": [47.566324470845636, 13.649596639977283]
2936
},
37+
"first_edit_dark_v2": {
38+
"name": "first_edit_dark_v2",
39+
"hash": "S9cJDgAItxi3aHeId3l2h4h35493r7c=",
40+
"width": 3333,
41+
"height": 4166,
42+
"coordinates": [46.378813302789084, 13.838457536045542]
43+
},
3044
"IMG_0794": {
3145
"name": "IMG_0794",
3246
"hash": "kRgKJYRmeXd9iIeAejh4jER0QGQH",
3347
"width": 6000,
3448
"height": 4000,
3549
"coordinates": [39.72138253626133, 21.634741657240383]
3650
},
51+
"milkyway_v1": {
52+
"name": "milkyway_v1",
53+
"hash": "jecFFARgJqq2eGtWiIeAmgW4eg==",
54+
"width": 2982,
55+
"height": 5016,
56+
"coordinates": [22.364220754871923, 114.37296101648073]
57+
},
3758
"Isle_Of_May_Cliff_Vibrant_Ig": {
3859
"name": "Isle_Of_May_Cliff_Vibrant_Ig",
3960
"hash": "HMcJJIK6iIdvhnh8l5ZysDcDaw==",
@@ -69,13 +90,6 @@
6990
"height": 4000,
7091
"coordinates": [39.287331706727116, 20.339684495626514]
7192
},
72-
"Puffin_dark": {
73-
"name": "Puffin_dark",
74-
"hash": "R9cFDAIopocJqIapl5fYdI889w==",
75-
"width": 1188,
76-
"height": 2017,
77-
"coordinates": [56.18564794208501, -2.559457093813127]
78-
},
7993
"Scotland_Bass_Rock_Dark_High_Contrast": {
8094
"name": "Scotland_Bass_Rock_Dark_High_Contrast",
8195
"hash": "UecJDYCKh4h/eIdJZZh1d4NgVQlW",
@@ -96,19 +110,5 @@
96110
"width": 4000,
97111
"height": 6000,
98112
"coordinates": [46.36790829748465, 14.09028245728108]
99-
},
100-
"first_edit_dark_v2": {
101-
"name": "first_edit_dark_v2",
102-
"hash": "S9cJDgAItxi3aHeId3l2h4h35493r7c=",
103-
"width": 3333,
104-
"height": 4166,
105-
"coordinates": [46.378813302789084, 13.838457536045542]
106-
},
107-
"milkyway_v1": {
108-
"name": "milkyway_v1",
109-
"hash": "jecFFARgJqq2eGtWiIeAmgW4eg==",
110-
"width": 2982,
111-
"height": 5016,
112-
"coordinates": [22.364220754871923, 114.37296101648073]
113113
}
114114
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import React, { useState } from "react";
2+
import { motion } from "framer-motion";
3+
import { cn } from "../../utils/utils";
4+
5+
type Card = {
6+
id: string;
7+
content: JSX.Element | React.ReactNode | string;
8+
className: string;
9+
thumbnail: JSX.Element;
10+
};
11+
12+
export const LayoutGrid = ({ cards }: { cards: Card[] }) => {
13+
const [selected, setSelected] = useState<Card | null>(null);
14+
const [lastSelected, setLastSelected] = useState<Card | null>(null);
15+
16+
const handleClick = (card: Card) => {
17+
setLastSelected(selected);
18+
setSelected(card);
19+
};
20+
21+
const handleOutsideClick = () => {
22+
setLastSelected(selected);
23+
setSelected(null);
24+
};
25+
26+
return (
27+
<div className="w-full h-full p-2 sm:p-10 grid grid-cols-1 md:grid-cols-4 max-w-7xl mx-auto gap-4 relative">
28+
{cards.map((card, i) => (
29+
<div key={i} className={cn(card.className, "")}>
30+
<motion.div
31+
onClick={() => handleClick(card)}
32+
className={cn(
33+
card.className,
34+
"relative overflow-hidden m-auto",
35+
selected?.id === card.id
36+
? "rounded-lg cursor-pointer max-w-xl absolute inset-x-4 m-auto z-50 flex justify-center items-center flex-wrap flex-col"
37+
: lastSelected?.id === card.id
38+
? "z-40 rounded-xl h-full w-full"
39+
: "rounded-xl h-full w-full",
40+
(i >= (cards.length - 2) && selected?.id === card.id) && 'bottom-20 sm:bottom-40'
41+
)}
42+
layoutId={`card-${card.id}`}
43+
>
44+
{selected?.id === card.id && <SelectedCard selected={selected} />}
45+
<ImageComponent card={card} />
46+
</motion.div>
47+
</div>
48+
))}
49+
50+
<motion.div
51+
onClick={handleOutsideClick}
52+
className={cn(
53+
"absolute h-full w-full left-0 top-0 bg-black opacity-0 z-10",
54+
selected?.id ? "pointer-events-auto" : "pointer-events-none"
55+
)}
56+
animate={{ opacity: selected?.id ? 0.7 : 0 }}
57+
/>
58+
</div>
59+
);
60+
};
61+
62+
const ImageComponent = ({ card }: { card: Card }) => {
63+
// return (
64+
// <motion.img
65+
// layoutId={`image-${card.id}-image`}
66+
// src={card.thumbnail}
67+
// height="500"
68+
// width="500"
69+
// className={cn(
70+
// "object-cover object-top absolute inset-0 h-full w-full transition duration-200"
71+
// )}
72+
// alt="thumbnail"
73+
// />
74+
// );
75+
return card.thumbnail
76+
};
77+
78+
const SelectedCard = ({ selected }: { selected: Card | null }) => {
79+
return (
80+
<div className="bg-transparent h-full w-full flex flex-col justify-end rounded-lg shadow-2xl relative z-[60]">
81+
<motion.div
82+
initial={{
83+
opacity: 0,
84+
}}
85+
animate={{
86+
opacity: 0.6,
87+
}}
88+
className="absolute inset-0 h-full w-full bg-black opacity-60 z-10"
89+
/>
90+
<motion.div
91+
layoutId={`content-${selected?.id}`}
92+
initial={{
93+
opacity: 0,
94+
y: 100,
95+
}}
96+
animate={{
97+
opacity: 1,
98+
y: 0,
99+
}}
100+
exit={{
101+
opacity: 0,
102+
y: 100,
103+
}}
104+
transition={{
105+
duration: 0.3,
106+
ease: "easeInOut",
107+
}}
108+
className="relative px-8 pb-4 z-[70]"
109+
>
110+
{selected?.content}
111+
</motion.div>
112+
</div>
113+
);
114+
};

src/components/Gallery/index.tsx

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,41 @@
11
import LazyImage from '../LazyImage';
2+
import { LayoutGrid } from './LayoutGrid';
23

3-
import { hashToDataUrl } from '../../utils/hash';
44
import IMAGE_JSON from '../../assets/img/images.json';
55

6-
const images = Object.values(IMAGE_JSON).map(({ name, width, height, hash }) => {
6+
import { hashToDataUrl } from '../../utils/hash';
7+
import { cn } from '../../utils/utils';
8+
9+
const Skeleton = () => {
710
return (
8-
<LazyImage className='mb-3' key={hash} src={name} width={width} height={height} placeholderImage={hashToDataUrl(hash)} />
11+
<div>
12+
<p className="font-bold md:text-4xl text-xl text-white">
13+
Rivers are serene
14+
</p>
15+
<p className="font-normal text-base text-white"></p>
16+
<p className="font-normal text-base my-4 max-w-lg text-neutral-200">
17+
A house by the river is a place of peace and tranquility. It&apos;s the
18+
perfect place to relax, unwind, and enjoy life.
19+
</p>
20+
</div>
921
);
22+
};
23+
24+
25+
const images = Object.values(IMAGE_JSON).map(({ name, width, height, hash }) => {
26+
return {
27+
id: hash,
28+
content: <Skeleton />,
29+
className: cn(height > width ? 'col-span-1' : 'md:col-span-2', 'grid'),
30+
thumbnail: (<LazyImage key={hash} className='h-full grid items-center' src={name} width={width} height={height} placeholderImage={hashToDataUrl(hash)} hash={hash} />),
31+
}
1032
});
1133

1234
export default function Gallery() {
13-
1435
return (
1536
<div>
1637
<p>Gallery for preview only, under construction :P</p>
17-
<div className='mt-3'>
18-
{images}
19-
</div>
38+
<LayoutGrid cards={images} />
2039
</div>
2140
)
2241
}

0 commit comments

Comments
 (0)