Skip to content

Commit 529bbbc

Browse files
committed
Merge branch 'develop' into feature/park
2 parents 3cafc3c + 088d2c9 commit 529bbbc

File tree

9 files changed

+422
-189
lines changed

9 files changed

+422
-189
lines changed

src/assets/images/upload.svg

Lines changed: 9 additions & 0 deletions
Loading
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { useState } from 'react';
2+
import styles from './BackgroundCard.module.scss';
3+
import uploadImage from '../../api/postUpload';
4+
import checked from '../../assets/images/checked.svg';
5+
import upload from '../../assets/images/upload.svg';
6+
7+
export default function BackgroundCard({
8+
type, // 'color' 또는 'image' 또는 'upload'
9+
color,
10+
url,
11+
isSelected,
12+
onClick,
13+
isLoading,
14+
onLoad,
15+
onSelect,
16+
}) {
17+
const [imageUrl, setImageUrl] = useState(url);
18+
19+
const getClassName = () => {
20+
if (type === 'color') {
21+
return `${styles[`background-card__color--${color}`]} ${isSelected ? styles['background-card__color--selected'] : ''}`;
22+
}
23+
if (type === 'image') {
24+
return `${styles[`background-card__image--${color}`]} ${isSelected ? styles['background-card__image--selected'] : ''}`;
25+
}
26+
if (type === 'upload') {
27+
return `${styles['background-card__upload']} ${imageUrl ? '' : styles['background-card__upload--no-image']} ${isSelected ? styles['background-card__image--selected'] : ''}`;
28+
}
29+
return '';
30+
};
31+
32+
const handleImageUpload = async (e) => {
33+
const file = e.target.files[0];
34+
if (!file) return;
35+
36+
try {
37+
const uploadedUrl = await uploadImage(file);
38+
setImageUrl(uploadedUrl);
39+
onSelect?.(uploadedUrl);
40+
} catch {
41+
alert('이미지 업로드 실패했습니다.');
42+
}
43+
};
44+
45+
return (
46+
<li className={getClassName()} onClick={onClick}>
47+
{type === 'image' && (
48+
<>
49+
{isLoading && <div className={styles['background-card__skeleton']} />}
50+
<img
51+
src={url}
52+
alt="배경이미지"
53+
className={`${
54+
styles['background-card__background-img']
55+
} ${isLoading ? styles['background-card__background-img--hidden'] : ''}`}
56+
onLoad={onLoad}
57+
/>
58+
</>
59+
)}
60+
{type === 'upload' && (
61+
<>
62+
{imageUrl ? (
63+
<>
64+
{isLoading && (
65+
<div className={styles['background-card__skeleton']} />
66+
)}
67+
<img
68+
src={imageUrl}
69+
alt="배경이미지"
70+
className={`${
71+
styles['background-card__background-img']
72+
} ${isLoading ? styles['background-card__background-img--hidden'] : ''}`}
73+
onLoad={onLoad}
74+
/>
75+
<button
76+
type="button"
77+
className={styles['background-card__remove-btn']}
78+
onClick={(e) => {
79+
e.stopPropagation();
80+
setImageUrl(null);
81+
onSelect?.(null);
82+
}}
83+
>
84+
85+
</button>
86+
</>
87+
) : (
88+
<>
89+
<input
90+
type="file"
91+
accept="image/*"
92+
onChange={handleImageUpload}
93+
style={{ display: 'none' }}
94+
id="upload-input"
95+
/>
96+
<label
97+
htmlFor="upload-input"
98+
className={styles['background-card__upload-btn']}
99+
>
100+
<img
101+
src={upload}
102+
alt="업로드된 이미지"
103+
className={styles['background-card__upload-img']}
104+
/>
105+
<span>이미지 업로드</span>
106+
</label>
107+
</>
108+
)}
109+
</>
110+
)}
111+
{isSelected && (
112+
<img
113+
src={checked}
114+
alt="선택됨"
115+
className={styles['background-card__check-icon']}
116+
/>
117+
)}
118+
</li>
119+
);
120+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
@use '../../assets/styles/variables.scss' as *;
2+
3+
.background-card__color--beige,
4+
.background-card__color--purple,
5+
.background-card__color--blue,
6+
.background-card__color--green {
7+
position: relative;
8+
width: 168px;
9+
aspect-ratio: 1 / 1;
10+
border: 1px solid rgba(0, 0, 0, 0.08);
11+
border-radius: 16px;
12+
list-style: none;
13+
flex-shrink: 0;
14+
cursor: pointer;
15+
transition:
16+
transform 0.3s ease-in-out,
17+
box-shadow 0.3s ease-in-out;
18+
19+
&:hover {
20+
transform: scale(1.05);
21+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
22+
}
23+
}
24+
.background-card__color--selected {
25+
cursor: default;
26+
}
27+
28+
.background-card__color--beige {
29+
background-color: $beige-200;
30+
}
31+
.background-card__color--purple {
32+
background-color: $purple-200;
33+
}
34+
.background-card__color--blue {
35+
background-color: $blue-200;
36+
}
37+
.background-card__color--green {
38+
background-color: $green-200;
39+
}
40+
41+
.background-card__check-icon {
42+
position: absolute;
43+
top: 50%;
44+
left: 50%;
45+
width: 44px;
46+
height: 44px;
47+
transform: translate(-50%, -50%);
48+
pointer-events: none;
49+
}
50+
51+
.background-card__image--1,
52+
.background-card__image--2,
53+
.background-card__image--3,
54+
.background-card__upload {
55+
position: relative;
56+
width: 168px;
57+
aspect-ratio: 1 / 1;
58+
border-radius: 16px;
59+
list-style: none;
60+
overflow: hidden;
61+
flex-shrink: 0;
62+
cursor: pointer;
63+
transition:
64+
transform 0.3s ease,
65+
box-shadow 0.3s ease;
66+
67+
&:hover {
68+
transform: scale(1.05) translateZ(0);
69+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
70+
}
71+
72+
.background-card__background-img {
73+
position: absolute;
74+
width: 100%;
75+
height: 100%;
76+
object-fit: cover;
77+
}
78+
}
79+
80+
.background-card__upload {
81+
display: flex;
82+
justify-content: center;
83+
align-items: center;
84+
}
85+
.background-card__upload-btn {
86+
display: flex;
87+
flex-direction: column;
88+
justify-content: center;
89+
align-items: center;
90+
position: absolute;
91+
width: 100%;
92+
height: 100%;
93+
background-color: $gray-200;
94+
cursor: pointer;
95+
gap: 4px;
96+
@include font-15-regular;
97+
color: $black;
98+
}
99+
100+
.background-card__upload-img {
101+
width: 40px;
102+
height: 40px;
103+
}
104+
105+
.background-card__remove-btn {
106+
position: absolute;
107+
top: 5px;
108+
right: 10px;
109+
padding: 0;
110+
background: inherit;
111+
border: none;
112+
color: black;
113+
@include font-24-bold;
114+
cursor: pointer;
115+
116+
transition:
117+
transform 0.2s ease,
118+
color 0.2s ease;
119+
120+
&:hover {
121+
transform: scale(1.2) translateZ(0);
122+
color: red;
123+
}
124+
}
125+
126+
.background-card__image--selected .background-card__background-img {
127+
opacity: 0.5;
128+
cursor: default;
129+
}
130+
131+
.background-card__check-icon {
132+
position: absolute;
133+
top: 50%;
134+
left: 50%;
135+
width: 44px;
136+
height: 44px;
137+
transform: translate(-50%, -50%);
138+
pointer-events: none;
139+
}
140+
141+
.background-card__upload--no-image {
142+
.background-card__check-icon {
143+
display: none;
144+
}
145+
}
146+
.background-card__skeleton {
147+
width: 100%;
148+
height: 100%;
149+
background-color: #e0e0e0;
150+
animation: skeleton-loading 1.2s infinite ease-in-out;
151+
border-radius: 8px;
152+
}
153+
154+
@keyframes skeleton-loading {
155+
0% {
156+
background-color: #e0e0e0;
157+
}
158+
50% {
159+
background-color: #f0f0f0;
160+
}
161+
100% {
162+
background-color: #e0e0e0;
163+
}
164+
}
165+
166+
.background-card__background-img--hidden {
167+
display: none;
168+
}
169+
170+
@media (max-width: 767px) {
171+
.background-card__color--beige,
172+
.background-card__color--purple,
173+
.background-card__color--blue,
174+
.background-card__color--green,
175+
.background-card__image--1,
176+
.background-card__image--2,
177+
.background-card__image--3,
178+
.background-card__upload {
179+
width: calc((100% - 12px) / 2);
180+
}
181+
}

src/components/Card/Card.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export default function Card({
4242

4343
return (
4444
<article
45-
className={`${styles.card} ${empty ? styles['card--empty'] : ''} ${showDelete ? styles['card--show'] : ''}`}
45+
className={`${styles.card} ${empty ? styles['card--empty'] : ''} ${showDelete ? styles['card--show'] : styles['card--unshow']}`}
4646
onClick={() => (empty ? clickPost() : onClick?.(id))}
4747
>
4848
{empty ? (

0 commit comments

Comments
 (0)