Skip to content

Commit aaadde1

Browse files
committed
chore: Card 컴포넌트 내 주석 추가
1 parent 837b836 commit aaadde1

1 file changed

Lines changed: 121 additions & 10 deletions

File tree

packages/react/src/card/Card.tsx

Lines changed: 121 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,28 @@ import { composeEventHandlers } from "../utils/composeEventHandlers";
44
import { useAriaIds, useAriaPress } from "@acme/react-a11y";
55
import { createContext } from "../context/createContext";
66

7+
/**
8+
* Card 컴포넌트의 인터랙션 모드를 정의합니다.
9+
* - "none": 정적 카드 (클릭 불가)
10+
* - "button": 버튼처럼 동작하는 카드 (키보드/마우스 지원)
11+
* - "link": 링크처럼 동작하는 카드 (키보드/마우스 지원)
12+
*/
713
type CardAction = "none" | "button" | "link";
814

15+
/**
16+
* 다형성 컴포넌트를 위한 props 타입
17+
* - as: 렌더링할 HTML 요소나 React 컴포넌트 지정
18+
* - asChild: true일 때 자식 요소를 직접 렌더링 (Slot 패턴)
19+
*/
920
type PolymorphicProp<C extends React.ElementType> = {
1021
as?: C;
1122
asChild?: boolean;
1223
};
1324

