Skip to content

Commit

Permalink
Refactor: 커피챗 오픈/수정 페이지 변경사항 적용 (#1702)
Browse files Browse the repository at this point in the history
* feat: 경력 선택 항목  UI 변경
- chip -> dropwdown

* feat: MO에서 사용 가능한 BottomSheetSelect 컴포넌트 추가

* feat: 경력 선택 항목 UI 변경
- chip -> PC: dropdown, MO: bottomsheet 으로 변경

* feat: 커피챗 진행 방식 UI 변경
- MO에서 dropdown -> bottomsheet 으로 변경

* fix: 자기소개 항목) 필수 -> 선택항목으로 변경

* refactor: MO: 관련 분야, 커피챗 주제 및 소개 섹션 chip md->sm으로 사이즈 변경

* chore: 커피챗 주제 및 소개 항목) placeholder 변경

* chore: 경력 선택 항목 dropdown 사이즈 변경
  • Loading branch information
pepperdad authored Dec 17, 2024
1 parent a7aae8e commit abfd07b
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 50 deletions.
2 changes: 1 addition & 1 deletion src/components/coffeechat/page/CoffeechatUploadPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default function CoffeechatUploadPage({ uploadType, form, onSubmit }: Cof
aside={
<ProgressBox
uploadType={uploadType}
myInfoInprogress={!!(watch('memberInfo.career') && watch('memberInfo.introduction'))}
myInfoInprogress={!!watch('memberInfo.career')}
coffeechatInfoInprogress={
!!(
watch('coffeeChatInfo.sections') &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import styled from '@emotion/styled';
import { colors } from '@sopt-makers/colors';
import { fonts } from '@sopt-makers/fonts';
import { IconCheck, IconChevronDown } from '@sopt-makers/icons';
import { Button } from '@sopt-makers/ui';
import { useEffect, useState } from 'react';

import { zIndex } from '@/styles/zIndex';

interface Option {
label: string;
value: string;
}

interface BottomSheetSelectProps {
options: Option[];
value: string | string[] | null | undefined;
placeholder: string;
onChange: (value: string) => void;
}
const BottomSheetSelect = ({ options, value, placeholder, onChange }: BottomSheetSelectProps) => {
const [open, setOpen] = useState(false);
const [selectedValue, setSelectedValue] = useState(value);
const [temporaryValue, setTemporaryValue] = useState(value);

const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);

const handleOptionSelect = (value: string) => {
setTemporaryValue(value);
};

const handleConfirm = () => {
setSelectedValue(temporaryValue);
if (temporaryValue !== '') onChange(temporaryValue as string);

handleClose();
};

useEffect(() => {
if (open) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}

return () => {
document.body.style.overflow = '';
};
}, [open]);

return (
<Container>
<InputField onClick={handleOpen}>
{selectedValue !== null ? <p>{selectedValue}</p> : <p style={{ color: '#808087' }}>{placeholder}</p>}
<IconChevronDown
style={{
width: 20,
height: 20,
transform: open ? 'rotate(-180deg)' : '',
transition: 'all 0.5s',
}}
/>
</InputField>

{open && (
<>
<Overlay onClick={handleClose} />
<BottomSheet>
<OptionList>
{options.map((option) => (
<OptionItem key={option.value} onClick={() => handleOptionSelect(option.value)}>
{option.label}
{temporaryValue === option.value && <CheckedIcon />}
</OptionItem>
))}
</OptionList>
<Button size='lg' style={{ width: '100%' }} onClick={handleConfirm}>
확인
</Button>
</BottomSheet>
</>
)}
</Container>
);
};
export default BottomSheetSelect;

const Container = styled.div`
position: relative;
width: 100%;
`;

const InputField = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
border-radius: 10px;
background-color: ${colors.gray800};
cursor: pointer;
padding: 11px 16px;
${fonts.BODY_16_M};
`;

const Overlay = styled.div`
position: fixed;
top: 0;
left: 0;
z-index: ${zIndex.헤더};
background-color: rgb(15 15 18 / 80%);
width: 100%;
height: 100%;
`;

const BottomSheet = styled.section`
position: fixed;
bottom: 0;
z-index: ${zIndex.헤더};
margin-bottom: 12px;
border-radius: 16px;
background-color: ${colors.gray800};
padding: 16px;
width: calc(100% - 40px);
`;

const OptionList = styled.ul`
margin: 0 0 16px;
padding: 0;
max-height: 300px;
overflow-y: auto;
list-style: none;
`;

const OptionItem = styled.li`
display: flex;
align-items: center;
justify-content: space-between;
border-radius: 4px;
cursor: pointer;
padding: 10px;
height: 44px;
${fonts.BODY_14_M}
`;

const CheckedIcon = styled(IconCheck)`
width: 24px;
height: 24px;
color: ${colors.success};
`;
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default function ChipField({ field, errorMessage, chipList, isSingleSelec
</Chip>
</Responsive>
<Responsive only='mobile'>
<Chip size='md' active={isActive(field, chip)}>
<Chip size='sm' active={isActive(field, chip)}>
{chip}
</Chip>
</Responsive>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SelectV2, TextArea } from '@sopt-makers/ui';
import { Controller, useFormContext } from 'react-hook-form';

import { COFFEECHAT_MOBILE_MEDIA_QUERY } from '@/components/coffeechat/mediaQuery';
import BottomSheetSelect from '@/components/coffeechat/upload/CoffeechatForm/BottomSheetSelect';
import ChipField from '@/components/coffeechat/upload/CoffeechatForm/ChipField';
import {
COFFECHAT_SECTION,
Expand Down Expand Up @@ -83,12 +84,7 @@ export default function CoffeechatInfoForm() {
value={field.value ?? ''}
maxLength={1000}
fixedHeight={189}
lineBreakPlaceholder={[
'• PM과 서비스기획자로 일하는 방법',
'• 포트폴리오 준비 및 작성 노하우',
'• 직무 전환 시 준비할 것들',
'• 당근, 토스, 넥슨, 하나은행, LG전자 면접 후기',
]}
lineBreakPlaceholder={['• PM과 서비스 기획자로 일하는 방법', '• 앱잼 전 미리 준비하면 좋은 것']}
isError={!!errors.coffeeChatInfo?.topic}
errorMessage={errors.coffeeChatInfo?.topic?.message}
onChange={(e) => field.onChange(e.target.value)}
Expand All @@ -100,12 +96,7 @@ export default function CoffeechatInfoForm() {
value={field.value ?? ''}
maxLength={1000}
fixedHeight={176}
lineBreakPlaceholder={[
'• PM과 서비스기획자로 일하는 방법',
'• 포트폴리오 준비 및 작성 노하우',
'• 직무 전환 시 준비할 것들',
'• 당근, 토스, 넥슨, 하나은행, LG전자 면접 후기',
]}
lineBreakPlaceholder={['• PM과 서비스 기획자로 일하는 방법', '• 앱잼 전 미리 준비하면 좋은 것']}
isError={!!errors.coffeeChatInfo?.topic}
errorMessage={errors.coffeeChatInfo?.topic?.message}
onChange={(e) => field.onChange(e.target.value)}
Expand All @@ -124,23 +115,33 @@ export default function CoffeechatInfoForm() {
name='coffeeChatInfo.meetingType'
control={control}
render={({ field }) => (
<div {...field}>
<SelectV2.Root
type='text'
visibleOptions={3}
defaultValue={MEETING_TYPE_OPTIONS.find((option) => option.value === field.value)}
onChange={(value) => field.onChange(value)}
>
<SelectV2.Trigger>
<SelectV2.TriggerContent placeholder={'진행 방식 선택'} />
</SelectV2.Trigger>
<SelectV2.Menu>
{MEETING_TYPE_OPTIONS.map((option) => (
<SelectV2.MenuItem key={option.value} option={option} />
))}
</SelectV2.Menu>
</SelectV2.Root>
</div>
<>
<Responsive only='desktop' {...field}>
<SelectV2.Root
type='text'
visibleOptions={3}
defaultValue={MEETING_TYPE_OPTIONS.find((option) => option.value === field.value)}
onChange={(value) => field.onChange(value)}
>
<SelectV2.Trigger>
<SelectV2.TriggerContent placeholder={'진행 방식 선택'} />
</SelectV2.Trigger>
<SelectV2.Menu>
{MEETING_TYPE_OPTIONS.map((option) => (
<SelectV2.MenuItem key={option.value} option={option} />
))}
</SelectV2.Menu>
</SelectV2.Root>
</Responsive>
<Responsive only='mobile' {...field}>
<BottomSheetSelect
options={[...MEETING_TYPE_OPTIONS]}
value={field.value}
placeholder='진행 방식 선택'
onChange={(value) => field.onChange(value)}
/>
</Responsive>
</>
)}
/>
</FormItem>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import styled from '@emotion/styled';
import { SelectV2 } from '@sopt-makers/ui';
import { Controller, useFormContext } from 'react-hook-form';

import { COFFEECHAT_MOBILE_MEDIA_QUERY } from '@/components/coffeechat/mediaQuery';
import ChipField from '@/components/coffeechat/upload/CoffeechatForm/ChipField';
import { CAREER_LEVEL } from '@/components/coffeechat/upload/CoffeechatForm/constants';
import BottomSheetSelect from '@/components/coffeechat/upload/CoffeechatForm/BottomSheetSelect';
import { CAREER_LEVEL_OPTIONS } from '@/components/coffeechat/upload/CoffeechatForm/constants';
import { CoffeechatFormContent } from '@/components/coffeechat/upload/CoffeechatForm/types';
import FormItem from '@/components/common/form/FormItem';
import FormTitle from '@/components/common/form/FormTitle';
import TextFieldLineBreak from '@/components/common/form/TextFieldLineBreak';
import Responsive from '@/components/common/Responsive';
import { MOBILE_MEDIA_QUERY } from '@/styles/mediaQuery';

export default function MyInfoForm() {
const {
Expand All @@ -25,17 +28,49 @@ export default function MyInfoForm() {
>
경력
</FormTitle>
<ChipField
field='memberInfo.career'
errorMessage={errors.memberInfo?.career?.message ?? ''}
chipList={CAREER_LEVEL}
isSingleSelect
/>

<FormItem errorMessage={errors.memberInfo?.career?.message ?? ''}>
<Controller
name='memberInfo.career'
control={control}
render={({ field }) => (
<>
<CareerOptionContainer>
<Responsive only='desktop' {...field}>
<SelectV2.Root
type='text'
className='option-container'
visibleOptions={6}
defaultValue={CAREER_LEVEL_OPTIONS.find((option) => option.value === field.value)}
onChange={(value) => field.onChange(value)}
>
<SelectV2.Trigger>
<SelectV2.TriggerContent placeholder={'경력 선택'} />
</SelectV2.Trigger>
<SelectV2.Menu>
{CAREER_LEVEL_OPTIONS.map((option) => (
<SelectV2.MenuItem key={option.value} option={option} />
))}
</SelectV2.Menu>
</SelectV2.Root>
</Responsive>

<Responsive only='mobile' {...field}>
<BottomSheetSelect
options={[...CAREER_LEVEL_OPTIONS]}
value={field.value}
placeholder='경력 선택'
onChange={(value) => field.onChange(value)}
/>
</Responsive>
</CareerOptionContainer>
</>
)}
/>
</FormItem>
</CareerWrapper>
<article>
<FormTitle essential breakPoint={COFFEECHAT_MOBILE_MEDIA_QUERY}>
자기소개
</FormTitle>
<FormTitle breakPoint={COFFEECHAT_MOBILE_MEDIA_QUERY}>자기소개</FormTitle>
<Controller
name='memberInfo.introduction'
control={control}
Expand Down Expand Up @@ -82,3 +117,36 @@ const CareerWrapper = styled.article`
flex-direction: column;
gap: 12px;
`;

const CareerOptionContainer = styled.div`
.option-container {
width: 312px;
button {
width: 312px;
div {
width: 312px;
}
}
}
@media ${MOBILE_MEDIA_QUERY} {
.option-container {
width: 100%;
ul {
margin-bottom: 24px;
max-height: 400px !important;
}
button {
width: 100%;
div {
width: 100%;
}
}
}
}
`;
15 changes: 8 additions & 7 deletions src/components/coffeechat/upload/CoffeechatForm/constants.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
export const CAREER_LEVEL = [
'아직 없어요',
'인턴 경험만 있어요',
'주니어 (0-3년)',
'미들 (4-8년)',
'시니어 (9년 이상)',
'창업 중',
export const CAREER_LEVEL_OPTIONS = [
{ label: '아직 없어요', value: '아직 없어요' },
{ label: '인턴 경험만 있어요', value: '인턴 경험만 있어요' },
{ label: '주니어 (0-3년)', value: '주니어 (0-3년)' },
{ label: '미들 (4-8년)', value: '미들 (4-8년)' },
{ label: '시니어 (9년 이상)', value: '시니어 (9년 이상)' },
{ label: '창업 중', value: '창업 중' },
] as const;
export type CareerLevelOptions = typeof CAREER_LEVEL_OPTIONS;

export const COFFECHAT_SECTION = ['SOPT 활동', '기획', '디자인', '프론트', '백엔드', '앱 개발', '기타'] as const;

Expand Down
1 change: 0 additions & 1 deletion src/components/coffeechat/upload/CoffeechatForm/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const myInfoSchema = yup.object().shape({
(value) => typeof value === 'string' || (Array.isArray(value) && value.length > 0),
)
.required('경력을 선택해주세요'),
introduction: yup.string().required('자기소개를 입력해주세요'),
});

const coffeeChatInfoSchema = yup.object().shape({
Expand Down

0 comments on commit abfd07b

Please sign in to comment.