Skip to content

Commit

Permalink
Merge pull request #128 from Bear29ers/feature/#123_create_gallery_la…
Browse files Browse the repository at this point in the history
…yout

Feature/#123 Create gallery layout(ギャラリーのレイアウトを作成する)
  • Loading branch information
Bear29ers authored Jul 21, 2024
2 parents f88685e + 5ae8ed7 commit c51ad27
Show file tree
Hide file tree
Showing 47 changed files with 1,754 additions and 41 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ const config = {
{
config: 'tailwind.config.ts',
cssFiles: ['**/*.scss', '**/*.css', '!**/node_modules', '!**/.*', '!**/dist', '!**/build'],
whitelist: ['gallery-intro-title'],
},
],
'tailwindcss/classnames-order': 'off',
Expand Down
9 changes: 9 additions & 0 deletions next.config.cjs → next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

const nextConfig = {
reactStrictMode: true,
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '*.cdninstagram.com',
port: '',
},
],
},
webpack: (config) => {
// Enable polling based on env variable being set
if (process.env.NEXT_WEBPACK_USEPOLLING) {
Expand Down
Binary file added public/images/example/image-1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/example/image-2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/example/image-3.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/example/image-4.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/example/image-5.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/example/image-6.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/example/image-7.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
215 changes: 191 additions & 24 deletions src/app/gallery/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,57 @@

import { useEffect, useState } from 'react';

import { AnimatePresence, motion } from 'framer-motion';

import AnimatedText from '@/components/common/AnimatedText/AnimatedText';
import Footer from '@/components/layout/Footer/Footer';
import AnimatedGallery from '@/components/ui/gallery/AnimatedGallery/AnimatedGallery';
import FanningImages from '@/components/ui/gallery/FanningImages/FanningImages';
import GalleryIntro from '@/components/ui/gallery/GalleryIntro/GalleryIntro';
import MainGallery from '@/components/ui/gallery/MainGallery/MainGallery';
import MainVisual from '@/components/ui/gallery/MainVisual/MainVisual';
import ScrollDown from '@/components/ui/gallery/ScrollDown/ScrollDown';
import StaggeredText from '@/components/ui/gallery/StaggeredText/StaggeredText';
import Subhead from '@/components/ui/gallery/Subhead/Subhead';

import useMediaQuery from '@/hooks/useMediaQuery/useMediaQuery';

import { zIndexList } from '@/constants/gallery';

import fetchMedia from '@/libs/fetchMedia';

import type { Media } from '@/types/media';
import type { MediaData, Media } from '@/types/media';

import type { NextPage } from 'next';

const Gallery: NextPage = () => {
const [mediaData, setMediaData] = useState<Media | null>(null);
// media data
const [mediaData, setMediaData] = useState<Media | undefined>(undefined);
const [mainVisual, setMainVisual] = useState<MediaData | undefined>(undefined);
const [animatingMediaList, setAnimatingMediaList] = useState<MediaData[] | undefined>(undefined);

// animation flag
const [loading, setLoading] = useState<boolean>(true);
const [isCompletedIntro, setIsCompletedIntro] = useState<boolean>(false);
const [isCompletedFanning, setIsCompletedFanning] = useState<boolean>(false);
const [isActiveGallery, setIsActiveGallery] = useState<boolean>(false);
const [isFullyGallerySet, setIsFullyGallerySet] = useState<boolean>(false);

// style state
const galleryItemMaxWidth = 'max-w-72';
const [fanningInitialY, setFanningInitialY] = useState<number>(220);
const [fanningStyleList, setFanningStyleList] = useState<{ x: string; y: number; rotate: number }[]>([
{ x: '-105%', y: 120, rotate: -20 },
{ x: '-30%', y: -90, rotate: 5 },
{ x: '-90%', y: 0, rotate: -17 },
{ x: '-5%', y: 40, rotate: 12 },
{ x: '-70%', y: 20, rotate: -10 },
{ x: '5%', y: 160, rotate: 15 },
]);

const isLarge = useMediaQuery('(min-width: 1000px)');
const isMedium = useMediaQuery('(min-width: 700px)');
const isSmall = useMediaQuery('(min-width: 450px)');

const loadMediaData = () => {
fetchMedia()
Expand All @@ -29,32 +69,159 @@ const Gallery: NextPage = () => {
loadMediaData();
}, []);

useEffect(() => {
if (mediaData && mediaData.media.data.length) {
const lastMediaItem = mediaData.media.data.pop();
setMainVisual(lastMediaItem);
const animatingMediaItems = mediaData.media.data.splice(0, 6);
setAnimatingMediaList(animatingMediaItems);
}
}, [mediaData]);

useEffect(() => {
if (isLarge) {
setFanningInitialY(220);
setFanningStyleList([
{ x: '-105%', y: 120, rotate: -20 },
{ x: '-30%', y: -90, rotate: 5 },
{ x: '-90%', y: 0, rotate: -17 },
{ x: '-5%', y: 40, rotate: 12 },
{ x: '-70%', y: 20, rotate: -10 },
{ x: '5%', y: 160, rotate: 15 },
]);
} else if (isMedium) {
setFanningInitialY(220);
setFanningStyleList([
{ x: '-105%', y: 120, rotate: -20 },
{ x: '-30%', y: -90, rotate: 5 },
{ x: '-90%', y: 0, rotate: -17 },
{ x: '-5%', y: 40, rotate: 12 },
{ x: '-70%', y: 20, rotate: -10 },
{ x: '5%', y: 160, rotate: 15 },
]);
} else if (isSmall) {
setFanningInitialY(180);
setFanningStyleList([
{ x: '-90%', y: 50, rotate: -20 },
{ x: '-30%', y: -60, rotate: 5 },
{ x: '-80%', y: -20, rotate: -17 },
{ x: '-25%', y: 15, rotate: 12 },
{ x: '-65%', y: 0, rotate: -10 },
{ x: '-6%', y: 70, rotate: 15 },
]);
} else {
setFanningInitialY(150);
setFanningStyleList([
{ x: '-52%', y: -240, rotate: -2 },
{ x: '-48%', y: -200, rotate: 2 },
{ x: '-53%', y: -160, rotate: -3 },
{ x: '-47%', y: -120, rotate: 3 },
{ x: '-54%', y: -80, rotate: -4 },
{ x: '-46%', y: -40, rotate: 4 },
]);
}
}, [isLarge, isMedium, isSmall]);

const introBgVariants = {
initial: {
y: 0,
},
animate: {
y: '-100%',
transition: {
ease: [0.8, 0, 0.2, 1],
duration: 1,
},
},
};

if (!mediaData || !mediaData.media.data.length || !mainVisual || !animatingMediaList) {
return <div className="fixed flex h-screen w-full flex-col items-center bg-hitGray bg-noise-pattern" />;
}

return (
<div className="flex w-full flex-col items-center px-2.5 text-white xs:px-5 lg:px-0">
<div className="my-24">
<AnimatedText text="Gallery" classes="text-[48px] xs:text-[60px] xsm:text-[80px]" />
</div>
<div>
<h1>Instagram Gallery</h1>
{!mediaData || !mediaData.media.data.length ? (
<div>No data available</div>
<div className="relative flex w-full flex-col items-center px-2.5 text-white xs:px-5 lg:px-0">
{isActiveGallery && (
<div className="my-24">
<AnimatedText text="Gallery" classes="text-[48px] xs:text-[60px] xsm:text-[80px]" />
</div>
)}
<div className="z-10 flex-col flex-center" />
<motion.div
className="fixed flex h-screen w-full flex-col items-center bg-hitGray bg-noise-pattern"
variants={introBgVariants}
initial="initial"
animate={isActiveGallery && 'animate'}>
{!loading && (
<div className="gallery-intro-title flex flex-col items-center gap-y-2">
<StaggeredText textList={['Life', 'in', 'Pixels']} />
<Subhead text="Capturing Moments, Creating Memories." />
</div>
)}
</motion.div>

<AnimatePresence mode="wait">
{loading ? (
<div className="fixed top-1/2 -translate-y-1/2">
<GalleryIntro
mainVisualImageSrc={mainVisual.mediaUrl}
layoutId={mainVisual.id}
mediaList={animatingMediaList}
setState={setLoading}
/>
</div>
) : (
<div>
{mediaData.media.data.map((post, index) => (
<div key={index}>
<img src={post.mediaUrl} alt={post.caption} />
<p>{post.caption}</p>
<p>Likes: {post.likeCount}</p>
<p>Posted by: {post.username}</p>
<a href={post.permalink} target="_blank" rel="noopener noreferrer">
View on Instagram
</a>
<>
{!isFullyGallerySet && (
<div className="fixed h-screen w-full">
<MainVisual
imageSrc={mainVisual.mediaUrl}
layoutId={mainVisual.id}
canAnimate={isActiveGallery}
maxWidth="mlg:max-w-[400px] msm:max-w-[350px] txs:max-w-[250px] max-w-[230px]"
setState={setIsCompletedIntro}
/>
{isCompletedIntro && (
<>
{isCompletedFanning && <ScrollDown state={isActiveGallery} setState={setIsActiveGallery} />}
<FanningImages
mediaList={animatingMediaList}
maxWidth="mlg:max-w-[400px] msm:max-w-[350px] txs:max-w-[250px] max-w-[230px]"
initialY={fanningInitialY}
styles={fanningStyleList}
setState={setIsCompletedFanning}
/>
</>
)}
</div>
))}
</div>
)}
{isActiveGallery && (
<div className="mx-auto w-full max-w-7xl flex-center">
<div className="grid w-fit grid-cols-1 justify-between gap-x-6 gap-y-12 xxs:grid-cols-2 xsm:gap-x-12 xsm:gap-y-20 md:grid-cols-3 lg:gap-24 xl:gap-x-32">
{animatingMediaList.map((media: MediaData, index: number) => (
<AnimatedGallery
layoutId={media.id}
imageSrc={media.mediaUrl}
zIndex={zIndexList[index]!}
maxWidth={galleryItemMaxWidth}
key={media.timestamp}
setState={index === animatingMediaList.length - 1 ? setIsFullyGallerySet : undefined}
/>
))}
{mediaData.media.data.map((media: MediaData) => (
<MainGallery
imageSrc={media.mediaUrl}
id={media.id}
maxWidth={galleryItemMaxWidth}
key={media.timestamp}
/>
))}
</div>
</div>
)}
</>
)}
</div>
{/* Footer */}
</AnimatePresence>
<Footer />
</div>
);
Expand Down
17 changes: 17 additions & 0 deletions src/app/globals.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,20 @@
transform: rotate(-20deg);
}
}

.gallery {
&-intro {
&-title {
@media screen and (height >= 900px) {
margin-top: 144px;
}

@media screen and (height >= 800px) {
display: flex;
}

display: none;
margin: 48px;
}
}
}
2 changes: 1 addition & 1 deletion src/components/common/AnimatedText/AnimatedText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const AnimatedText: FC<Props> = ({ text, classes }) => {
}, []);

return (
<AnimatePresence mode="wait">
<AnimatePresence>
{text.split('').map((letter: string, index: number) => (
<motion.h1
key={index}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/Project/Project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const Project: FC<Props> = ({ project }) => {
</div>
<button
type="button"
className="inline w-fit rounded-xl bg-pink-500 px-4 py-1 text-xs font-medium uppercase transition-colors duration-500 ease-in-out hover:bg-pink-400 xs:text-sm"
className="inline w-fit rounded-xl bg-customRed-500 px-4 py-1 text-xs font-medium uppercase transition-colors duration-500 ease-in-out hover:bg-customRed-400 xs:text-sm"
onClick={handleClick}>
{isShow ? 'Show Less' : 'Show More'}
</button>
Expand Down
12 changes: 6 additions & 6 deletions src/components/ui/Status/Status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,23 @@ const Status: FC = () => {
<div className="size-56 rounded-full bg-darker flex-center xs:size-64 xxs:size-72 md:size-80">
<Image src={ProfileIcon} alt={PROFILE_TEXT.userName} className="w-28 xs:w-36 xxs:w-44 md:w-52" />
</div>
<span className="absolute left-32 top-9 size-2 rounded-full bg-mantis xs:top-12 xs:size-3" />
<span className="absolute left-32 top-9 size-2 rounded-full bg-customGreen-500 xs:top-12 xs:size-3" />
<motion.span
className="absolute left-6 top-12 size-7 rounded-lg bg-maximumBlue xs:top-16 xxs:size-9"
className="absolute left-6 top-12 size-7 rounded-lg bg-customCyan-500 xs:top-16 xxs:size-9"
initial={{ rotate: 60 }}
whileHover={{ scale: 1.2, rotate: 240 }}
transition={{ type: 'spring', stiffness: 400 }}
/>
<span className="absolute bottom-24 left-4 size-3 rounded-full bg-mediumSlateBlue xs:bottom-36 xs:size-4" />
<span className="absolute bottom-24 left-4 size-3 rounded-full bg-customViolet-500 xs:bottom-36 xs:size-4" />
<motion.span
className="absolute right-0 top-9 size-10 rounded-lg bg-mediumSlateBlue xxs:size-12"
className="absolute right-0 top-9 size-10 rounded-lg bg-customViolet-500 xxs:size-12"
initial={{ rotate: 30 }}
whileHover={{ scale: 1.2, rotate: 210 }}
transition={{ type: 'spring', stiffness: 400 }}
/>
<span className="absolute bottom-28 right-10 size-1.5 rounded-full bg-maximumBlue xs:right-16 xs:size-2.5" />
<span className="absolute bottom-28 right-10 size-1.5 rounded-full bg-customCyan-500 xs:right-16 xs:size-2.5" />
<motion.span
className="absolute bottom-6 right-24 size-5 rounded-md bg-mantis xxs:size-7"
className="absolute bottom-6 right-24 size-5 rounded-md bg-customGreen-500 xxs:size-7"
initial={{ rotate: 15 }}
whileHover={{ scale: 1.2, rotate: 195 }}
transition={{ type: 'spring', stiffness: 400 }}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import AnimatedGallery from './AnimatedGallery';

import type { Meta, StoryObj } from '@storybook/react';

const meta: Meta<typeof AnimatedGallery> = {
component: AnimatedGallery,
tags: ['autodocs'],
};

export default meta;

type Story = StoryObj<typeof AnimatedGallery>;

export const Default: Story = {
args: {
layoutId: '18033720356079349',
imageSrc: '/images/example/image-1.jpg',
zIndex: 'z-[1]',
maxWidth: 'max-w-72',
setState: () => {},
},
render: ({ layoutId, imageSrc, zIndex, maxWidth, setState }) => {
return (
<AnimatedGallery
layoutId={layoutId}
imageSrc={imageSrc}
zIndex={zIndex}
maxWidth={maxWidth}
setState={setState}
/>
);
},
};
Loading

0 comments on commit c51ad27

Please sign in to comment.