Skip to content

Commit 1ccc397

Browse files
authored
Merge pull request #282 from DguFarmSystem/develop
deploy: 프로젝트 단일 페이지 수정 + 소식 페이지 수정
2 parents 17b7f7e + 392bebe commit 1ccc397

File tree

13 files changed

+463
-9
lines changed

13 files changed

+463
-9
lines changed

apps/website/src/assets/home.png

31.2 KB
Loading
43.9 KB
Loading

apps/website/src/layouts/DetailLayout/DetailLayout.styled.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,11 @@ export const ImageContainer = styled.div<LayoutProps>`
124124
`;
125125

126126
export const Thumbnail = styled.img<LayoutProps>`
127-
width: 827.92px;
127+
width: 800px;
128+
border-radius: 4px;
128129
// height: 533px;
129130
flex-shrink: 0;
130-
aspect-ratio: 827.92/533.00;
131+
// aspect-ratio: 827.92/533.00;
131132
132133
border-top: 3px solid var(--FarmSystem_DarkGrey, #999);
133134
border-bottom: 1px solid var(--FarmSystem_DarkGrey, #999);
@@ -146,4 +147,26 @@ export const ContentBox = styled.p<LayoutProps>`
146147
font-weight: 400;
147148
line-height: 30px; /* 150% */
148149
letter-spacing: -0.24px;
149-
`;
150+
`;
151+
152+
////////////////////// 이미지 ////////////////////////
153+
export const ImageGallery = styled.div<{ $isMobile?: boolean; $isTablet?: boolean; $isDesktop?: boolean }>`
154+
width: 100%;
155+
max-width: 800px;
156+
display: grid;
157+
grid-template-columns: repeat(auto-fill, minmax(120px, 180px));
158+
gap: 10px;
159+
`;
160+
161+
export const Image = styled.img`
162+
width: 100%;
163+
aspect-ratio: 1 / 1;
164+
object-fit: cover;
165+
border-radius: 4px;
166+
cursor: pointer;
167+
transition: transform 0.2s;
168+
169+
&:hover {
170+
transform: scale(1.05);
171+
}
172+
`;

apps/website/src/layouts/DetailLayout/DetailLayout.tsx

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as S from "./DetailLayout.styled";
2+
import { useState } from "react";
23
import GoBackArrow from "@/assets/LeftArrow.png";
34
import useMediaQueries from "@/hooks/useMediaQueries";
5+
import ImageModal from './ImageModal';
46

