오늘자 PR: width 100%가 안 먹힌 이유, table 레이아웃에 대하여
오늘자 PR에 여담 좀 추가했다
fix: Tabs 및 자식 레이아웃 수정 by SihyeonHong · Pull Request #53 · SihyeonHong/EasiestCV
Bug Fix
번호 매긴 항목들은 모두 이어지는 내용.
`PublicHome` 모바일 레이아웃 약간 변경
기존 코드:
<CardContent className="flex flex-col items-start justify-center gap-5 md:flex-row">
items-center
였다가 md 이상에서 텍스트가 길 경우 이미지 상하에 여백 생기는 것을 고치기 위해 items-start
로 변경했었다.
그런데 모바일 화면에서는 items-start
하면 이미지가 작을 경우 왼쪽에 치우친다는 걸 까먹었다.
수정:
<CardContent className="flex flex-col items-center justify-center gap-5 md:flex-row md:items-start">
1. 모바일에서 내비게이션 바 반응형 안 되는 문제
부트스트랩 -> TailwindCSS 로 바꾸면서 Tabs 컴포넌트의 내비게이션 부분에 반응형 적용이 안 되어 있었다는 걸 잊고 있었다. (교훈: 모바일 우선 작업 제발)
폰에서 확인해보니 내비게이션 바 가로로 잘리고 있었다. flex-col
은 짧은 메뉴 여러개다 보니 안 어울릴 것 같아서, 가로 길이 넘치면 줄 넘어가는 걸로 결정했다.
shadcn에서 받은 Tabs 컴포넌트에서
const TabsList = React.forwardRef<
(중략)
className={cn(
"inline-flex h-9"
inline-flex h-9
지우고 flex flex-wrap gap-1
추가.
inline-flex
: 한 줄로만 되게 만드는 거h-9
: 고정 높이 지워서 두 줄 이상 될 때 높이 자동 조절되도록
그리고 메뉴 좀 큰 게 더 예쁜 것 같아서 하는 김에 min-h-11
도 추가.
2. Tabs
너비 조절
1번 해결했더니 새롭게 등장한 문제: TabsList가 TabsContent 내부의 Card보다 약간 왼쪽으로 튀어나왔다.
이걸 해결하려고 뜯어보다 보니 Public과 Admin 페이지가 원칙상 레이아웃이 똑같아야 하는데, AdminTabs
와 PublicContent
에서 각자 레이아웃을 조절하고 있었다는 걸 발견했다.
통합하기 위해 그냥 Tabs 공통 컴포넌트에 margin과 width를 주기로 했다.
const Tabs = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Root
ref={ref}
className={cn("mx-2 md:mx-8 lg:mx-auto lg:max-w-[1024px]", className)}
{...props}
/>
));
Tabs.displayName = TabsPrimitive.Root.displayName;
const Tabs = TabsPrimitive.Root;
로 단순 alias 할당되어있던 Tabs를 forwardRef
로 래핑하여 TailwindCSS 유틸리티 클래스 추가.
기존에 PublicContent 등에 산발적으로 있던 <Card className="mx-2 md:mx-8 lg:w-[1024px]">
와 같은 클래스는 모두 삭제.
아 맞다 로그인
로그인도 탭을 쓰는데 여긴 같은 너비가 아니라는 걸 깜빡했다.
하지만 그냥 Public과 Admin이 공동으로 사용하는 너비를 Tabs에 주고, 로그인 탭에만 예외 처리를 하는 게 낫겠다고 판단했다.
수정된 코드:
<div className="mx-auto flex w-full max-w-lg flex-col gap-4 p-2 md:max-w-xl">
<Tabs defaultValue="login" className="mx-0 w-full max-w-none md:mx-0">
3. lg 미만 뷰포트에서 Card 사이즈 문제
2번에서 Tabs에 "mx-2 md:mx-8 lg:mx-auto lg:max-w-[1024px]"
를 추가했으나,
lg 이상의 화면에서는 1024px 고정된 너비로 잘 나왔지만 lg(1024px) 미만 뷰포트에서 의도대로 너비가 동작하지 않았다.
의도: mx-2 md:mx-8
여백만 남기고 Card 컴포넌트가 100% width로 채우기. 반응형.
실제: 내부 텍스트 양에 따라 너비가 조정됨. 따라서 탭을 바꿀 때마다 카드의 너비가 달라지는 현상(shrink-to-fit) 발생.
Admin에서는 너비가 꽉 채워지는데 Public에서만 문제가 발생해서 더 포인트를 찾기 어려웠다. 처음에는 둘 간 차이에 해답이 있을 거라고 생각해서 대조에 집중했으나 나중에 알고 보니 그냥 Admin 안에는 ReactQuill 에디터가 들어가 있어서 w-full
이 가능했던 것뿐이었다.
3-1. PublicContainer
에 w-full
주기
컴포넌트 구조가 대충 이런 식인데
1 <PublicContainer>
2 <PublicTabs> = <Tabs>
3 <PublicContents> = <Card>
4 <CardContent>
5 <div dangerouslySetInnerHTML={{
w-full
을 부모 컴포넌트에서 줬어야 했는데 자식 컴포넌트에서 줘야 한다고 착각했다.
아무래도 Admin 쪽에서 ReactQuill 에디터로 인해 너비가 최대로 늘어날 수 있었다는 점을 파악한 다음이라 자식이 넓어져야 한다고 생각한 듯하다.
min-w-full
, max-w-full
, w-full
등 width 관련된 클래스들을 PublicTabs
이하의 컴포넌트들에서만 여러가지 조합으로 넣었다 빼기를 반복했다. prose
도 width에 관여한다길래 prose max-w-none dark:prose-invert prose-headings:!text-inherit prose-p:!text-inherit
등의 클래스들도 마찬가지로 넣었다 뺐다 며칠간 삽질을 했다. Tailwind 클래스가 잘 안 먹히나 싶어서 인라인 스타일도 시도해봤지만 마찬가지였다.
w-full
만을 위한 FullWidth
라는 컴포넌트를 만들어서 역시 PublicTabs
이하에서 여기저기 끼워넣어 봤지만 소용없었다.
원래 PublicContainer
는 CSS에 전혀 관여하지 않고 데이터 페칭 상태에 따라서 화면에 어떤 페이지를 띄워줄 것인지 결정하는 컴포넌트였기 때문에 거기까지 올라갈 생각을 미처 못했다. 여기까지 올라와서 w-full
로 자식들을 제어해 줘야 하는 거였다.
수정한 코드: PublicContainer.tsx
<div className="flex w-full justify-center px-4">
<div className="w-full max-w-[1024px]">
3-2. 또 다른 방법: display:table
활용
사실 이 방법을 먼저 알게 되었다.
export default function PublicContents({ userid, tid }: Props) {
<Card className="table w-full table-fixed lg:w-[1024px]">
<CardContent className="table-cell">
{content && (
display: table
: 요소를 테이블처럼 동작하게 만든다. 무슨 뜻이냐면, 해당 요소가display: block
처럼 쌓이는 대신 테이블처럼 레이아웃을 구성하며, 자식 요소 중display: table-row
,display: table-cell
등을 가진 요소들이 실제 테이블 구조처럼 정렬된다. 즉, 테이블의 레이아웃 규칙을 따르게 된다.table-layout: fixed
: 브라우저의 "최적 너비 계산"을 무시하고 테이블의 너비를 내용과 상관없이 고정.display: table-cell
: 자식 요소가 테이블 셀처럼 동작. 부모의 전체 너비를 차지.
그리고 Tabs에서 "w-full mx-2 md:mx-8 lg:mx-auto lg:max-w-[1024px]"
를 줘서
이렇게 지정된 너비를 강제로 유지하게 만들었다.
채택하지 않은 이유
table layout보다는 부모 컴포넌트에서 w-full 주는 것이 더 의도를 명확히 반영하기 때문에 3-1 방법을 선택했다.
여담
이 문제를 해결하다가 table layout이라는 것을 처음 알게 되고 AI한테 이게 어떤 원리로 내 문제를 해결해준 거냐고 물었는데,
이 문제는 CSS의 shrink-to-fit 동작 때문에 발생했습니다.
블록 요소라도 특정 상황에서는 내용에 맞춰 너비가 축소됩니다
브라우저가 텍스트 콘텐츠를 기반으로 "최적의 너비"를 계산하려고 시도합니다
width: 100%
나min-width: 100%
등은 여전히 이런 계산의 영향을 받습니다
HTML 요소의 width가 명시적으로 설정되지 않았거나, width: auto
일 때 브라우저는 다음 중 최소 너비를 계산해서 요소의 실제 너비를 결정하게 됩니다.
min-content: 가장 긴 단어(또는 줄)를 줄바꿈 없이 담을 수 있는 최소 너비
max-content: 줄바꿈 없이 모든 내용을 한 줄에 담을 수 있는 최대 너비
available width: 부모 요소에서 사용할 수 있는 남은 공간
(예시 코드)
w-full
이라도 부모 컨텍스트가 제대로 정의되지 않았거나, 내부에 길쭉한 텍스트가 있으면브라우저는 “이 내용을 다 보여주려면 이만큼 너비가 있어야겠네” → Card 자체를 넓혀버림
이런 설명을 해주길래 여기서 깨달았다.
"그러니까 나는 지금까지 주로 실제 콘텐츠가 담기는 html div랑 그 근처 Card에서 주로 width를 조절하려고 시도했는데, 그보다는 부모 컴포넌트에서 width를 조절했어야 했던 거야?" 라고 물으니 맞단다....
Others
InitPage
에서UsageGuide
컴포넌트 분리: 아래 데모 계정 설명 부분.InitPage
여전히 파일 너무 길어서 나중에 차차 컴포넌트 분리하고 싶음.불필요한 Tailwind Class 삭제. (ex: 안 먹히고 있던
w-full
,p-0
등)오타 수정.