refactor: JDS Card 컴포넌트 vanilla-extract 마이그레이션#500
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughCard 컴포넌트 전체를 Emotion styled-components 기반에서 Vanilla Extract CSS 모듈 기반으로 전환하고, Figma 디자인 변형 기준으로 타입·컨텍스트·프리셋 API를 재정리했습니다. ChangesCard 컴포넌트 API 전면 재설계
추정 코드 리뷰 노력🎯 4 (Complex) | ⏱️ ~60 minutes 연관 가능성이 있는 PR
제안 레이블
제안 리뷰어
시
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (5)
.changeset/card-migration.md (3)
42-48: 📐 Maintainability & Code Quality | 🔵 Trivial
Card.Content자식 분류 로직 제거 누락동작 변경 목록에
Card.Content의 자식 자동 분류(classification) 로직 제거가 빠져 있습니다. 기존에Card.Content가 자식 타입에 따라 자동 배치하던 동작이 사라졌으므로, 마이그레이션 시 compound 조합 방식이 달라질 수 있습니다. 이 변경을 "동작 변경" 또는 "소비자 영향"에 추가하는 것을 권장합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.changeset/card-migration.md around lines 42 - 48, Add the missing Card.Content behavior change to the migration notes: its child auto-classification/auto-placement logic has been removed, so compound composition may behave differently for consumers. Update the .changeset/card-migration.md summary alongside the existing Card.Meta, Plate, and Card.Overlay items, referencing Card.Content explicitly so the behavior change is clearly documented.
19-19: 📐 Maintainability & Code Quality | 🔵 Trivial단독
Thumbnail노출 미지원 명시
Card.Thumbnail또는Thumbnail으로 표기되어 있으나,index.ts에서는Card.Thumbnail만 공개하고 단독Thumbnail은 export하지 않습니다.Thumbnail단독 사용은 불가능하므로Card.Thumbnail로만 안내하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.changeset/card-migration.md at line 19, The migration note currently suggests both Card.Thumbnail and standalone Thumbnail, but index.ts only exposes Card.Thumbnail. Update the Card.Image/CardImageProps replacement guidance to mention Card.Thumbnail only, and remove any standalone Thumbnail wording so the documented API matches the actual export.
11-11: 📐 Maintainability & Code Quality | 🔵 Trivial
Card.ContentGroup설명 수정 필요"title·body를 묶는 그룹 컨테이너"라는 설명은 실제 용도와 다릅니다.
CardContentGroupProps는 일반div슬롯으로,TitleBody내부 컴포넌트가 아닙니다.Card.Content의 자식 분류 로직 제거를 대체하는 용도이므로, 설명을 "콘텐츠 영역 그룹핑 컨테이너" 등으로 수정하세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.changeset/card-migration.md at line 11, `Card.ContentGroup` in the changeset description is inaccurate and should be updated to match its real role. Adjust the wording in the `CardContentGroupProps` entry so it describes a general content-area grouping `div` slot, not a title·body wrapper or `TitleBody` internal component; use a phrase like “콘텐츠 영역 그룹핑 컨테이너” to reflect its purpose as the replacement for `Card.Content` child classification logic.packages/jds/src/components/Card/Plate.stories.tsx (1)
15-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win스토리 예시에
Card.ContentGroup를 반영해 주세요.실제 프리셋 구현은
packages/jds/src/components/Card/presets/shared.tsx의TitleBody를 통해Card.Title/Card.Body를Card.ContentGroup으로 묶고 있고, 이번 PR에서도 그 슬롯을 public API로 노출했습니다. 지금 문서 설명과 예시는 여전히Card.Content바로 아래에 두고 있어서, 새 권장 조합과 마이그레이션 방향이 Storybook에 드러나지 않습니다.예시
<Card.Content> - <Card.Title>플레이트 카드 제목</Card.Title> - <Card.Body>카드 내용은 두 줄을 넘어가면 말줄임(...) 표시합니다.</Card.Body> + <Card.ContentGroup> + <Card.Title>플레이트 카드 제목</Card.Title> + <Card.Body>카드 내용은 두 줄을 넘어가면 말줄임(...) 표시합니다.</Card.Body> + </Card.ContentGroup> <Card.Caption>캡션 레이블</Card.Caption> </Card.Content>Also applies to: 62-66, 115-119
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/jds/src/components/Card/Plate.stories.tsx` around lines 15 - 23, The Card Plate story still shows the old composition using Card.Content, but the new public pattern should demonstrate Card.ContentGroup as the wrapper for Card.Title and Card.Body. Update the story content and example in Plate.stories.tsx to reflect the current preset structure used by TitleBody from presets/shared.tsx, so Storybook documents the recommended slot hierarchy and migration path. Also adjust any related description text to mention Card.ContentGroup instead of implying Card.Content is the direct parent for the title/body slots.packages/jds/src/components/Card/Post.stories.tsx (1)
15-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win여기도
Card.ContentGroup기준으로 예시를 맞춰 주세요.
Post프리셋도 내부적으로는TitleBody를 통해 제목/본문을Card.ContentGroup으로 묶습니다. 스토리가 그 구조를 생략하면 새 슬롯 API가 문서에서 보이지 않고, consumers가Card.Content의 권장 자식 구조를 오해하기 쉽습니다.예시
<Card.Content> - <Card.Title>포스트 카드 제목</Card.Title> - <Card.Body>카드 내용은 두 줄을 넘어가면 말줄임(...) 표시합니다.</Card.Body> + <Card.ContentGroup> + <Card.Title>포스트 카드 제목</Card.Title> + <Card.Body>카드 내용은 두 줄을 넘어가면 말줄임(...) 표시합니다.</Card.Body> + </Card.ContentGroup> <Card.Meta> <Card.MetaItem>김젝트</Card.MetaItem> <Card.MetaItem>2026년 2월 25일(수)</Card.MetaItem> </Card.Meta> </Card.Content>Also applies to: 55-61, 114-120
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/jds/src/components/Card/Post.stories.tsx` around lines 15 - 23, The Post story copy is still describing the old slot structure, so update the `component` documentation in `Post.stories.tsx` to show `Card.ContentGroup` and `TitleBody` as the recommended composition for `variant='post'`. Make the example and surrounding text reflect that `Post` uses `Card.ContentGroup` to wrap title/body content, while keeping the existing references to `Card.Root`, `Card.Thumbnail`, `Card.Content`, `Card.Meta`, and `Card.Overlay` accurate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/jds/src/components/Card/card.types.ts`:
- Around line 35-38: `CardThumbnailImage.src` is too permissive and allows
invalid `image` objects to compile; update the `CardThumbnailImage` type so
`src` is required, or if empty thumbnails are intended, add explicit placeholder
handling in both `CardThumbnailImage` and `CardThumbnail.tsx` so `Thumbnail` is
never called with a missing source. Use the `CardThumbnailImage` interface and
the `CardThumbnail` component as the main touchpoints when applying the fix.
- Around line 57-60: The PresetButtonProps onClick type is too narrow and blocks
standard button handlers, so update the PresetButtonProps definition to reuse
the native button onClick signature from
ComponentPropsWithoutRef<"button">["onClick"] instead of () => void. Keep the
existing as: "button" and type fields intact, and ensure CardOverlay can pass
its button handler through without a type mismatch.
In `@packages/jds/src/components/Card/compound/CardCaption.tsx`:
- Around line 15-23: `CardCaption`에서 내부 슬롯 식별자인 `data-card-slot="caption"`이 소비자
props에 의해 덮어써질 수 있으니, `CardCaption` 컴포넌트의 props 펼침 순서를 조정해 `...restProps`보다
`data-card-slot`이 뒤에서 고정되도록 변경하세요. `CardCaption`의 `<span>` 렌더링 부분에서
`data-card-slot`과 `restProps`의 순서를 바꿔, 외부에서 같은 속성을 넘겨도 `card.css.ts`의
`:has([data-card-slot='caption'])` 분기가 유지되게 하세요.
In `@packages/jds/src/components/Card/compound/CardOverlay.tsx`:
- Around line 45-56: The disabled branch in CardOverlay still forwards
interactive anchor props, so the link can remain clickable or focusable even
when isDisabled is true. Update the CardOverlay anchor rendering to strip or
override interaction props from restProps/anchorProps when disabled,
specifically onClick and tabIndex (and any similar event-driven props you pass
through), while keeping href undefined and the disabled aria/data attributes
intact. Use the existing CardOverlay component and anchorProps handling as the
place to centralize this disabled-state filtering.
In `@packages/jds/src/components/Card/compound/CardTitle.tsx`:
- Around line 15-19: `CardTitle` is allowing `...restProps` to override the
generated `titleId`, which can break `CardOverlay`’s `aria-labelledby` linkage.
Update the `CardTitle` component so the `id` stays stable by giving `titleId`
precedence (or by explicitly supporting a custom `id` and propagating it through
the shared context used by `CardOverlay`). Use the `CardTitle` render path and
its related context/overlay wiring to keep the label reference consistent.
---
Nitpick comments:
In @.changeset/card-migration.md:
- Around line 42-48: Add the missing Card.Content behavior change to the
migration notes: its child auto-classification/auto-placement logic has been
removed, so compound composition may behave differently for consumers. Update
the .changeset/card-migration.md summary alongside the existing Card.Meta,
Plate, and Card.Overlay items, referencing Card.Content explicitly so the
behavior change is clearly documented.
- Line 19: The migration note currently suggests both Card.Thumbnail and
standalone Thumbnail, but index.ts only exposes Card.Thumbnail. Update the
Card.Image/CardImageProps replacement guidance to mention Card.Thumbnail only,
and remove any standalone Thumbnail wording so the documented API matches the
actual export.
- Line 11: `Card.ContentGroup` in the changeset description is inaccurate and
should be updated to match its real role. Adjust the wording in the
`CardContentGroupProps` entry so it describes a general content-area grouping
`div` slot, not a title·body wrapper or `TitleBody` internal component; use a
phrase like “콘텐츠 영역 그룹핑 컨테이너” to reflect its purpose as the replacement for
`Card.Content` child classification logic.
In `@packages/jds/src/components/Card/Plate.stories.tsx`:
- Around line 15-23: The Card Plate story still shows the old composition using
Card.Content, but the new public pattern should demonstrate Card.ContentGroup as
the wrapper for Card.Title and Card.Body. Update the story content and example
in Plate.stories.tsx to reflect the current preset structure used by TitleBody
from presets/shared.tsx, so Storybook documents the recommended slot hierarchy
and migration path. Also adjust any related description text to mention
Card.ContentGroup instead of implying Card.Content is the direct parent for the
title/body slots.
In `@packages/jds/src/components/Card/Post.stories.tsx`:
- Around line 15-23: The Post story copy is still describing the old slot
structure, so update the `component` documentation in `Post.stories.tsx` to show
`Card.ContentGroup` and `TitleBody` as the recommended composition for
`variant='post'`. Make the example and surrounding text reflect that `Post` uses
`Card.ContentGroup` to wrap title/body content, while keeping the existing
references to `Card.Root`, `Card.Thumbnail`, `Card.Content`, `Card.Meta`, and
`Card.Overlay` accurate.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 48056627-555c-4d02-9ecf-3f8d10c8f10e
📒 Files selected for processing (31)
.changeset/card-migration.mdpackages/jds/src/components/Card/Card.context.tspackages/jds/src/components/Card/Card.stories.tsxpackages/jds/src/components/Card/Card.types.tspackages/jds/src/components/Card/Plate.stories.tsxpackages/jds/src/components/Card/Post.stories.tsxpackages/jds/src/components/Card/card.types.tspackages/jds/src/components/Card/cardContext.tspackages/jds/src/components/Card/compound/CardBody.tsxpackages/jds/src/components/Card/compound/CardCaption.tsxpackages/jds/src/components/Card/compound/CardContent.tsxpackages/jds/src/components/Card/compound/CardContentGroup.tsxpackages/jds/src/components/Card/compound/CardImage.tsxpackages/jds/src/components/Card/compound/CardLabel.tsxpackages/jds/src/components/Card/compound/CardMeta.tsxpackages/jds/src/components/Card/compound/CardMetaItem.tsxpackages/jds/src/components/Card/compound/CardOverlay.tsxpackages/jds/src/components/Card/compound/CardRoot.tsxpackages/jds/src/components/Card/compound/CardThumbnail.tsxpackages/jds/src/components/Card/compound/CardTitle.tsxpackages/jds/src/components/Card/compound/card.css.tspackages/jds/src/components/Card/compound/compound.styles.tspackages/jds/src/components/Card/compound/index.tspackages/jds/src/components/Card/index.tspackages/jds/src/components/Card/presets/Plate.tsxpackages/jds/src/components/Card/presets/PlateCompact.tsxpackages/jds/src/components/Card/presets/PlateWithLabel.tsxpackages/jds/src/components/Card/presets/PlateWithTitle.tsxpackages/jds/src/components/Card/presets/Post.tsxpackages/jds/src/components/Card/presets/index.tspackages/jds/src/components/Card/presets/shared.tsx
💤 Files with no reviewable changes (9)
- packages/jds/src/components/Card/presets/PlateWithLabel.tsx
- packages/jds/src/components/Card/presets/PlateCompact.tsx
- packages/jds/src/components/Card/compound/CardImage.tsx
- packages/jds/src/components/Card/Card.context.ts
- packages/jds/src/components/Card/compound/compound.styles.ts
- packages/jds/src/components/Card/compound/CardLabel.tsx
- packages/jds/src/components/Card/presets/PlateWithTitle.tsx
- packages/jds/src/components/Card/Card.stories.tsx
- packages/jds/src/components/Card/Card.types.ts
| export interface CardThumbnailImage { | ||
| src?: string; | ||
| alt: string; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
CardThumbnailImage.src를 optional로 두면 잘못된 입력이 타입에서 통과합니다.
CardThumbnail.tsx는 image.src를 그대로 Thumbnail에 넘기고 빈 이미지 상태를 처리하지 않습니다. 지금 타입이면 image={{ alt: "..." }}가 허용돼서 런타임에서 깨진 썸네일을 만들 수 있습니다. src를 필수로 올리거나, 정말 비어 있는 이미지를 지원할 거면 placeholder 분기를 타입과 구현에 같이 추가해 주세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/jds/src/components/Card/card.types.ts` around lines 35 - 38,
`CardThumbnailImage.src` is too permissive and allows invalid `image` objects to
compile; update the `CardThumbnailImage` type so `src` is required, or if empty
thumbnails are intended, add explicit placeholder handling in both
`CardThumbnailImage` and `CardThumbnail.tsx` so `Thumbnail` is never called with
a missing source. Use the `CardThumbnailImage` interface and the `CardThumbnail`
component as the main touchpoints when applying the fix.
| interface PresetButtonProps { | ||
| as: "button"; | ||
| onClick: () => void; | ||
| type?: "button" | "submit" | "reset"; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the target file and locate related symbols/usages.
git ls-files packages/jds/src/components/Card/card.types.ts
echo '--- outline ---'
ast-grep outline packages/jds/src/components/Card/card.types.ts --view expanded || true
echo '--- usages ---'
rg -n "PresetButtonProps|CardOverlay|as=\"button\"|onClick:" packages/jds/src/components/Card -S || trueRepository: JECT-Study/JECT-Official-WebSite-Client
Length of output: 4696
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- Plate preset ---'
sed -n '1,220p' packages/jds/src/components/Card/presets/Plate.tsx
echo '--- Post preset ---'
sed -n '1,220p' packages/jds/src/components/Card/presets/Post.tsx
echo '--- CardOverlay ---'
sed -n '1,220p' packages/jds/src/components/Card/compound/CardOverlay.tsxRepository: JECT-Study/JECT-Official-WebSite-Client
Length of output: 5965
프리셋 버튼의 onClick 타입을 네이티브 버튼 시그니처로 맞춰주세요.
지금은 () => void라서 일반적인 React.MouseEvent<HTMLButtonElement> 핸들러를 넘길 수 없습니다. CardOverlay as="button"로 그대로 전달되는 만큼 ComponentPropsWithoutRef<"button">["onClick"]를 재사용하는 편이 맞습니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/jds/src/components/Card/card.types.ts` around lines 57 - 60, The
PresetButtonProps onClick type is too narrow and blocks standard button
handlers, so update the PresetButtonProps definition to reuse the native button
onClick signature from ComponentPropsWithoutRef<"button">["onClick"] instead of
() => void. Keep the existing as: "button" and type fields intact, and ensure
CardOverlay can pass its button handler through without a type mismatch.
| <span | ||
| ref={ref} | ||
| data-card-slot='caption' | ||
| className={clsx( | ||
| getLabelClassName({ size: "xs", weight: "subtle" }), | ||
| styles.caption, | ||
| className, | ||
| )} | ||
| {...restProps} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the component and the related CSS contract.
sed -n '1,120p' packages/jds/src/components/Card/compound/CardCaption.tsx
printf '\n--- CSS ---\n'
sed -n '1,180p' packages/jds/src/components/Card/compound/card.css.ts
printf '\n--- Search for other Card slot usages ---\n'
rg -n "data-card-slot|:has\\(" packages/jds/src/components/Card/compound -SRepository: JECT-Study/JECT-Official-WebSite-Client
Length of output: 6554
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,220p' packages/jds/src/components/Card/card.types.tsRepository: JECT-Study/JECT-Official-WebSite-Client
Length of output: 2395
data-card-slot을 소비자 props보다 뒤에서 고정하세요.
packages/jds/src/components/Card/compound/CardCaption.tsx에서 ...restProps가 마지막에 펼쳐져 같은 속성이 들어오면 card.css.ts의 :has([data-card-slot='caption']) 분기가 깨집니다. 내부 슬롯 식별자는 덮어써지지 않도록 순서를 바꾸는 게 안전합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/jds/src/components/Card/compound/CardCaption.tsx` around lines 15 -
23, `CardCaption`에서 내부 슬롯 식별자인 `data-card-slot="caption"`이 소비자 props에 의해 덮어써질 수
있으니, `CardCaption` 컴포넌트의 props 펼침 순서를 조정해 `...restProps`보다 `data-card-slot`이 뒤에서
고정되도록 변경하세요. `CardCaption`의 `<span>` 렌더링 부분에서 `data-card-slot`과 `restProps`의 순서를
바꿔, 외부에서 같은 속성을 넘겨도 `card.css.ts`의 `:has([data-card-slot='caption'])` 분기가 유지되게
하세요.
| const { href, ...anchorProps } = restProps as ComponentPropsWithoutRef<"a">; | ||
|
|
||
| return ( | ||
| <StyledCardOverlay | ||
| as={Component} | ||
| ref={ref} | ||
| <a | ||
| ref={ref as Ref<HTMLAnchorElement>} | ||
| className={overlayClassName} | ||
| {...anchorProps} | ||
| aria-labelledby={ariaLabelledby} | ||
| data-overlay | ||
| $variant={context.variant} | ||
| $cardStyle={context.cardStyle} | ||
| $isDisabled={context.isDisabled} | ||
| {...restProps} | ||
| data-disabled={isDisabled || undefined} | ||
| aria-disabled={isDisabled || undefined} | ||
| href={isDisabled ? undefined : href} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
비활성 링크 분기에서도 클릭 핸들러가 살아 있습니다.
Line 45-56은 href만 제거하고 onClick/tabIndex는 그대로 전달해서, isDisabled여도 소비자가 JS 액션이나 포커스를 다시 살릴 수 있습니다. 비활성 상태에서는 이 상호작용 props도 함께 차단해 주세요.
수정 예시
- const { href, ...anchorProps } = restProps as ComponentPropsWithoutRef<"a">;
+ const { href, onClick, tabIndex, ...anchorProps } =
+ restProps as ComponentPropsWithoutRef<"a">;
return (
<a
ref={ref as Ref<HTMLAnchorElement>}
className={overlayClassName}
{...anchorProps}
+ onClick={isDisabled ? undefined : onClick}
+ tabIndex={isDisabled ? -1 : tabIndex}
aria-labelledby={ariaLabelledby}
data-overlay
data-disabled={isDisabled || undefined}
aria-disabled={isDisabled || undefined}
href={isDisabled ? undefined : href}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { href, ...anchorProps } = restProps as ComponentPropsWithoutRef<"a">; | |
| return ( | |
| <StyledCardOverlay | |
| as={Component} | |
| ref={ref} | |
| <a | |
| ref={ref as Ref<HTMLAnchorElement>} | |
| className={overlayClassName} | |
| {...anchorProps} | |
| aria-labelledby={ariaLabelledby} | |
| data-overlay | |
| $variant={context.variant} | |
| $cardStyle={context.cardStyle} | |
| $isDisabled={context.isDisabled} | |
| {...restProps} | |
| data-disabled={isDisabled || undefined} | |
| aria-disabled={isDisabled || undefined} | |
| href={isDisabled ? undefined : href} | |
| const { href, onClick, tabIndex, ...anchorProps } = | |
| restProps as ComponentPropsWithoutRef<"a">; | |
| return ( | |
| <a | |
| ref={ref as Ref<HTMLAnchorElement>} | |
| className={overlayClassName} | |
| {...anchorProps} | |
| onClick={isDisabled ? undefined : onClick} | |
| tabIndex={isDisabled ? -1 : tabIndex} | |
| aria-labelledby={ariaLabelledby} | |
| data-overlay | |
| data-disabled={isDisabled || undefined} | |
| aria-disabled={isDisabled || undefined} | |
| href={isDisabled ? undefined : href} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/jds/src/components/Card/compound/CardOverlay.tsx` around lines 45 -
56, The disabled branch in CardOverlay still forwards interactive anchor props,
so the link can remain clickable or focusable even when isDisabled is true.
Update the CardOverlay anchor rendering to strip or override interaction props
from restProps/anchorProps when disabled, specifically onClick and tabIndex (and
any similar event-driven props you pass through), while keeping href undefined
and the disabled aria/data attributes intact. Use the existing CardOverlay
component and anchorProps handling as the place to centralize this
disabled-state filtering.
| <h3 | ||
| ref={ref} | ||
| id={titleId} | ||
| className={clsx(getTitleClassName({ size: "xs" }), styles.title, className)} | ||
| {...restProps} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate relevant files
git ls-files 'packages/jds/src/components/Card/**' | sed -n '1,200p'
echo '--- CardTitle.tsx ---'
sed -n '1,220p' packages/jds/src/components/Card/compound/CardTitle.tsx
echo '--- CardOverlay.tsx ---'
sed -n '1,260p' packages/jds/src/components/Card/compound/CardOverlay.tsx
echo '--- card.types.ts ---'
sed -n '1,260p' packages/jds/src/components/Card/card.types.tsRepository: JECT-Study/JECT-Official-WebSite-Client
Length of output: 6377
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for titleId / aria-labelledby wiring across Card components
rg -n "titleId|aria-labelledby|CardTitle|CardOverlay|Card.Root" packages/jds/src/components/Card -SRepository: JECT-Study/JECT-Official-WebSite-Client
Length of output: 6183
CardTitle에서 id가 덮이지 않게 하세요. ...restProps가 뒤에 와서 소비자 id가 titleId를 덮어쓰면 CardOverlay의 aria-labelledby가 끊어집니다. id 우선순위를 고정하거나 커스텀 id를 컨텍스트까지 함께 반영해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/jds/src/components/Card/compound/CardTitle.tsx` around lines 15 -
19, `CardTitle` is allowing `...restProps` to override the generated `titleId`,
which can break `CardOverlay`’s `aria-labelledby` linkage. Update the
`CardTitle` component so the `id` stays stable by giving `titleId` precedence
(or by explicitly supporting a custom `id` and propagating it through the shared
context used by `CardOverlay`). Use the `CardTitle` render path and its related
context/overlay wiring to keep the label reference consistent.
💡 작업 내용
Card.Image제거 후 내부 이미지를Thumbnail로 일원화Card.Preset.Plate하나로 단일화cardStyle제거,Card.Meta가로 배치)Card.Overlay접근성·타입 강화 (disabled 키보드 차단,as별 prop 강제)Plate/Post)로 분리💡 자세한 설명
이미지 일원화 —
Card.Image제거,Thumbnail로 통합Thumbnail과 역할이 겹치고 미사용 prop이 쌓여 있던Card.Image를 제거하고, 내부 이미지를Thumbnail로 일원화했습니다.Card.Thumbnail은 public으로 노출합니다.Plate preset 단일화
Figma에 없는 변형이던 WithTitle/WithLabel/Compact와 Card.Label을 제거하고,
Card.Preset.Plate하나로 통합했습니다.Post 정합
디자인에 맞춰 Post는 항상 empty 스타일로 렌더링하도록 cardStyle/CardStyle을 제거하고,
Card.Meta를 가로 배치로 변경했습니다.Card.Overlay접근성·타입 강화disabled가
pointer-events: none만 적용돼 키보드로 활성화되던 문제를 막고,as별 prop을 타입으로 강제했습니다.스타일·내부 정리
카드 전용 인라인 focus/hover 스타일을 공용
focusRing/overlay유틸로 교체하고,Card.ContentGroup승격, 타입 정리(CardSlotProps제네릭·named preset 변형),CardRootOwnProps→CardRootProps네이밍 통일 등 내부를 정리했습니다.Storybook 분리
단일
Card.stories.tsx를 variant별로Plate.stories.tsx/Post.stories.tsx로 분리해, 각 Figma 변형 table(PlateVariantMatrix/PostVariantMatrix)을 독립적으로 확인할 수 있게 했습니다.📗 참고 자료 (선택)
.changeset/card-migration.md— 전체 변경점 및 소비자 영향 (AS-IS → TO-BE)✅ 셀프 체크리스트
closes #474
Summary by CodeRabbit
New Features
Plate와Post프리셋을 사용할 수 있게 되었습니다.Bug Fixes