57
interface DetailLayoutProps {
68
title?: string;
@@ -17,9 +19,10 @@ export default function DetailLayout({
1719
date = "(임시) 게시일자: 2025년 03월 13일",
1820
tag = "(임시) 태그",
1921
thumbnailUrl = "",
20-
// imageUrls = [],
22+
imageUrls = [],
2123
}: DetailLayoutProps) {
2224
const { isMobile, isTablet, isDesktop } = useMediaQueries();
25+
const [selectedImage, setSelectedImage] = useState<string | null>(null);
2326

2427
return (
2528
<S.DetailCard $isMobile={isMobile} $isTablet={isTablet} $isDesktop={isDesktop}>
@@ -56,6 +59,26 @@ export default function DetailLayout({
5659
alt={title}
5760
/>
5861
</S.ImageContainer>
62+
<S.ImageContainer $isMobile={isMobile} $isTablet={isTablet} $isDesktop={isDesktop}>
63+
{imageUrls && imageUrls.length > 0 && (
64+
<S.ImageGallery $isMobile={isMobile} $isTablet={isTablet} $isDesktop={isDesktop}>
65+
{imageUrls.map((url, index) => (
66+
<S.Image
67+
key={index}
68+
src={url}
69+
alt={`Image ${index + 1}`}
70+
onClick={() => setSelectedImage(url)}
71+
/>
72+
))}
73+
</S.ImageGallery>
74+
)}
75+
</S.ImageContainer>
76+
{selectedImage && (
77+
<ImageModal
78+
imageUrl={selectedImage}
79+
onClose={() => setSelectedImage(null)}
80+
/>
81+
)}
5982
<S.ContentBox $isMobile={isMobile} $isTablet={isTablet} $isDesktop={isDesktop}>
6083
{content}
6184
</S.ContentBox>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import styled from "styled-components";
2+
3+
export const ModalOverlay = styled.div`
4+
position: fixed;
5+
inset: 0;
6+
background: rgba(0, 0, 0, 0.7);
7+
z-index: 999;
8+
display: flex;
9+
justify-content: center;
10+
align-items: center;
11+
`;
12+
13+
export const ModalImage = styled.img`
14+
max-width: 90vw;
15+
max-height: 90vh;
16+
border-radius: 0px;
17+
object-fit: contain;
18+
`;
19+
20+
export const ModalContent = styled.div`
21+
position: relative;
22+
z-index: 1000;
23+
max-width: 90vw;
24+
max-height: 90vh;
25+
`;
26+
27+
export const ModalCloseArea = styled.div`
28+
position: fixed;
29+
inset: 0;
30+
cursor: zoom-out;
31+
`;
32+
33+
34+
export const CloseButton = styled.button`
35+
position: absolute;
36+
top: -20px;
37+
right: -20px;
38+
background: #fff;
39+
border: none;
40+
color: #000;
41+
font-size: 28px;
42+
font-weight: bold;
43+
border-radius: 50%;
44+
width: 40px;
45+
height: 40px;
46+
cursor: pointer;
47+
z-index: 1001;
48+
box-shadow: 0 0 8px rgba(0, 0, 0, 0.5);
49+
50+
&:hover {
51+
background: #eee;
52+
}
53+
`;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { useEffect, useRef } from 'react';
2+
import ReactDOM from 'react-dom';
3+
import * as S from './ImageModal.styled';
4+
5+
interface ImageModalProps {
6+
imageUrl: string;
7+
onClose: () => void;
8+
}
9+
10+
export default function ImageModal({ imageUrl, onClose }: ImageModalProps) {
11+
const modalRef = useRef<HTMLDivElement>(null);
12+
const closeButtonRef = useRef<HTMLButtonElement>(null);
13+
14+
useEffect(() => {
15+
const handleKeyDown = (e: KeyboardEvent) => {
16+
if (e.key === 'Escape') onClose();
17+
if (e.key === 'Tab') {
18+
e.preventDefault();
19+
closeButtonRef.current?.focus();
20+
}
21+
};
22+
23+
document.addEventListener('keydown', handleKeyDown);
24+
return () => document.removeEventListener('keydown', handleKeyDown);
25+
}, [onClose]);
26+
27+
useEffect(() => {
28+
const originalStyle = document.body.style.overflow;
29+
document.body.style.overflow = 'hidden';
30+
return () => {
31+
document.body.style.overflow = originalStyle;
32+
};
33+
}, []);
34+
35+
if (typeof window === 'undefined') return null;
36+
37+
return ReactDOM.createPortal(
38+
<S.ModalOverlay onClick={onClose} ref={modalRef}>
39+
<S.ModalCloseArea />
40+
<S.ModalContent onClick={(e) => e.stopPropagation()}>
41+
<S.CloseButton
42+
ref={closeButtonRef}
43+
type="button"
44+
onClick={onClose}
45+
aria-label="Close modal">
46+
&times;
47+
</S.CloseButton>
48+
<S.ModalImage src={imageUrl} alt="Modal image" />
49+
</S.ModalContent>
50+
</S.ModalOverlay>,
51+
document.body
52+
);
53+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import styled from "styled-components";
2+
3+
interface LayoutProps {
4+
$isMobile?: boolean;
5+
$isTablet?: boolean;
6+
$isDesktop?: boolean;
7+
}
8+
9+
export const DetailCard = styled.div<LayoutProps>`
10+
display: flex;
11+
width: 100%;
12+
max-width: 1000px;
13+
padding: ${({ $isMobile }) => ($isMobile ? "20px": "50px")};
14+
flex-direction: column;
15+
justify-content: flex-end;
16+
align-items: center;
17+
18+
border-radius: 20px;
19+
background: var(--FarmSystem_White, #FCFCFC);
20+
box-shadow: 0px 0px 20px 5px var(--FarmSystem_LightGrey, #E5E5E5);
21+
gap: ${({ $isMobile }) => ($isMobile ? "20px": "70px")};
22+
`;
23+
24+
export const GoBackContainer = styled.div<LayoutProps>`
25+
display: flex;
26+
align-items: left;
27+
align-self: stretch;
28+
`;
29+
30+
export const GoBackButton = styled.button<LayoutProps>`
31+
display: flex;
32+
align-items: center;
33+
align-self: stretch;
34+
cursor: pointer;
35+
36+
color: var(--FarmSystem_Green01, #28723F);
37+
font-size: ${({ $isMobile }) => ($isMobile ? "16px": "24px")};
38+
39+
font-style: normal;
40+
font-weight: 500;
41+
line-height: 40px; /* 166.667% */
42+
letter-spacing: -0.24px;
43+
`;
44+
45+
export const GoBackImg = styled.img<LayoutProps>`
46+
width: ${({ $isMobile }) => ($isMobile ? "20px": "30px")};;
47+
height: ${({ $isMobile }) => ($isMobile ? "24px": "40px")};
48+
`;
49+
50+
export const TitleContainer = styled.div<LayoutProps>`
51+
display: flex;
52+
max-width: 800px;
53+
width: 100%;
54+
flex-direction: column;
55+
align-items: flex-start;
56+
gap: ${({ $isMobile }) => ($isMobile ? "15px": "30px")};
57+
`;
58+
59+
export const ParticipantsAndTagContainer = styled.div<LayoutProps>`
60+
display: flex;
61+
padding: 10px 0px;
62+
flex-direction: row;
63+
justify-content: space-between;
64+
align-items: center;
65+
flex: 1 0 0;
66+
align-self: stretch;
67+
width: 100%;
68+
`;
69+
70+
export const Link = styled.a<LayoutProps>`
71+
display: flex;
72+
color: var(--FarmSystem_Black, #191919);
73+
font-size: ${({ $isMobile }) => ($isMobile ? "12px": "16px")};
74+
font-style: normal;
75+
font-weight: 400;
76+
line-height: 30px; /* 150% */
77+
letter-spacing: -0.24px;
78+
79+
`;
80+
81+
export const Tag = styled.p<LayoutProps>`
82+
display: flex;
83+
height: ${({ $isMobile }) => ($isMobile ? "32px": "40px")};
84+
padding: 5px 20px;
85+
justify-content: center;
86+
align-items: center;
87+
88+
border-radius: 15px;
89+
background: var(--FarmSystem_Green06, #006811);
90+
91+
color: var(--FarmSystem_White, #FCFCFC);
92+
text-align: center;
93+
font-size: ${({ $isMobile }) => ($isMobile ? "16px": "20px")};
94+
font-style: normal;
95+
font-weight: 400;
96+
line-height: 20px; /* 100% */
97+
letter-spacing: -0.24px;
98+
`;
99+
100+
export const Participant = styled.p<LayoutProps>`
101+
display: flex;
102+
flex-direction: row;
103+
gap: 10px;
104+
color: var(--FarmSystem_Black, #191919);
105+
106+
107+
font-size: ${({ $isMobile }) => ($isMobile ? "16px": "20px")};
108+
font-style: normal;
109+
font-weight: 400;
110+
line-height: 30px; /* 150% */
111+
letter-spacing: -0.24px;
112+
`;
113+
114+
export const Title = styled.h2<LayoutProps>`
115+
display: flex;
116+
padding: 10px 0px;
117+
justify-content: start;
118+
align-items: center;
119+
gap: 10px;
120+
align-self: stretch;
121+
122+
color: var(--FarmSystem_Black, #191919);
123+
font-family: "Pretendard Variable";
124+
font-size: ${({ $isMobile }) => ($isMobile ? "24px": "32px")};
125+
font-style: normal;
126+
font-weight: 700;
127+
line-height: 40px; /* 125% */
128+
letter-spacing: -0.24px;
129+
130+
width: 100%;
131+
max-width: 800px;
132+
`;
133+
134+
export const ImageContainer = styled.div<LayoutProps>`
135+
display: flex;
136+
width: 100%;
137+
align-items: center;
138+
justify-content: center;
139+
`;
140+
141+
export const Thumbnail = styled.img<LayoutProps>`
142+
width: 827.92px;
143+
// height: 533px;
144+
flex-shrink: 0;
145+
aspect-ratio: 827.92/533.00;
146+
147+
border-top: 3px solid var(--FarmSystem_DarkGrey, #999);
148+
border-bottom: 1px solid var(--FarmSystem_DarkGrey, #999);
149+
background: url(<path-to-image>) lightgray 50% / cover no-repeat;
150+
`;
151+
152+
export const ContentBox = styled.p<LayoutProps>`
153+
width: 100%;
154+
max-width: 800px;
155+
white-space: pre-wrap;
156+
157+
color: var(--FarmSystem_Black, #191919);
158+
font-family: "Pretendard Variable";
159+
font-size: ${({ $isMobile }) => ($isMobile ? "18px": "20px")};
160+
font-style: normal;
161+
font-weight: 400;
162+
line-height: 30px; /* 150% */
163+
letter-spacing: -0.24px;
164+
`;
165+
166+
export const LinkContainer = styled.div<LayoutProps>`
167+
display: flex;
168+
flex-direction: row;
169+
gap: 10px;
170+
justify-content: flex-start;
171+
max-width: 800px;
172+
width: 100%;
173+
`;
174+
175+
export const LinkIcon = styled.img<LayoutProps>`
176+
width: ${({ $isMobile }) => ($isMobile ? "30px": "50px")};
177+
height: ${({ $isMobile }) => ($isMobile ? "30px": "50px")};
178+
transition: transform 0.2s ease-in-out;
179+
cursor: pointer;
180+
object-fit: cover;
181+
&:hover {
182+
transform: scale(1.1);
183+
}
184+
`;

0 commit comments

Comments
 (0)