Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/soryeongk/detail page #3

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
18 changes: 2 additions & 16 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,16 @@
import React from 'react';
import { ApolloProvider } from '@apollo/client';
import { ThemeProvider } from 'styled-components';
import GlobalStyle from './styles/globalStyle';
import client from './apollo/client';
import theme from './styles/theme';
import Slider from './components/Slider';

const dummyCardInfo = {
desc: 'μ™„λ²½ν•œ λ‰΄μš• νœ΄κ°€ κ³„νšν•˜κΈ°',
reviewCount: 139,
reviewRating: 4.99,
price: 11688,
nation: 'λ―Έκ΅­',
thumbnail:
'https://a0.muscache.com/im/pictures/lombard/MtTemplate-2496585-poster/original/e6de8fae-018d-4411-b0a3-81bbb6e4e5c3.jpeg',
};
import Router from './pages/Router';

function App() {
return (
<ApolloProvider client={client}>
<ThemeProvider theme={theme}>
<GlobalStyle />
<Slider
label="μ „ 세계 ν˜„μ§€ 호슀트의 도움을 λ°›μ•„ 여행을 κ³„νšν•΄λ³΄μ„Έμš”"
cardList={Array(10).fill(dummyCardInfo)}
/>
<Router />
</ThemeProvider>
</ApolloProvider>
);
Expand Down
Empty file removed src/assets/.keep
Empty file.
58 changes: 58 additions & 0 deletions src/assets/svgIcons/GallerySliderIcons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import styled, { css } from 'styled-components';

interface IconArrowProps {
direction: 'next' | 'prev';
onGalleryIndex: (direction: 'next' | 'prev') => void;
}

const StSvg = styled.svg<{ direction: 'next' | 'prev' }>`
position: absolute;
z-index: 5;

top: 250px;
${({ direction }) =>
direction === 'prev'
? css`
left: 5px;
`
: css`
right: 5px;
`}
display: block;

height: 19px;
width: 19px;
fill: rgb(255, 255, 255);
`;

function IconArrow(props: IconArrowProps) {
const { direction, onGalleryIndex } = props;
const arrow =
direction === 'next' ? (
<path
d="m4.29 1.71a1 1 0 1 1 1.42-1.41l8 8a1 1 0 0 1 0 1.41l-8 8a1 1 0 1 1 -1.42-1.41l7.29-7.29z"
fillRule="evenodd"
/>
) : (
<path
d="m13.7 16.29a1 1 0 1 1 -1.42 1.41l-8-8a1 1 0 0 1 0-1.41l8-8a1 1 0 1 1 1.42 1.41l-7.29 7.29z"
fillRule="evenodd"
/>
);

return (
<StSvg
onClick={() => onGalleryIndex(direction)}
viewBox="0 0 18 18"
role="img"
aria-hidden="false"
aria-label={direction}
focusable="false"
direction={direction}
>
{arrow}
</StSvg>
);
}

export default { IconArrow };
44 changes: 44 additions & 0 deletions src/assets/svgIcons/images/GalleryImages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const MOCK_IMAGE_LIST = [
{
url: 'https://dummyimage.com/720x960/FF5A5F/fff.png&text=%20%20AirBorB01',
alt: '더미 사진01',
},
{
url: 'https://dummyimage.com/720x960/00A699/fff.png&text=%20%20AirBorB02',
alt: '더미 사진02',
},
{
url: 'https://dummyimage.com/720x960/FC642D/fff.png&text=%20%20AirBorB03',
alt: '더미 사진03',
},
{
url: 'https://dummyimage.com/720x960/484848/fff.png&text=%20%20AirBorB04',
alt: '더미 사진04',
},
{
url: 'https://dummyimage.com/720x960/767676/fff.png&text=%20%20AirBorB05',
alt: '더미 사진05',
},
{
url: 'https://dummyimage.com/720x960/FF5A5F/fff.png&text=%20%20AirBorB06',
alt: '더미 사진06',
},
{
url: 'https://dummyimage.com/720x960/00A699/fff.png&text=%20%20AirBorB07',
alt: '더미 사진07',
},
{
url: 'https://dummyimage.com/720x960/FC642D/fff.png&text=%20%20AirBorB08',
alt: '더미 사진08',
},
{
url: 'https://dummyimage.com/720x960/484848/fff.png&text=%20%20AirBorB09',
alt: '더미 사진09',
},
{
url: 'https://dummyimage.com/720x960/767676/fff.png&text=%20%20AirBorB10',
alt: '더미 사진10',
},
];

export default MOCK_IMAGE_LIST;
Empty file removed src/components/.keep
Empty file.
134 changes: 134 additions & 0 deletions src/components/GallerySlider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import styled, { css } from 'styled-components';
import GallerySliderIcons from '../assets/svgIcons/GallerySliderIcons';
import MOCK_IMAGE_LIST from '../assets/svgIcons/images/GalleryImages';

const StGalleryWrapper = styled.section`
position: relative;

width: 100%;
height: 500px;
`;

const StImagesWrapper = styled.div`
position: relative;
overflow-x: scroll;
display: flex;
height: 100%;
`;

const StImage = styled.img`
position: absolute;
left: 0;

width: 100%;
height: 100%;
`;

const StSpareImage = styled(StImage)<{ direction: 'next' | 'prev' }>`
visibility: hidden;

${({ direction }) =>
direction === 'prev'
? css`
left: -375px;
`
: css`
right: -375px;
`}
`;

const StCarouseDotWrapper = styled.nav`
position: absolute;
bottom: 5px;

display: flex;
justify-content: space-between;

padding: 0 15px;
width: 100%;
`;

const StCarouselDot = styled.button<{ isactive: boolean }>`
border: none;
background-color: ${({ isactive }) => (isactive ? 'white' : '#FFFFFF66')};
width: 25px;
height: 2px;
`;

export default function GallerySlider() {
const GalleryMaxIndex = MOCK_IMAGE_LIST.length - 1;
const [galleryIndex, setGalleryIndex] = useState<number>(0);

const getGalleryIndex = useCallback(
(current: number, direction: 'next' | 'prev') => {
if (direction === 'prev') {
if (current === 0) return GalleryMaxIndex;
return current - 1;
}
if (current === GalleryMaxIndex) return 0;
return current + 1;
},
[GalleryMaxIndex],
);

const handleGalleryIndex = (direction: 'next' | 'prev') => {
setGalleryIndex((current) => getGalleryIndex(current, direction));
};

const prevGalleryIndex = useMemo(() => {
return getGalleryIndex(galleryIndex, 'prev');
}, [galleryIndex, getGalleryIndex]);

const nextGalleryIndex = useMemo(() => {
return getGalleryIndex(galleryIndex, 'next');
}, [galleryIndex, getGalleryIndex]);

const arrow = (direction: 'prev' | 'next') => {
return (
<GallerySliderIcons.IconArrow direction={direction} onGalleryIndex={handleGalleryIndex} />
);
};

useEffect(() => {
const carouselTimer = setTimeout(() => {
setGalleryIndex((current) => getGalleryIndex(current, 'next'));
}, 5000);

return () => {
clearTimeout(carouselTimer);
};
}, [getGalleryIndex, galleryIndex]);

return (
<StGalleryWrapper>
{arrow('prev')}

<StImagesWrapper>
<StSpareImage
direction="prev"
src={MOCK_IMAGE_LIST[prevGalleryIndex].url}
alt={MOCK_IMAGE_LIST[prevGalleryIndex].alt}
/>
<StImage src={MOCK_IMAGE_LIST[galleryIndex].url} alt={MOCK_IMAGE_LIST[galleryIndex].alt} />
<StSpareImage
direction="next"
src={MOCK_IMAGE_LIST[nextGalleryIndex].url}
alt={MOCK_IMAGE_LIST[nextGalleryIndex].alt}
/>
</StImagesWrapper>
{arrow('next')}

{/* μŠ¬λΌμ΄λ” ν•˜λ‹¨ 점박이듀 */}
<StCarouseDotWrapper>
{MOCK_IMAGE_LIST.map((image, idx) => (
<StCarouselDot
isactive={galleryIndex === idx}
key={image.alt}
onClick={() => setGalleryIndex(idx)}
/>
))}
</StCarouseDotWrapper>
</StGalleryWrapper>
);
}
Empty file removed src/pages/.keep
Empty file.
5 changes: 5 additions & 0 deletions src/pages/Detail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import GallerySlider from '../components/GallerySlider';

export default function Detail() {
return <GallerySlider />;
}
20 changes: 20 additions & 0 deletions src/pages/Main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Slider from '../components/Slider';

const dummyCardInfo = {
desc: 'μ™„λ²½ν•œ λ‰΄μš• νœ΄κ°€ κ³„νšν•˜κΈ°',
reviewCount: 139,
reviewRating: 4.99,
price: 11688,
nation: 'λ―Έκ΅­',
thumbnail:
'https://a0.muscache.com/im/pictures/lombard/MtTemplate-2496585-poster/original/e6de8fae-018d-4411-b0a3-81bbb6e4e5c3.jpeg',
};

export default function Main() {
return (
<Slider
label="μ „ 세계 ν˜„μ§€ 호슀트의 도움을 λ°›μ•„ 여행을 κ³„νšν•΄λ³΄μ„Έμš”"
cardList={Array(10).fill(dummyCardInfo)}
/>
);
}
14 changes: 14 additions & 0 deletions src/pages/Router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Detail from './Detail';
import Main from './Main';

export default function Router() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Main />} />
<Route path="/detail/:id" element={<Detail />} />
</Routes>
</BrowserRouter>
);
}