Skip to content

Commit 5f732be

Browse files
sumi-0011claudesumi
authored
refactor: InventoryGrid 전면 적용 및 레이아웃 높이 제한 (#377)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: sumi <sumi@sumiui-MacBookAir.local>
1 parent 3d4079a commit 5f732be

11 files changed

Lines changed: 86 additions & 178 deletions

File tree

apps/web/src/app/[locale]/mypage/(github-custom)/LinePersonaSelect.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import React, { useState } from 'react';
44
import { useTranslations } from 'next-intl';
55
import { cn } from '@gitanimals/ui-tailwind';
6-
import { DialogV2, ScrollArea } from '@gitanimals/ui-tailwind';
6+
import { DialogV2 } from '@gitanimals/ui-tailwind';
77
import { ExpandIcon } from 'lucide-react';
88

99
import { SelectPersonaList } from '../PersonaList';
@@ -31,25 +31,25 @@ export const LinePersonaSelect = ({ selectPersona, onChangePersona }: Props) =>
3131
<ExpandIcon color="white" size={20} />
3232
</button>
3333
</section>
34-
<ScrollArea className="h-40">
35-
<SelectPersonaList
36-
selectPersona={selectPersona ? [selectPersona] : []}
37-
onSelectPersona={(persona) => onChangePersona(persona.id)}
38-
/>
39-
</ScrollArea>
34+
<SelectPersonaList
35+
selectPersona={selectPersona ? [selectPersona] : []}
36+
onSelectPersona={(persona) => onChangePersona(persona.id)}
37+
>
38+
<SelectPersonaList.InventoryGrid minRows={2} maxRows={3} />
39+
</SelectPersonaList>
4040
<DialogV2 open={isExtend} onOpenChange={() => setIsExtend(false)}>
41-
<DialogV2.Content size="lg">
41+
<DialogV2.Content size="lg" className="h-full">
4242
<DialogV2.CloseButton />
43-
<DialogV2.Header>
44-
<DialogV2.Title>{t('line-type-select-pet')}</DialogV2.Title>
45-
</DialogV2.Header>
4643
<SelectPersonaList
4744
selectPersona={selectPersona ? [selectPersona] : []}
4845
onSelectPersona={(persona) => onChangePersona(persona.id)}
4946
>
50-
<SelectPersonaList.Toolbar showSearch />
51-
<DialogV2.Body>
52-
<SelectPersonaList.Grid />
47+
<DialogV2.Header>
48+
<DialogV2.Title>{t('line-type-select-pet')}</DialogV2.Title>
49+
<SelectPersonaList.Toolbar showSearch />
50+
</DialogV2.Header>
51+
<DialogV2.Body scroll={false} className="h-full flex-1">
52+
<SelectPersonaList.InventoryGrid minRows={2} mode="dialog" />
5353
</DialogV2.Body>
5454
</SelectPersonaList>
5555
</DialogV2.Content>

apps/web/src/app/[locale]/mypage/PersonaList.tsx

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,9 @@ function Grid() {
101101
// ─── InventoryGrid (Embla carousel + dynamic grid) ─────────────────
102102

103103
const MAX_DOTS = 5;
104-
const NAV_HEIGHT = 44; // arrows + dots bar
104+
const NAV_HEIGHT = 40; // arrows(24px) + gap(8px)
105105
/** 다이얼로그 크롬: 제목(50) + 검색(40) + 필터(40) + 패딩/갭(30) + nav(44) */
106106
const DIALOG_CHROME_HEIGHT = 204;
107-
const INLINE_CHROME_HEIGHT = NAV_HEIGHT;
108107

109108
function useInventoryGrid(
110109
totalItems: number,
@@ -134,17 +133,27 @@ function useInventoryGrid(
134133
if (width < minItemSize * 2) return;
135134
const nextCols = Math.max(Math.floor((width + gap) / (minItemSize + gap)), 1);
136135

137-
// rows: 뷰포트 높이에서 크롬 높이를 뺀 가용 영역 기반
138-
// 실제 아이템 높이를 측정하여 정확한 행 수 계산
139-
const firstItem = el.querySelector('[class*="grid"] > button');
140-
const measuredHeight = firstItem ? firstItem.getBoundingClientRect().height : 0;
141-
const itemHeight = measuredHeight > 0 ? measuredHeight : minItemSize;
136+
// rows: 가용 높이 기반. 아이템이 aspect-square이므로 컬럼 너비 = 아이템 높이
137+
const colWidth = (width - (nextCols - 1) * gap) / nextCols;
138+
const itemHeight = colWidth;
142139
const rowHeight = itemHeight + gap;
143140

144141
const vh = window.innerHeight;
145-
const chrome = mode === 'dialog' ? DIALOG_CHROME_HEIGHT : INLINE_CHROME_HEIGHT;
146-
const dialogHeight = mode === 'dialog' ? vh * 0.9 : vh;
147-
const availableHeight = dialogHeight - chrome;
142+
let availableHeight: number;
143+
if (mode === 'dialog') {
144+
availableHeight = vh * 0.9 - DIALOG_CHROME_HEIGHT;
145+
} else {
146+
// inline: 컨테이너에 flex-1 min-h-0이 설정되어 있으므로
147+
// clientHeight가 부모 flex에 의해 제한된 실제 높이
148+
const containerHeight = el.clientHeight;
149+
if (containerHeight > 0) {
150+
availableHeight = containerHeight - NAV_HEIGHT;
151+
} else {
152+
// 초기 렌더링 시 높이가 0일 수 있음 → fallback
153+
const rect = el.getBoundingClientRect();
154+
availableHeight = vh - rect.top - NAV_HEIGHT;
155+
}
156+
}
148157
const nextRows =
149158
availableHeight > 0 ? Math.min(Math.max(Math.floor(availableHeight / rowHeight), minRows), maxRows) : minRows;
150159

@@ -225,11 +234,11 @@ function InventoryGrid({ minRows = 2, maxRows = 10, minItemSize = 64, gap = 4, m
225234
}
226235

227236
return (
228-
<div ref={containerRef} className="overflow-hidden">
237+
<div ref={containerRef} className="overflow-hidden flex flex-col gap-2 flex-1 min-h-0">
229238
{!ready ? null : (
230239
<>
231240
{/* Navigation: arrows left, dots right */}
232-
<div className="flex mb-2 justify-between items-center">
241+
<div className="flex justify-between items-center">
233242
<div className="flex gap-[10px]">
234243
<PrevButton onClick={onPrevButtonClick} disabled={prevBtnDisabled} className="w-6 h-6" />
235244
<NextButton onClick={onNextButtonClick} disabled={nextBtnDisabled} className="w-6 h-6" />

apps/web/src/app/[locale]/mypage/layout.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ async function MypageLayout({ children }: { children: React.ReactNode }) {
1919
<ProfileSection />
2020
<div
2121
className={cn(
22-
'overflow-x-hidden w-full rounded-2xl',
23-
'bg-white/10 backdrop-blur-[7px] max-h-[1400px]',
22+
'overflow-hidden w-full rounded-2xl',
23+
'bg-white/10 backdrop-blur-[7px] max-h-[calc(100vh-300px)]',
2424
'p-10 flex flex-col relative gap-10',
25-
'max-pc:gap-6 max-pc:p-6',
25+
'max-pc:max-h-[calc(100vh-124px)] max-pc:gap-6 max-pc:p-6',
2626
'max-mobile:gap-3 max-mobile:bg-transparent max-mobile:p-0',
2727
'max-mobile:max-h-none max-mobile:h-auto max-mobile:overflow-y-auto max-mobile:rounded-none',
2828
)}

apps/web/src/app/[locale]/mypage/my-pet/(merge)/MergePersona.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useQueryClient } from '@tanstack/react-query';
99

1010
import { useClientSession } from '@/shared/utils/clientAuth';
1111

12-
import { SelectPersonaList } from '../_components/SelectPersonaList';
12+
import { SelectPersonaList } from '../../PersonaList';
1313
import { SpinningLoader } from '../_components/SpinningLoader';
1414

1515
import { MergePreview } from './MergePreview';
@@ -72,9 +72,14 @@ export function MergePersona({ isOpen, onClose, targetPersona: initTargetPersona
7272
</DialogV2.Header>
7373
<MergePreview targetPersona={targetPersona} materialPersona={materialPersona} />
7474

75-
<DialogV2.Body>
76-
<SelectPersonaList selectPersona={selectPersona} onSelectPersona={onSelectPersona} />
77-
</DialogV2.Body>
75+
<SelectPersonaList selectPersona={selectPersona} onSelectPersona={onSelectPersona}>
76+
<SelectPersonaList.Toolbar showSearch />
77+
78+
<DialogV2.Body scroll={false} className="h-full flex-1">
79+
<SelectPersonaList.InventoryGrid minRows={2} mode="dialog" />
80+
</DialogV2.Body>
81+
</SelectPersonaList>
82+
7883

7984
<DialogV2.Footer className="justify-center">
8085
<Button variant="secondary" onClick={onClose}>

apps/web/src/app/[locale]/mypage/my-pet/_components/SelectPersonaList.tsx

Lines changed: 0 additions & 78 deletions
This file was deleted.

apps/web/src/app/[locale]/mypage/my-pet/page.tsx

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { useCallback, useState } from 'react';
44
import { useTranslations } from 'next-intl';
55
import { type Persona } from '@gitanimals/api';
66
import { cn } from '@gitanimals/ui-tailwind';
7-
import { ScrollArea } from '@gitanimals/ui-tailwind';
87

98
import { SelectPersonaList } from '../PersonaList';
109

@@ -19,28 +18,26 @@ function MypageMyPets() {
1918
}, []);
2019

2120
return (
22-
<div className="flex flex-col">
21+
<div className="flex flex-col h-full min-h-0">
2322
<SelectedPetTable currentPersona={selectPersona} reset={() => setSelectPersona(null)} />
2423
<section
2524
className={cn(
26-
'relative',
25+
'relative flex-1 min-h-0 flex flex-col',
2726
'[&_.heading]:font-product [&_.heading]:text-glyph-18 [&_.heading]:font-bold',
2827
'[&_.heading]:text-white [&_.heading]:mb-4',
2928
)}
3029
>
3130
<h2 className="heading">{t('pet-list')}</h2>
3231

33-
<ScrollArea className="h-[calc(100vh-424px)]">
34-
<SelectPersonaList
35-
selectPersona={selectPersona ? [selectPersona.id] : []}
36-
onSelectPersona={(persona) => setSelectPersona(persona)}
37-
initSelectPersonas={initSelectPersonas}
38-
isSpecialEffect
39-
>
40-
<SelectPersonaList.Toolbar showSearch showEvolvableFilter />
41-
<SelectPersonaList.Grid />
42-
</SelectPersonaList>
43-
</ScrollArea>
32+
<SelectPersonaList
33+
selectPersona={selectPersona ? [selectPersona.id] : []}
34+
onSelectPersona={(persona) => setSelectPersona(persona)}
35+
initSelectPersonas={initSelectPersonas}
36+
isSpecialEffect
37+
>
38+
<SelectPersonaList.Toolbar showSearch showEvolvableFilter />
39+
<SelectPersonaList.InventoryGrid minRows={2} mode="inline" />
40+
</SelectPersonaList>
4441
</section>
4542

4643
<p

apps/web/src/app/[locale]/shop/_auction/SellSection/EditModal.tsx

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React, { useState } from 'react';
44
import { useTranslations } from 'next-intl';
55
import { auctionQueries, useChangeProductPrice, useDeleteProduct, userQueries } from '@gitanimals/react-query';
66
import { cn } from '@gitanimals/ui-tailwind';
7-
import { Button, DialogV2 } from '@gitanimals/ui-tailwind';
7+
import { Button, DialogV2, TextField } from '@gitanimals/ui-tailwind';
88
import { useQueryClient } from '@tanstack/react-query';
99
import { toast } from 'sonner';
1010

@@ -63,12 +63,12 @@ function EditModal({ isOpen, onClose, productId }: { isOpen: boolean; onClose: (
6363
<DialogV2.Header>
6464
<DialogV2.Title className={titleStyle}>{t('edit-product')}</DialogV2.Title>
6565
</DialogV2.Header>
66-
<input
67-
className={inputStyle}
66+
<TextField
6867
placeholder="Type price..."
6968
type="number"
7069
value={Boolean(price) ? price : ''}
7170
onChange={(e) => setPrice(Number(e.target.value))}
71+
className="[&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-inner-spin-button]:m-0"
7272
/>
7373
<DialogV2.Footer>
7474
<Button onClick={onSave} variant="secondary" size="m">
@@ -86,14 +86,3 @@ function EditModal({ isOpen, onClose, productId }: { isOpen: boolean; onClose: (
8686
export default EditModal;
8787

8888
const titleStyle = cn('font-product text-glyph-20 text-left text-white w-full');
89-
90-
const inputStyle = cn(
91-
'flex h-[55px] py-3.5 pl-5 pr-3.5',
92-
'items-start gap-2 w-full outline-none',
93-
'rounded-lg border border-white/25',
94-
'font-product text-glyph-16 text-white',
95-
'placeholder:text-glyph-16 placeholder:text-white/75',
96-
'[&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-inner-spin-button]:m-0',
97-
);
98-
99-
const buttonWrapperStyle = cn('flex justify-end gap-2 w-full');

apps/web/src/app/[locale]/shop/_auction/SellSection/SellInputRow.tsx

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useTranslations } from 'next-intl';
55
import type { Persona } from '@gitanimals/api';
66
import useIsMobile from '@gitanimals/react/src/hooks/useIsMobile/useIsMobile';
77
import { auctionQueries, userQueries } from '@gitanimals/react-query';
8-
import { Button, DialogV2 } from '@gitanimals/ui-tailwind';
8+
import { Button, DialogV2, TextField } from '@gitanimals/ui-tailwind';
99
import { cn } from '@gitanimals/ui-tailwind/utils';
1010
import { snakeToTitleCase } from '@gitanimals/util-common';
1111
import { useQueryClient } from '@tanstack/react-query';
@@ -112,11 +112,10 @@ function SellInputRow({ item, initPersona }: Props) {
112112
<div>{ANIMAL_TIER_TEXT_MAP[personaTier]}</div>
113113
<div>{item.level}</div>
114114
<div>
115-
<input
115+
<TextField
116116
className={cn(
117-
'font-product text-glyph-20',
118-
'w-full h-full min-h-16 text-xl font-bold',
119-
'border-none outline-none',
117+
'text-glyph-20 text-xl font-bold',
118+
'h-full min-h-16 border-none',
120119
'placeholder:text-glyph-20 placeholder:text-white/25',
121120
'[&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-inner-spin-button]:m-0',
122121
)}
@@ -156,19 +155,12 @@ function SellPriceModal({
156155
<DialogV2.Header>
157156
<DialogV2.Title>{t('sell-price-modal-title')}</DialogV2.Title>
158157
</DialogV2.Header>
159-
<input
160-
className={cn(
161-
'flex h-[55px] py-3.5 pl-5 pr-3.5',
162-
'items-start gap-2 w-full outline-none',
163-
'rounded-lg border border-white/25',
164-
'font-product text-glyph-16 text-white',
165-
'placeholder:text-glyph-16 placeholder:text-white/75',
166-
'[&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-inner-spin-button]:m-0',
167-
)}
158+
<TextField
168159
placeholder="Type price..."
169160
type="number"
170161
value={Boolean(sellPrice) ? sellPrice : ''}
171162
onChange={(e) => setSellPrice(Number(e.target.value))}
163+
className="[&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-inner-spin-button]:m-0"
172164
/>
173165
<DialogV2.Footer>
174166
<Button onClick={onClose} variant="secondary" size="m">

apps/web/src/components/Global/FeedbackForm.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { useState } from 'react';
55
import Image from 'next/image';
66
import { XIcon } from '@gitanimals/ui-icon';
77
import { Button } from '@gitanimals/ui-tailwind';
8+
import { cn } from '@gitanimals/ui-tailwind/utils';
89
import { toast } from 'sonner';
910

1011
import { usePostFeedback } from '@/features/feedback/model/usePostFeedback';
1112
import type { PostIssueRequest } from '@/features/feedback/model/usePostIssue';
12-
import Input from '@/components/Input';
1313
import Select from '@/components/Select';
1414
import TextArea from '@/components/TextArea';
1515
import type { GithubIssueType } from '@/shared/config/github';
@@ -152,6 +152,23 @@ const useFeedbackContent = () => {
152152
return { content, onContentChange, isValid, initContent };
153153
};
154154

155+
function Input(props: React.ComponentProps<'input'>) {
156+
return (
157+
<input
158+
{...props}
159+
className={cn(
160+
'border border-black/10 bg-transparent text-black/75',
161+
'py-[14px] pb-[13px] pl-5 pr-[14px]',
162+
'font-product text-glyph-16',
163+
'rounded-lg outline-none w-full',
164+
'focus:border-[#00894d]',
165+
'placeholder:text-black/50 placeholder:font-product placeholder:text-glyph-16',
166+
props.className,
167+
)}
168+
/>
169+
);
170+
}
171+
155172
function LabelSelect({ onChange }: { onChange: (value: string[]) => void }) {
156173
return (
157174
<Select>

0 commit comments

Comments
 (0)