Repository navigation
(i25 152) feat/inputs of modal PR - #55
hanjiwon1108 wants to merge 57 commits into
Conversation
… into (I25-152)-feat/InputsOfModal
|
🚀 배포 준비중 예상포트 : |
There was a problem hiding this comment.
Pull Request Overview
This PR implements inputs and modal components for the BSMHub v2 project, adding form elements and interface components for user interactions.
- Adds various input types with different states (text, date, lock, edit, search)
- Implements labeled inputs with required field support
- Creates button components and picture upload functionality with preview
Reviewed Changes
Copilot reviewed 103 out of 138 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/app/components/inputsOfModal/ | Core modal input components including buttons, inputs, labels, and picture upload |
| src/app/components/system/text.ts | Typography system using styled-components |
| src/app/components/layout/Header.tsx | Simplified header component with navigation |
| tailwind.config.ts | Updated color scheme and utility classes |
| package.json | Added dependencies for styled-components and icons |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| name={name} | ||
| required={required} | ||
| readOnly={type === 'lock'} | ||
| className={`flex w-full h-[3.3125rem] py-1 ${ |
There was a problem hiding this comment.
This complex template literal with conditional classes should be refactored using a utility function or clsx library for better readability and maintainability.
| ]; | ||
|
|
||
| return ( | ||
| <header className="flex-center p-white-space-margin h-9 border-b border-[light-gray-outline]"> |
There was a problem hiding this comment.
The Tailwind class 'border-[light-gray-outline]' should use the proper color syntax 'border-light-gray-outline' to reference the custom color defined in the config.
| <header className="flex-center p-white-space-margin h-9 border-b border-[light-gray-outline]"> | |
| <header className="flex-center p-white-space-margin h-9 border-b border-light-gray-outline"> |
|
🚀 배포 준비중 예상포트 : |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: LEE JEONGHYEOK <157395300+GAMZAMANDU@users.noreply.github.com>
|
🚀 배포 준비중 예상포트 : |
| import React from 'react'; | ||
|
|
||
| interface ButtonsProps { | ||
| color?: 'black' | 'blue' | 'red' | 'green' | 'gray'; |
| black: 'bg-black', | ||
| blue: 'bg-blue-500', | ||
| red: 'bg-red-500', | ||
| green: 'bg-green-500', | ||
| gray: 'bg-gray-500', |
There was a problem hiding this comment.
색깔들은 웬만하면 tailwind.config.ts에 정의된 클래스를 사용해주세요.
| <button | ||
| className={`flex w-full h-[3.25rem] py-2 px-[1.375rem] justify-center items-center gap-1 shrink-0 rounded-full ${bgClass} text-white text-base font-bold leading-5`} | ||
| onClick={onClick} | ||
| > |
There was a problem hiding this comment.
flex, justify-center, items-center를 모두 합쳐둔 flex-center가 정의되어 있으니 확인 부탁드립니다..
또한 모든 글씨체는 tailwind.config.ts에 정의된 것을 사용해주세요.
| placeholder = 'Placeholder', | ||
| value, | ||
| onChange, | ||
| name, |
There was a problem hiding this comment.
name보단 label이 더 좋을 것 같습니다.
| ['lock', 'edit', 'search', 'date'].includes(type) ? 'pr-10' : 'px-2.5' | ||
| } pl-2.5 items-center gap-2.5 shrink-0 rounded-md text-gray-base text-base font-normal leading-6 tracking-[0.0057rem] ${ | ||
| type !== 'lock' ? 'bg-light-gray-outline' : '' |
| return ( | ||
| <div className="inline-block h-[5.875rem]"> | ||
| <div | ||
| className="h-full aspect-1 flex flex-col items-center justify-center rounded-md bg-light-gray-outline cursor-pointer overflow-hidden relative" |
There was a problem hiding this comment.
둥근 회색깔 사각형이 자주 사용되고 있는데 이러한 부분을 레이아웃화 하셔야합니다.
| import { ChangeEvent } from 'react'; | ||
|
|
||
| export type InputType = 'text' | 'lock' | 'date' | 'edit' | 'search'; | ||
|
|
||
| export interface BaseInputProps { | ||
| type?: InputType; | ||
| placeholder?: string; | ||
| value?: string | number; | ||
| onChange?: (e: ChangeEvent<HTMLInputElement>) => void; | ||
| name?: string; | ||
| required?: boolean; | ||
| id?: string; | ||
| } | ||
|
|
||
| export interface LabelInputsProps extends BaseInputProps { | ||
| label?: string; | ||
| } |
- react-hook-form v7.64.0 추가 - 폼 상태 관리 및 검증을 위한 의존성
- MultiInput 컴포넌트 사용으로 여러 필드 동시 관리 - onlyOne 옵션으로 단일/다중 입력 제어 - InputConfig 타입에 onlyOne 속성 추가 - 추가 버튼에 type='button' 추가하여 폼 제출 방지 - onInputsChange 콜백으로 외부 상태 관리 지원 - config 기반 동적 입력 생성
- FormFieldConfig: label과 InputListProvider 쌍 설정 - FormConfig: 전체 폼 설정 타입 - InputConfig import 추가 - React Hook Form 통합을 위한 타입 구조
- React Hook Form 통합으로 폼 상태 관리 - config 기반 동적 폼 필드 생성 - label과 InputListProvider 자동 페어링 - 필수 필드 자동 검증 및 에러 메시지 표시 - title prop으로 폼 제목 커스터마이징 - Buttons 컴포넌트 통합 제출 버튼
- InputListProvider.md: 컴포넌트 사용법 및 API 문서 - InputListProvider-with-ReactHookForm.md: React Hook Form 통합 가이드 - InputOfModal.md: config 기반 폼 생성 컴포넌트 문서 - 각 문서에 사용 예제, 타입 정의, 트러블슈팅 포함 - onlyOne prop 업데이트 반영
- PictureUpload 코드 간소화 (59줄 → 36줄) - aspectRatio prop 추가로 다양한 비율 지원 (1:1, 3:4, 16:9 등) - InputOfModal에서 picture 타입으로 PictureUpload 사용 가능 - 타입 안전성 개선: aspectRatio는 picture 타입 전용으로 제한
- ModalContext: 전역 모달 상태 관리 (Context API + useCallback) - Modal 컴포넌트: UI 렌더링 및 배경 클릭으로 닫기 - 모달 스타일: 부드러운 애니메이션 및 backdrop blur 효과 - body 스크롤 방지 기능 - inputsOfModal 컴포넌트들을 modal 폴더로 이동 - 타입 시스템 개선 (extends 방식으로 통일) - 사용 가이드 문서 작성
- inputsOfModal 폴더 삭제 (modal/inputsOfModal로 이동 완료) - @utils alias 추가 (tsconfig.json) - import 경로를 @components, @utils alias로 통일 - useInputList의 MultiInputItem 타입을 MultiInput.tsx에서 import - layout.tsx와 page.tsx의 import 경로 수정
- write/edit/read 3가지 모드로 구분된 SkillTag 컴포넌트 추가 - 여러 스킬 태그를 관리하는 SkillTagProvider 구현 - react-input-autosize를 사용한 자동 너비 조절 인풋 - 한글 IME 입력 처리 추가 - 태그 생성/수정/삭제 기능 및 상태 관리 - 인풋 포커스 제거를 위한 CSS 유틸리티 추가
- 체크박스 인풋 컴포넌트 추가 - 모달 인풋 시스템에 체크박스 타입 지원
- inputsOfModal 폴더를 inputs로 리네이밍 - FormFieldConfig 타입을 discriminated union으로 개선 - SkillTag와 Checkbox를 InputOfModal에 통합 - Config 구조 단순화 (타입별 필요한 필드만 정의) - import 경로 정리 및 통일 - common.css 중복 제거
- InputType에서 'lock' 타입 제거 - BaseInputPropsCommon에 readOnly prop 추가 - InputListProvider에서 isLocked → isReadOnly로 변경 - SingleInput에서 type 기반 readonly → readOnly prop으로 변경 - 일관된 readOnly 패턴으로 API 단순화
- 초기 태그 배열을 설정할 수 있는 initialTags prop 추가 - useEffect로 초기 태그 자동 설정 로직 구현 - 읽기 전용 모드에서 초기값 표시 가능
- inputsOfModal → modal/inputs로 폴더명 변경 - layout.tsx, page.tsx의 import 경로 업데이트 - 테스트 코드에 SkillTag 및 Checkbox 예제 추가
- import 경로 수정 (inputsOfModal → modal/inputs) - 입력 잠금 → 입력 읽기 전용으로 용어 변경 - InputType에서 'lock' 제거, readOnly prop 추가 - readOnly prop 사용법 설명 추가 - React Hook Form 통합 가이드 경로 업데이트
- Discriminated Union 타입 구조 설명 추가 - InputListFieldConfig - SkillTagFieldConfig - CheckboxFieldConfig - 필드 타입별 상세 설명 섹션 추가 - 각 필드 타입의 데이터 형식 명시 - 실전 예제 (프로필 작성 폼) 추가 - SkillTag, Checkbox 렌더링 로직 설명 - import 경로 업데이트
- SkillTag 컴포넌트 전용 상세 문서 작성 - write/edit/read 모드 설명 - Discriminated Union Props 구조 - 한글 IME Composition 처리 원리 - 자동 너비 조정 (react-input-autosize) - 이벤트 버블링 방지 로직 - SkillTagProvider 사용법 - InputOfModal 통합 예제 - 실전 사용 사례 및 트러블슈팅 - 성능 최적화 팁
문제:
- defaultValues에서 모든 필드를 빈 배열([])로 초기화
- JS에서 []는 truthy 값이므로 checked={git diff src/app/components/modal/inputs/InputOfModal.tsxvalue}가 true가 됨
해결:
- Checkbox 타입은 false로, 나머지는 []로 초기화
- 삼항 연산자로 간결하게 처리
- validation 로직 간소화 (배열 타입만 검증)
- InputMode (write/edit/read) 도입으로 상태와 타입 분리 - Picture를 독립적인 필드 타입으로 추가 - Checkbox 비제어/제어 컴포넌트 모두 지원 - SkillTag autoFocus 제거로 포커스 관리 개선 - SingleInput에 icon prop 추가 (check/search/calendar)
- AutosizeInput 컴포넌트에 autoFocus prop 추가 - 태그 작성 시 자동으로 포커스 설정
- Checkbox.md 문서 생성 - Controlled/Uncontrolled 모드 설명 - React Hook Form 통합 예제 - InputOfModal에서 사용법 - 검증 로직 설명 - InputOfModal에 Checkbox required 검증 추가 - PictureUpload value 타입 any로 변경하여 호환성 개선
- onTagsChange 콜백을 useRef로 관리하여 의존성 무한 루프 방지 - InputOfModal의 Checkbox onChange 단순화 - PictureUpload value 타입 체크 명확화 - useEffect 의존성 배열 최적화
|
🚀 배포 준비중 예상포트 : |
| > | ||
| {preview ? ( | ||
| // eslint-disable-next-line @next/next/no-img-element | ||
| <img src={preview} alt="업로드된 이미지" className="object-cover w-full h-full" /> |
Check warning
Code scanning / CodeQL
DOM text reinterpreted as HTML Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 1 year ago
To fix the problem, we must ensure that only safe and intended values are assigned to the src attribute of the <img> element. Specifically, when the value prop is a string (possibly attacker-supplied), we should validate that it is a safe image URL before setting it as preview. The best approach is to only allow URLs that either start with blob:, are from trusted domains, or are recognized safe protocols (e.g., https://, data:image/, etc.), and explicitly reject javascript:, data:text/html;, or other dangerous schemes.
Required changes:
- Implement a function (e.g.,
isSafeImageSrc) that validates the string is a Blob URL or trusted image resource (starts withhttp://,https://, ordata:image/). - In both the
useEffectwherepreviewis set from a stringvalue, and the rendering wherepreviewis used asimg.src, use this validator to block unsafe URLs. - Optionally, fallback to not showing a preview if validation fails.
Add the function to the same file, and update the logic in the relevant lines.
| @@ -8,6 +8,16 @@ | ||
| onFileChange?: (file: File | null) => void; // 파일 변경 콜백 | ||
| } | ||
|
|
||
| function isSafeImageSrc(src: string): boolean { | ||
| // allow blob:, http(s), and data:image/* only | ||
| return ( | ||
| src.startsWith('blob:') || | ||
| src.startsWith('https://') || | ||
| src.startsWith('http://') || | ||
| src.startsWith('data:image/') | ||
| ); | ||
| } | ||
|
|
||
| function PictureUpload({ aspectRatio = '1:1', value, onFileChange }: PictureUploadProps) { | ||
| const [preview, setPreview] = useState<string>(); | ||
| const inputRef = useRef<HTMLInputElement>(null); | ||
| @@ -15,7 +25,11 @@ | ||
| // value prop이 string URL일 때만 preview 업데이트 (초기 로드용) | ||
| useEffect(() => { | ||
| if (typeof value === 'string') { | ||
| setPreview(value); | ||
| if (isSafeImageSrc(value)) { | ||
| setPreview(value); | ||
| } else { | ||
| setPreview(undefined); | ||
| } | ||
| } | ||
| }, [value]); | ||
|
|
||
| @@ -43,7 +57,7 @@ | ||
| className="overflow-hidden input-common flex-col !justify-center cursor-pointer" | ||
| style={{ height: '5.875rem', width: getWidth(aspectRatio) }} | ||
| > | ||
| {preview ? ( | ||
| {(preview && isSafeImageSrc(preview)) ? ( | ||
| // eslint-disable-next-line @next/next/no-img-element | ||
| <img src={preview} alt="업로드된 이미지" className="object-cover w-full h-full" /> | ||
| ) : ( |

💡 개요
bsmhub v2/ Inputs of Modal components 구현
📃 작업내용
추가하기 버튼,
일반 버튼(색깔과 text props),
다양한 형태의 Input type(text, date, lock, edit, search),
라벨있는 inpput(required 유무),
사진 업로드(미리보기)
🔀 변경사항
📸 스크린샷