25+
/**
26+
* Card 컴포넌트의 기본 props 타입
27+
* 인터랙션, 접근성, 이벤트 핸들링 관련 속성들을 포함합니다.
28+
*/
1429
type CardBaseProps = {
1530
/** 카드가 인터랙티브(클릭/키보드 활성)한지 */
1631
action?: CardAction;
@@ -30,20 +45,42 @@ type CardBaseProps = {
3045
externalHandlersFirst?: boolean;
3146
} & React.HTMLAttributes<HTMLElement>;
3247

48+
/**
49+
* Card 컴포넌트의 전체 props 타입
50+
* 다형성 지원과 함께 HTML 속성들을 상속받습니다.
51+
*/
3352
export type CardProps<C extends React.ElementType = React.ElementType> =
3453
React.PropsWithChildren<CardBaseProps & PolymorphicProp<C>> &
3554
Omit<
3655
React.ComponentPropsWithoutRef<C>,
3756
keyof CardBaseProps | "children" | "as" | "asChild"
3857
>;
3958

59+
/**
60+
* Card Context에서 공유되는 값들
61+
* 접근성을 위한 ID들을 하위 컴포넌트들과 공유합니다.
62+
*/
4063
type CardContextValue = {
4164
titleId: string;
4265
descId: string;
4366
};
4467

68+
/**
69+
* Card 컴포넌트의 Context 생성
70+
* 향상된 createContext 유틸리티를 사용하여 타입 안전성과 성능을 개선합니다.
71+
*/
4572
const [CardProvider, useCardContext] = createContext<CardContextValue>("Card");
4673

74+
/**
75+
* Card 컴포넌트의 루트 요소
76+
*
77+
* 주요 기능:
78+
* - 다형성 지원 (as, asChild props)
79+
* - 인터랙션 모드 지원 (none, button, link)
80+
* - 접근성 자동 처리 (ARIA 속성, 키보드 네비게이션)
81+
* - 이벤트 핸들러 조합 (내부/외부 핸들러 통합)
82+
* - Context를 통한 ID 공유
83+
*/
4784
export const CardRoot = React.forwardRef<any, CardProps>(
4885
(
4986
{
@@ -63,7 +100,8 @@ export const CardRoot = React.forwardRef<any, CardProps>(
63100
},
64101
ref
65102
) => {
66-
// asChild일 때 자식이 하나인지 검증
103+
// asChild 모드일 때 자식 요소 검증
104+
// Slot 패턴을 사용할 때는 정확히 하나의 React 요소가 필요합니다
67105
if (asChild) {
68106
if (!React.isValidElement(children)) {
69107
throw new Error(
@@ -72,44 +110,58 @@ export const CardRoot = React.forwardRef<any, CardProps>(
72110
}
73111
}
74112

113+
// 렌더링할 요소 타입 결정
114+
// asChild가 true면 Slot 컴포넌트를, 아니면 지정된 요소나 기본 div를 사용
75115
const elementType = asChild ? Slot : (as ?? "div");
116+
117+
// 접근성을 위한 고유 ID 생성
118+
// 외부에서 제공된 ID가 있으면 우선 사용, 없으면 자동 생성
76119
const { label: autoTitleId, desc: autoDescId } = useAriaIds("card");
77120
const titleId = ariaLabelledbyProp || autoTitleId;
78121
const descId = ariaDescribedbyProp || autoDescId;
79122

80-
// a11y press (button 모드일 때 키보드/마우스 일원화)
123+
// 접근성 지원을 위한 키보드/마우스 이벤트 통합
124+
// useAriaPress 훅을 사용하여 키보드와 마우스 이벤트를 일관되게 처리
81125
const press = useAriaPress({
82126
disabled: disabled || action === "none",
83127
onPress: onPress ? (t) => onPress(t) : undefined,
84128
});
85129

86-
// 역할/ARIA 계산
130+
// ARIA 역할 및 인터랙션 상태 계산
131+
// action prop에 따라 적절한 ARIA 역할을 자동 설정
87132
const isButton = action === "button";
88133
const isLink = action === "link";
89134
const role =
90135
rest.role ?? (isButton ? "button" : isLink ? "link" : undefined);
91136

92-
// Card 컴포넌트의 내부 기본 동작
137+
// Card 컴포넌트의 내부 기본 동작 정의
138+
// 인터랙티브 모드일 때만 내부 핸들러를 생성 (포커스 관리, 애니메이션 등)
93139
const internalOnClick =
94140
isButton || isLink
95141
? (e: React.MouseEvent) => {
96142
// Card의 기본 클릭 동작 (예: 포커스 관리, 애니메이션 등)
143+
// 향후 확장 가능한 내부 로직
97144
}
98145
: undefined;
99146

100147
const internalOnKeyDown =
101148
isButton || isLink
102149
? (e: React.KeyboardEvent) => {
103150
// Card의 기본 키보드 동작
151+
// 향후 확장 가능한 내부 로직
104152
}
105153
: undefined;
106154

107-
// 외부 onPress를 처리하는 핸들러
155+
// 외부 onPress 이벤트를 처리하는 핸들러
156+
// useAriaPress를 다시 호출하여 외부 핸들러와 내부 핸들러를 분리
108157
const pressHandlers = useAriaPress({
109158
disabled: disabled || action === "none",
110159
onPress: onPress ? (t) => onPress(t) : undefined,
111160
});
112161

162+
// 이벤트 핸들러 조합
163+
// 내부 핸들러와 외부 핸들러를 composeEventHandlers로 통합
164+
// externalHandlersFirst 옵션으로 실행 순서 제어 가능
113165
const handleClick = composeEventHandlers(
114166
internalOnClick,
115167
pressHandlers.onClick,
@@ -121,35 +173,48 @@ export const CardRoot = React.forwardRef<any, CardProps>(
121173
{ externalFirst: !!externalHandlersFirst }
122174
);
123175

124-
// 네이티브 button 기본 submit 방지
176+
// 네이티브 button 요소의 기본 submit 동작 방지
177+
// button 요소로 렌더링될 때 type="button"을 명시적으로 설정
125178
const typeProp =
126179
elementType === "button" && isButton ? { type: "button" } : {};
127180

181+
// Context Provider로 ID들을 하위 컴포넌트들과 공유
182+
// CardTitle, CardDescription 등이 이 ID들을 사용하여 접근성 연결
128183
return (
129184
<CardProvider titleId={titleId} descId={descId}>
130185
{React.createElement(
131186
elementType,
132187
{
188+
// 기본 HTML 속성들 전달
133189
...rest,
190+
// 인터랙티브 모드일 때만 press 관련 속성들 추가
134191
...(isButton || isLink ? press : {}),
135192
ref,
193+
// button 요소의 기본 submit 방지
136194
...typeProp,
195+
// ARIA 역할 설정
137196
role,
197+
// 접근성 속성들
138198
"aria-disabled": disabled || undefined,
139199
"aria-pressed":
140200
isButton && typeof pressed === "boolean" ? pressed : undefined,
141201
"aria-labelledby": titleId,
142202
"aria-describedby": descId,
203+
// 키보드 네비게이션을 위한 tabIndex 설정
143204
tabIndex:
144205
rest.tabIndex ??
145206
(isButton || isLink ? press.tabIndex : undefined),
207+
// 조합된 이벤트 핸들러들
146208
onClick: handleClick,
147209
onKeyDown: handleKeyDown,
210+
// 포커스 표시를 위한 데이터 속성
148211
"data-focus-visible": "",
149-
// asChild일 때는 기본 스타일을 적용하지 않음 (레이아웃 깨짐 방지)
212+
// 조건부 스타일 적용
213+
// asChild일 때는 display: block 추가 (레이아웃 깨짐 방지)
214+
// 일반 모드일 때는 기본 스타일만 적용
150215
style: asChild
151216
? {
152-
display: "block",
217+
display: "block", // asChild일 때만 추가
153218
outline: "none",
154219
borderRadius: "var(--ds-radius-2, 12px)",
155220
background: "var(--ds-semantic-color-bg-layer-default)",
@@ -180,7 +245,14 @@ export const CardRoot = React.forwardRef<any, CardProps>(
180245
);
181246
CardRoot.displayName = "Card.Root";
182247

183-
// 슬롯들: aria-labelledby / describedby에 연결
248+
/* -------------------------------------------------------------------------------------------------
249+
* Card 하위 컴포넌트들
250+
* -----------------------------------------------------------------------------------------------*/
251+
252+
/**
253+
* Card의 헤더 영역
254+
* 다형성 지원으로 다양한 요소로 렌더링 가능
255+
*/
184256
export const CardHeader = React.forwardRef<
185257
HTMLElement,
186258
React.HTMLAttributes<HTMLElement> & PolymorphicProp<any>
@@ -190,6 +262,10 @@ export const CardHeader = React.forwardRef<
190262
});
191263
CardHeader.displayName = "Card.Header";
192264

265+
/**
266+
* Card의 미디어 영역 (이미지, 비디오 등)
267+
* 다형성 지원으로 다양한 요소로 렌더링 가능
268+
*/
193269
export const CardMedia = React.forwardRef<
194270
HTMLElement,
195271
React.HTMLAttributes<HTMLElement> & PolymorphicProp<any>
@@ -199,6 +275,11 @@ export const CardMedia = React.forwardRef<
199275
});
200276
CardMedia.displayName = "Card.Media";
201277

278+
/**
279+
* Card의 제목
280+
* Context에서 제공되는 titleId를 자동으로 연결하여 접근성 지원
281+
* Card.Root의 aria-labelledby와 자동 연결됨
282+
*/
202283
export const CardTitle = React.forwardRef<
203284
HTMLElement,
204285
React.HTMLAttributes<HTMLElement> & PolymorphicProp<any>
@@ -209,6 +290,11 @@ export const CardTitle = React.forwardRef<
209290
});
210291
CardTitle.displayName = "Card.Title";
211292

293+
/**
294+
* Card의 설명
295+
* Context에서 제공되는 descId를 자동으로 연결하여 접근성 지원
296+
* Card.Root의 aria-describedby와 자동 연결됨
297+
*/
212298
export const CardDescription = React.forwardRef<
213299
HTMLElement,
214300
React.HTMLAttributes<HTMLElement> & PolymorphicProp<any>
@@ -219,6 +305,10 @@ export const CardDescription = React.forwardRef<
219305
});
220306
CardDescription.displayName = "Card.Description";
221307

308+
/**
309+
* Card의 본문 영역
310+
* 다형성 지원으로 다양한 요소로 렌더링 가능
311+
*/
222312
export const CardBody = React.forwardRef<
223313
HTMLElement,
224314
React.HTMLAttributes<HTMLElement> & PolymorphicProp<any>
@@ -228,6 +318,10 @@ export const CardBody = React.forwardRef<
228318
});
229319
CardBody.displayName = "Card.Body";
230320

321+
/**
322+
* Card의 푸터 영역
323+
* 다형성 지원으로 다양한 요소로 렌더링 가능
324+
*/
231325
export const CardFooter = React.forwardRef<
232326
HTMLElement,
233327
React.HTMLAttributes<HTMLElement> & PolymorphicProp<any>
@@ -237,7 +331,24 @@ export const CardFooter = React.forwardRef<
237331
});
238332
CardFooter.displayName = "Card.Footer";
239333

240-
// 네임스페이스 export (선호 시)
334+
/* -------------------------------------------------------------------------------------------------
335+
* 네임스페이스 export
336+
* -----------------------------------------------------------------------------------------------*/
337+
338+
/**
339+
* Card 컴포넌트의 네임스페이스 export
340+
*
341+
* 사용법:
342+
* ```tsx
343+
* <Card.Root>
344+
* <Card.Header>
345+
* <Card.Title>제목</Card.Title>
346+
* </Card.Header>
347+
* <Card.Body>내용</Card.Body>
348+
* <Card.Footer>푸터</Card.Footer>
349+
* </Card.Root>
350+
* ```
351+
*/
241352
export const Card = Object.assign(CardRoot, {
242353
Root: CardRoot,
243354
Header: CardHeader,

0 commit comments

Comments
 (0)