-
Notifications
You must be signed in to change notification settings - Fork 31
[이유진] sprint6 #137
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
The head ref may contain hidden characters: "React-\uC774\uC720\uC9C4-sprint6"
[이유진] sprint6 #137
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import React, { useRef, useState } from "react"; | ||
| import "./ImageUploader.scss"; | ||
| import plusIcon from "../../../images/ic_plus.png"; | ||
|
|
||
| function ImageUploader({ value, onChange }) { | ||
| const fileInputRef = useRef(null); | ||
| const [error, setError] = useState(""); | ||
|
|
||
| const handleFileChange = (e) => { | ||
| const file = e.target.files?.[0]; | ||
|
|
||
| if (value) { | ||
| setError("*이미지 등록은 최대 1개까지 가능합니다."); | ||
| if (fileInputRef.current) fileInputRef.current.value = ""; | ||
| return; | ||
| } | ||
|
|
||
| if (file) { | ||
| const imageUrl = URL.createObjectURL(file); | ||
| onChange({ file, preview: imageUrl }); | ||
| setError(""); | ||
| } | ||
| }; | ||
|
|
||
| const handleRemove = () => { | ||
| onChange(null); | ||
| setError(""); | ||
| if (fileInputRef.current) { | ||
| fileInputRef.current.value = ""; | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <div className={`image-uploader ${error ? "error" : ""}`}> | ||
| <label className="input-label">상품 이미지</label> | ||
| <div className="image-preview-container"> | ||
| <div className="upload-box"> | ||
| <span> | ||
| <img src={plusIcon} alt="" /> | ||
| </span> | ||
| <span>이미지 등록</span> | ||
| <input | ||
| type="file" | ||
| accept="image/" | ||
| ref={fileInputRef} | ||
| onChange={handleFileChange} | ||
| /> | ||
| </div> | ||
| {value && ( | ||
| <div className="upload-image"> | ||
| <img src={value.preview} alt="미리보기" /> | ||
| <button | ||
| type="button" | ||
| className="el-btn btn-remove" | ||
| aria-label="이미지 삭제" | ||
| onClick={handleRemove} | ||
| ></button> | ||
| </div> | ||
| )} | ||
| </div> | ||
| {error && <p className="error-msg">{error}</p>} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export default ImageUploader; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| @charset 'uft-8'; | ||
|
|
||
| .image-uploader { | ||
| .image-preview-container { | ||
| display: flex; | ||
| gap: 24px; | ||
|
|
||
| .upload-box { | ||
| position: relative; | ||
| display: flex; | ||
| flex-direction: column; | ||
| justify-content: center; | ||
| align-items: center; | ||
| width: 282px; | ||
| height: 282px; | ||
| background: var(--color-gray100); | ||
| border-radius: 12px; | ||
| color: var(--color-gray400); | ||
| font-size: var(--text-lg); | ||
|
|
||
| input { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hidden 속성을 쓰는 방법도 있습니다 :) <input hidden /> |
||
| position: absolute; | ||
| top: 0; | ||
| left: 0; | ||
| width: 100%; | ||
| height: 100%; | ||
| cursor: pointer; | ||
| opacity: 0; | ||
| } | ||
| } | ||
|
|
||
| .upload-image { | ||
| position: relative; | ||
| width: 282px; | ||
| height: 282px; | ||
|
|
||
| img { | ||
| width: 100%; | ||
| height: 100%; | ||
| object-fit: cover; | ||
| border-radius: 12px; | ||
| } | ||
|
|
||
| .el-btn.btn-remove { | ||
| position: absolute; | ||
| top: 12px; | ||
| right: 12px; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| .error-msg { | ||
| margin-top: 16px; | ||
| font-size: var(--text-lg); | ||
| color: var(--color-error); | ||
| } | ||
| } | ||
|
|
||
| @media (width < 1200px) { | ||
| .image-uploader { | ||
| .image-preview-container { | ||
| gap: 10px; | ||
|
|
||
| .upload-box { | ||
| width: 168px; | ||
| height: 168px; | ||
| } | ||
|
|
||
| .upload-image { | ||
| width: 168px; | ||
| height: 168px; | ||
| } | ||
| } | ||
| } | ||
| } | ||
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import React, { useState } from "react"; | ||
| import "./TagInput.scss"; | ||
|
|
||
| function TagInput({ tags, onChange }) { | ||
| const [inputValue, setInputValue] = useState(""); | ||
|
|
||
| const handleKeyDown = (e) => { | ||
| if ((e.key === "Enter" || e.key === ",") && inputValue.trim()) { | ||
| e.preventDefault(); | ||
| const newTag = inputValue.trim(); | ||
| if (!tags.includes(newTag)) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 중복 처리하실 때 Set을 서보셔도 좋아요 :) |
||
| onChange([...tags, newTag]); | ||
| } | ||
| setInputValue(""); | ||
| } | ||
| }; | ||
|
|
||
| const removeTag = (indexToRemove) => { | ||
| const updatedTag = tags.filter((_, index) => index !== indexToRemove); | ||
| onChange(updatedTag); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className="tag-input-container el-txt-input"> | ||
| <label className="input-label">태그</label> | ||
| <div className="tag-input-box"> | ||
| <input | ||
| type="text" | ||
| placeholder="태그를 입력해주세요" | ||
| value={inputValue} | ||
| onChange={(e) => setInputValue(e.target.value)} | ||
| onKeyDown={handleKeyDown} | ||
| /> | ||
| </div> | ||
| <div className="tag-list"> | ||
| {tags.map((tag, index) => ( | ||
| <div className="tag-item" key={index}> | ||
| {tag} | ||
| <button | ||
| className="el-btn btn-remove" | ||
| onClick={() => removeTag(index)} | ||
| ></button> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export default TagInput; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| @charset 'uft-8'; | ||
|
|
||
| .tag-input-container { | ||
| .tag-list { | ||
| margin-top: 14px; | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 12px; | ||
|
|
||
| .tag-item { | ||
| position: relative; | ||
| background: var(--color-gray100); | ||
| border-radius: 26px; | ||
| padding: 0 42px 0 16px; | ||
| font-size: var(--text-lg); | ||
| color: var(--color-gray800); | ||
| line-height: 36px; | ||
|
|
||
| &::before { | ||
| content: "#"; | ||
| } | ||
|
|
||
| .el-btn.btn-remove { | ||
| top: 50%; | ||
| right: 12px; | ||
| transform: translateY(-50%); | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
꼼꼼하게 잘 챙겨주셨네요! 👍