style(custom-entry-modal): 커스텀 입장 모달 톤과 크기를 안정화
맥락: - 커스텀 입장 모달이 허브 대비 과도하게 어두워 화면 톤 일관성이 떨어졌다. - 탭(공간/사운드/타이머) 전환 시 본문 높이가 달라져 모달 외곽 크기가 흔들리는 UX 이슈가 있었다. 변경사항: - Modal 오버레이/패널/헤더/푸터 스타일을 밝은 글래스 톤으로 조정해 허브 톤과 맞췄다. - CustomEntryModal의 탭 콘텐츠 영역을 고정 높이 컨테이너로 변경해 탭 전환 시 모달 전체 크기가 변하지 않도록 했다. - 공간/사운드/타이머 옵션 버튼과 커스텀 타이머 입력 필드를 밝은 팔레트로 재정의했다. - Tabs 컴포넌트를 밝은 표면 톤에 맞게 보정했다. - 세션 문서(90_current_state, session_brief)에 이번 작업 내용/리스크를 반영했다. 검증: - npx tsc --noEmit 세션-상태: 커스텀 입장 모달의 밝은 톤 정렬 및 탭 전환 크기 고정 반영 완료 세션-다음: RoomSheet/도크 패널의 인원수 기반 표현을 분위기형 정보로 전환 세션-리스크: 모달 고정 높이 적용으로 작은 화면에서 탭 본문 내부 스크롤 의존도가 증가할 수 있음
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
|
||||
import { ROOM_THEMES } from '@/entities/room';
|
||||
import { SOUND_PRESETS, TIMER_PRESETS } from '@/entities/session';
|
||||
import { Button, Chip, Modal, Tabs } from '@/shared/ui';
|
||||
import { Button, Modal, Tabs } from '@/shared/ui';
|
||||
import { cn } from '@/shared/lib/cn';
|
||||
import {
|
||||
type CustomEntrySelection,
|
||||
useCustomEntryForm,
|
||||
@@ -64,95 +65,118 @@ export const CustomEntryModal = ({
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:justify-end">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="!bg-white/10 !text-white hover:!bg-white/15"
|
||||
className="!border !border-brand-dark/16 !bg-white/72 !text-brand-dark hover:!bg-white"
|
||||
onClick={handleClose}
|
||||
>
|
||||
닫기
|
||||
</Button>
|
||||
<Button onClick={handleEnter}>이 설정으로 입장</Button>
|
||||
<Button className="!shadow-none" onClick={handleEnter}>
|
||||
이 설정으로 입장
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="space-y-5">
|
||||
<Tabs value={activeTab} options={tabOptions} onChange={(value) => setActiveTab(value as 'theme' | 'sound' | 'timer')} />
|
||||
|
||||
{activeTab === 'theme' ? (
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
{ROOM_THEMES.map((room) => (
|
||||
<button
|
||||
key={room.id}
|
||||
type="button"
|
||||
onClick={() => onSelectRoom(room.id)}
|
||||
className={`rounded-xl border px-3 py-3 text-left transition-colors ${
|
||||
selectedRoomId === room.id
|
||||
? 'border-sky-200 bg-sky-300/22 text-sky-50'
|
||||
: 'border-white/16 bg-white/5 text-white/85 hover:bg-white/10'
|
||||
}`}
|
||||
>
|
||||
<p className="text-sm font-medium">{room.name}</p>
|
||||
<p className="mt-1 text-xs text-white/70">{room.description}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{activeTab === 'sound' ? (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{SOUND_PRESETS.map((preset) => (
|
||||
<Chip
|
||||
key={preset.id}
|
||||
active={selectedSoundId === preset.id}
|
||||
onClick={() => setSelectedSoundId(preset.id)}
|
||||
>
|
||||
{preset.label}
|
||||
</Chip>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{activeTab === 'timer' ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{TIMER_PRESETS.map((preset) => (
|
||||
<Chip
|
||||
key={preset.id}
|
||||
active={selectedTimerId === preset.id}
|
||||
onClick={() => setSelectedTimerId(preset.id)}
|
||||
>
|
||||
{preset.label}
|
||||
</Chip>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{selectedTimerId === 'custom' ? (
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<label className="space-y-1 text-sm text-white/75">
|
||||
<span>집중(분)</span>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={customFocusMinutes}
|
||||
onChange={(event) => setCustomFocusMinutes(event.target.value)}
|
||||
className="w-full rounded-xl border border-white/20 bg-slate-900/70 px-3 py-2 text-white placeholder:text-white/45"
|
||||
placeholder="25"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="space-y-1 text-sm text-white/75">
|
||||
<span>휴식(분)</span>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={customBreakMinutes}
|
||||
onChange={(event) => setCustomBreakMinutes(event.target.value)}
|
||||
className="w-full rounded-xl border border-white/20 bg-slate-900/70 px-3 py-2 text-white placeholder:text-white/45"
|
||||
placeholder="5"
|
||||
/>
|
||||
</label>
|
||||
<div className="h-[350px] overflow-hidden rounded-2xl border border-brand-dark/12 bg-white/50 p-3 sm:h-[390px]">
|
||||
{activeTab === 'theme' ? (
|
||||
<div className="h-full overflow-y-auto pr-1">
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
{ROOM_THEMES.map((room) => (
|
||||
<button
|
||||
key={room.id}
|
||||
type="button"
|
||||
onClick={() => onSelectRoom(room.id)}
|
||||
className={cn(
|
||||
'rounded-xl border px-3 py-3 text-left transition-colors',
|
||||
selectedRoomId === room.id
|
||||
? 'border-brand-primary/45 bg-brand-soft/58 text-brand-dark'
|
||||
: 'border-brand-dark/14 bg-white/72 text-brand-dark/84 hover:bg-white',
|
||||
)}
|
||||
>
|
||||
<p className="text-sm font-medium">{room.name}</p>
|
||||
<p className="mt-1 text-xs text-brand-dark/62">{room.description}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{activeTab === 'sound' ? (
|
||||
<div className="h-full overflow-y-auto pr-1">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{SOUND_PRESETS.map((preset) => (
|
||||
<button
|
||||
key={preset.id}
|
||||
type="button"
|
||||
onClick={() => setSelectedSoundId(preset.id)}
|
||||
className={cn(
|
||||
'rounded-full border px-3 py-1.5 text-xs font-medium transition-colors',
|
||||
selectedSoundId === preset.id
|
||||
? 'border-brand-primary/45 bg-brand-soft/58 text-brand-dark'
|
||||
: 'border-brand-dark/14 bg-white/75 text-brand-dark/82 hover:bg-white',
|
||||
)}
|
||||
>
|
||||
{preset.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{activeTab === 'timer' ? (
|
||||
<div className="h-full overflow-y-auto pr-1">
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{TIMER_PRESETS.map((preset) => (
|
||||
<button
|
||||
key={preset.id}
|
||||
type="button"
|
||||
onClick={() => setSelectedTimerId(preset.id)}
|
||||
className={cn(
|
||||
'rounded-full border px-3 py-1.5 text-xs font-medium transition-colors',
|
||||
selectedTimerId === preset.id
|
||||
? 'border-brand-primary/45 bg-brand-soft/58 text-brand-dark'
|
||||
: 'border-brand-dark/14 bg-white/75 text-brand-dark/82 hover:bg-white',
|
||||
)}
|
||||
>
|
||||
{preset.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{selectedTimerId === 'custom' ? (
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<label className="space-y-1 text-sm text-brand-dark/76">
|
||||
<span>집중(분)</span>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={customFocusMinutes}
|
||||
onChange={(event) => setCustomFocusMinutes(event.target.value)}
|
||||
className="w-full rounded-xl border border-brand-dark/18 bg-white/86 px-3 py-2 text-brand-dark placeholder:text-brand-dark/45"
|
||||
placeholder="25"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="space-y-1 text-sm text-brand-dark/76">
|
||||
<span>휴식(분)</span>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={customBreakMinutes}
|
||||
onChange={(event) => setCustomBreakMinutes(event.target.value)}
|
||||
className="w-full rounded-xl border border-brand-dark/18 bg-white/86 px-3 py-2 text-brand-dark placeholder:text-brand-dark/45"
|
||||
placeholder="5"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user