@uniai-fe/uds-primitives 0.2.11 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/styles.css +1118 -890
  2. package/package.json +1 -1
  3. package/src/components/button/index.tsx +1 -1
  4. package/src/components/button/markup/Base.tsx +1 -1
  5. package/src/components/button/styles/button.scss +32 -0
  6. package/src/components/button/styles/variables.scss +16 -0
  7. package/src/components/button/types/index.ts +3 -179
  8. package/src/components/button/types/{constants.ts → options.ts} +28 -13
  9. package/src/components/button/types/props.ts +264 -0
  10. package/src/components/chip/markup/Chip.tsx +14 -2
  11. package/src/components/chip/styles/chip.scss +154 -0
  12. package/src/components/chip/styles/index.scss +2 -138
  13. package/src/components/chip/styles/variables.scss +26 -0
  14. package/src/components/chip/types/index.ts +2 -52
  15. package/src/components/chip/types/options.ts +38 -0
  16. package/src/components/chip/types/props.ts +115 -0
  17. package/src/components/chip/utils/class-name.ts +36 -0
  18. package/src/components/chip/utils/index.ts +1 -36
  19. package/src/components/form/index.tsx +3 -16
  20. package/src/components/form/markup/form-field/index.tsx +1 -0
  21. package/src/components/form/markup/index.tsx +11 -0
  22. package/src/components/form/styles/index.scss +2 -2
  23. package/src/components/form/utils/index.ts +1 -0
  24. package/src/components/input/markup/foundation/Button.tsx +1 -6
  25. package/src/components/input/markup/text/AuthCode.tsx +3 -1
  26. package/src/components/input/markup/text/Email.tsx +16 -11
  27. package/src/components/input/markup/text/Password.tsx +3 -3
  28. package/src/components/input/markup/text/Phone.tsx +30 -28
  29. package/src/components/input/markup/text/Search.tsx +3 -1
  30. package/src/components/input/types/text.ts +10 -5
  31. package/src/components/input/utils/index.tsx +0 -1
  32. package/src/components/table/markup/Container.tsx +36 -70
  33. package/src/components/table/markup/foundation/Cell.tsx +4 -6
  34. package/src/components/table/styles/foundation.scss +75 -9
  35. package/src/components/table/types/foundation.ts +62 -59
  36. package/src/index.scss +7 -6
  37. package/src/index.tsx +19 -22
  38. package/src/components/button/types/templates.ts +0 -84
  39. package/src/components/input/utils/verification.tsx +0 -35
@@ -0,0 +1,11 @@
1
+ import FormProvider from "./Provider";
2
+ import { FormField } from "./form-field";
3
+
4
+ /**
5
+ * Form namespace 조립 전용 파일.
6
+ * - index.tsx 배럴 파일에서 로직/객체 생성을 분리한다.
7
+ */
8
+ export const Form = {
9
+ Provider: FormProvider,
10
+ Field: FormField,
11
+ };
@@ -1,2 +1,2 @@
1
- @use "./form-field/variables.scss";
2
- @use "./form-field/layout.scss";
1
+ @use "./form-field/variables.scss" as form-field-variables;
2
+ @use "./form-field/layout.scss" as form-field-layout;
@@ -0,0 +1 @@
1
+ export * from "./form-field";
@@ -1,7 +1,6 @@
1
1
  "use client";
2
2
 
3
3
  import { Button } from "../../../button";
4
- import type { ButtonProps } from "../../../button/types";
5
4
  import type { InputUtilityButtonProps } from "../../types";
6
5
 
7
6
  /**
@@ -19,17 +18,13 @@ export default function InputBaseUtilityButton({
19
18
  disabled,
20
19
  onClick,
21
20
  }: InputUtilityButtonProps) {
22
- const handleButtonClick: ButtonProps["onClick"] = event => {
23
- onClick?.(event);
24
- };
25
-
26
21
  return (
27
22
  <Button.Default
28
23
  fill="outlined"
29
24
  size="small"
30
25
  priority={priority}
31
26
  className="input-utility-button"
32
- onClick={handleButtonClick}
27
+ onClick={event => onClick?.(event)}
33
28
  disabled={disabled}
34
29
  >
35
30
  {children}
@@ -22,7 +22,7 @@ const DEFAULT_PLACEHOLDER = "인증코드 입력";
22
22
  const DEFAULT_COUNTDOWN_ACTION_LABEL = "시간연장";
23
23
 
24
24
  /**
25
- * AuthCodeInput — Text Input priority secondary 스타일로 구성된 인증번호 입력.
25
+ * Input Preset; AuthCode 카운트다운 입력
26
26
  * @component
27
27
  * @param {AuthCodeInputProps} props 인증코드 입력 props
28
28
  * @property {string} [props.value] 제어형 인증코드 값
@@ -36,6 +36,8 @@ const DEFAULT_COUNTDOWN_ACTION_LABEL = "시간연장";
36
36
  * @property {ReactNode} [props.countdownActionLabel="시간연장"] 제한 시간 연장 버튼 라벨
37
37
  * @property {InputUtilityButtonClickHandler} [props.onCountdownAction] 연장 버튼 클릭 핸들러
38
38
  * @property {boolean} [props.countdownActionDisabled] 연장 버튼 disabled 여부
39
+ * @example
40
+ * <AuthCodeInput length={6} onComplete={code => console.log(code)} />
39
41
  */
40
42
  const AuthCodeInput = forwardRef<HTMLInputElement, AuthCodeInputProps>(
41
43
  (
@@ -5,10 +5,10 @@ import { forwardRef, useCallback } from "react";
5
5
 
6
6
  import type { EmailInputProps } from "../../types";
7
7
  import InputBase from "../foundation/Input";
8
- import { renderVerificationRequestButton } from "../../utils/verification";
8
+ import InputBaseUtilityButton from "../foundation/Button";
9
9
 
10
10
  /**
11
- * EmailInput 이메일 입력과 인증요청 버튼만 제공한다.
11
+ * Input Preset; Email 인증요청 조합 입력
12
12
  * @component
13
13
  * @param {EmailInputProps} props 이메일 입력 props
14
14
  * @property {string} [props.value] 제어형 이메일 값
@@ -19,6 +19,8 @@ import { renderVerificationRequestButton } from "../../utils/verification";
19
19
  * @property {ReactNode} [props.requestButtonLabel] 버튼 라벨
20
20
  * @property {boolean} [props.requestButtonDisabled] 버튼 disabled 상태
21
21
  * @property {ReactNode} [props.right] 기본 오른쪽 슬롯 override
22
+ * @example
23
+ * <EmailInput onRequestCode={() => {}} />
22
24
  */
23
25
  const EmailInput = forwardRef<HTMLInputElement, EmailInputProps>(
24
26
  (
@@ -35,7 +37,6 @@ const EmailInput = forwardRef<HTMLInputElement, EmailInputProps>(
35
37
  },
36
38
  forwardedRef,
37
39
  ) => {
38
- // 이메일 값 변경 시 상위 상태(onValueChange)와 native onChange를 모두 호출한다.
39
40
  const handleChange = useCallback(
40
41
  (event: ChangeEvent<HTMLInputElement>) => {
41
42
  onValueChange?.(event.currentTarget.value);
@@ -44,13 +45,6 @@ const EmailInput = forwardRef<HTMLInputElement, EmailInputProps>(
44
45
  [onChange, onValueChange],
45
46
  );
46
47
 
47
- // 인증 요청 옵션이 있을 때만 우측 액션 버튼을 만든다.
48
- const actionButton = renderVerificationRequestButton({
49
- onRequestCode,
50
- requestButtonDisabled,
51
- requestButtonLabel,
52
- });
53
-
54
48
  return (
55
49
  <div className="email-verification">
56
50
  <InputBase
@@ -61,7 +55,18 @@ const EmailInput = forwardRef<HTMLInputElement, EmailInputProps>(
61
55
  value={value}
62
56
  defaultValue={defaultValue}
63
57
  onChange={handleChange}
64
- right={right ?? actionButton}
58
+ right={
59
+ right ??
60
+ (onRequestCode ? (
61
+ <InputBaseUtilityButton
62
+ priority="primary"
63
+ onClick={onRequestCode}
64
+ disabled={requestButtonDisabled}
65
+ >
66
+ {requestButtonLabel}
67
+ </InputBaseUtilityButton>
68
+ ) : null)
69
+ }
65
70
  />
66
71
  </div>
67
72
  );
@@ -7,13 +7,15 @@ import HideOffIcon from "../../img/hide-off.svg";
7
7
  import HideOnIcon from "../../img/hide-on.svg";
8
8
 
9
9
  /**
10
- * PasswordInput 기본 Text 입력을 비밀번호 토글 UX로 확장한다.
10
+ * Input Preset; Password 표시 전환 입력
11
11
  * @component
12
12
  * @param {InputPasswordProps} props
13
13
  * @property {boolean} [props.defaultVisible=false] 초기 노출 여부
14
14
  * @property {{show: string; hide: string}} [props.toggleLabel] 토글 버튼 라벨
15
15
  * @property {ReactNode} [props.right] 오른쪽 슬롯 override
16
16
  * @property {string | number | readonly string[]} [props.defaultValue] 초기값
17
+ * @example
18
+ * <PasswordInput defaultVisible={false} />
17
19
  */
18
20
  const PasswordInput = forwardRef<HTMLInputElement, InputPasswordProps>(
19
21
  (
@@ -36,13 +38,11 @@ const PasswordInput = forwardRef<HTMLInputElement, InputPasswordProps>(
36
38
  <button
37
39
  type="button"
38
40
  className="input-password-toggle"
39
- // Tab 이동 시 다음 입력 필드로 바로 넘어가도록 토글 버튼은 순서에서 제외한다.
40
41
  tabIndex={-1}
41
42
  onClick={handleToggle}
42
43
  aria-pressed={visible}
43
44
  aria-label={visible ? toggleLabel.hide : toggleLabel.show}
44
45
  >
45
- {/* 토글 상태에 따라 hide-on/off 아이콘을 교체한다. */}
46
46
  {visible ? (
47
47
  <HideOffIcon aria-hidden="true" />
48
48
  ) : (
@@ -5,8 +5,8 @@ import type { ChangeEvent } from "react";
5
5
  import { forwardRef, useCallback, useMemo } from "react";
6
6
  import type { PhoneInputProps } from "../../types";
7
7
  import InputBase from "../foundation/Input";
8
- import { renderVerificationRequestButton } from "../../utils/verification";
9
8
  import { useDigitField } from "../../hooks/useDigitField";
9
+ import InputBaseUtilityButton from "../foundation/Button";
10
10
 
11
11
  /**
12
12
  * 휴대폰 입력에서 허용할 숫자 자리수. 국내 번호 11자 기준.
@@ -14,22 +14,20 @@ import { useDigitField } from "../../hooks/useDigitField";
14
14
  const MAX_DIGITS = 11;
15
15
 
16
16
  /**
17
- * util-functions의 maskPhone 규칙으로 자리수/패턴을 일원화한다.
18
- */
19
- const formatPhoneNumber = (digits: string) => maskPhone(digits);
20
-
21
- /**
22
- * 휴대폰 번호 입력 컴포넌트; 마스킹 처리와 인증요청 버튼까지만 담당한다.
17
+ * Input Preset; Phone 인증요청 조합 입력
23
18
  * @component
24
19
  * @param {PhoneInputProps} props 휴대폰 입력 props
25
- * @param {string} [props.value] 제어형 포맷 값
26
- * @param {string} [props.defaultValue] 비제어 초기값
27
- * @param {(value: string, digits: string) => void} [props.onValueChange] 포맷/숫자 변경 콜백
28
- * @param {(event: ChangeEvent<HTMLInputElement>) => void} [props.onChange] native onChange override
29
- * @param {InputUtilityButtonClickHandler} [props.onRequestCode] 인증 요청 버튼 클릭 시 호출
30
- * @param {ReactNode} [props.requestButtonLabel] 인증 요청 버튼 라벨
31
- * @param {boolean} [props.requestButtonDisabled] 인증 요청 버튼 disabled
32
- * @param {ReactNode} [props.right] 기본 오른쪽 슬롯 override
20
+ * @property {string} [props.value] 제어형 포맷 값
21
+ * @property {string} [props.defaultValue] 비제어 초기값
22
+ * @property {(value: string, digits: string) => void} [props.onValueChange] 포맷/숫자 변경 콜백
23
+ * @property {(event: ChangeEvent<HTMLInputElement>) => void} [props.onChange] native onChange override
24
+ * @property {InputUtilityButtonClickHandler} [props.onRequestCode] 인증 요청 버튼 클릭 시 호출
25
+ * @property {ReactNode} [props.requestButtonLabel] 인증 요청 버튼 라벨
26
+ * @property {boolean} [props.requestButtonDisabled] 인증 요청 버튼 disabled
27
+ * @property {string} [props.placeholder] 전화번호 placeholder 텍스트
28
+ * @property {ReactNode} [props.right] 기본 오른쪽 슬롯 override
29
+ * @example
30
+ * <PhoneInput onRequestCode={() => {}} />
33
31
  */
34
32
  const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(
35
33
  (
@@ -41,50 +39,54 @@ const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(
41
39
  onRequestCode,
42
40
  requestButtonLabel = "인증번호 요청",
43
41
  requestButtonDisabled,
42
+ // 기본 placeholder를 유지하되 호출자가 값을 전달하면 그대로 우선 적용한다.
43
+ placeholder = "000-0000-0000",
44
44
  right,
45
45
  ...restProps
46
46
  },
47
47
  forwardedRef,
48
48
  ) => {
49
- // 제어/비제어 숫자 입력을 통합 관리한다.
50
49
  const { digits, handleDigitsChange } = useDigitField({
51
50
  value,
52
51
  defaultValue,
53
52
  maxLength: MAX_DIGITS,
54
53
  });
55
- const formattedValue = useMemo(() => formatPhoneNumber(digits), [digits]);
54
+ const formattedValue = useMemo(() => maskPhone(digits), [digits]);
56
55
 
57
56
  const handleChange = useCallback(
58
57
  (event: ChangeEvent<HTMLInputElement>) => {
59
58
  const digitsOnly = handleDigitsChange(event);
60
- const formatted = formatPhoneNumber(digitsOnly);
59
+ const formatted = maskPhone(digitsOnly);
61
60
  if (event.currentTarget.value !== formatted) {
62
61
  event.currentTarget.value = formatted;
63
62
  }
64
- // 포맷팅된 값과 숫자만으로 분리된 값을 모두 호출자에게 전달한다.
65
63
  onValueChange?.(formatted, digitsOnly);
66
64
  onChange?.(event);
67
65
  },
68
66
  [handleDigitsChange, onChange, onValueChange],
69
67
  );
70
68
 
71
- const actionButton = renderVerificationRequestButton({
72
- onRequestCode,
73
- requestButtonDisabled,
74
- requestButtonLabel,
75
- });
76
-
77
- // 기본 전화 입력 필드; 인증 UI 노출 여부에 따라 래핑된다.
78
69
  const phoneField = (
79
70
  <InputBase
80
71
  {...restProps}
81
72
  ref={forwardedRef}
82
73
  type="tel"
83
74
  inputMode="tel"
84
- placeholder="000-0000-0000"
75
+ placeholder={placeholder}
85
76
  value={formattedValue}
86
77
  onChange={handleChange}
87
- right={right ?? actionButton}
78
+ right={
79
+ right ??
80
+ (onRequestCode ? (
81
+ <InputBaseUtilityButton
82
+ priority="primary"
83
+ onClick={onRequestCode}
84
+ disabled={requestButtonDisabled}
85
+ >
86
+ {requestButtonLabel}
87
+ </InputBaseUtilityButton>
88
+ ) : null)
89
+ }
88
90
  />
89
91
  );
90
92
 
@@ -4,11 +4,13 @@ import InputBase from "../foundation/Input";
4
4
  import SearchIcon from "../../img/search.svg";
5
5
 
6
6
  /**
7
- * SearchInput 검색 아이콘(left slot)과 type="search"를 적용한 Text 입력.
7
+ * Input Preset; Search 아이콘 고정 입력
8
8
  * @component
9
9
  * @param {SearchInputProps} props
10
10
  * @property {ReactNode} [props.left] left 슬롯 override
11
11
  * @property {ReactNode} [props.right] right 슬롯 override
12
+ * @example
13
+ * <SearchInput placeholder="검색어 입력" />
12
14
  */
13
15
  const SearchInput = forwardRef<HTMLInputElement, SearchInputProps>(
14
16
  ({ left, ...restProps }, forwardedRef) => {
@@ -1,4 +1,4 @@
1
- import type { ComponentPropsWithoutRef, MouseEvent, ReactNode } from "react";
1
+ import type { ChangeEvent, MouseEvent, ReactNode } from "react";
2
2
  import type { ButtonPriority } from "../../button/types";
3
3
  import type { InputProps } from "./foundation";
4
4
 
@@ -64,7 +64,7 @@ export interface InputPasswordProps extends Omit<InputProps, "type"> {
64
64
  * @property {string} [value] 제어형 값
65
65
  * @property {string} [defaultValue] 비제어 초기값
66
66
  * @property {(value:string)=>void} [onValueChange] 값 변경 시 호출
67
- * @property {ComponentPropsWithoutRef<"input">["onChange"]} [onChange] native onChange override
67
+ * @property {(event: ChangeEvent<HTMLInputElement>) => void} [onChange] native onChange override
68
68
  * @property {number} [length] 자리수 제한
69
69
  * @property {(code:string)=>void} [onComplete] 입력 완료 콜백
70
70
  * @property {string} [placeholder] placeholder 텍스트
@@ -92,7 +92,7 @@ export interface AuthCodeInputProps extends Omit<
92
92
  /**
93
93
  * native onChange override
94
94
  */
95
- onChange?: ComponentPropsWithoutRef<"input">["onChange"];
95
+ onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
96
96
  /**
97
97
  * 자리수 제한
98
98
  */
@@ -148,7 +148,7 @@ export interface EmailInputProps extends Omit<
148
148
  /**
149
149
  * native onChange override
150
150
  */
151
- onChange?: ComponentPropsWithoutRef<"input">["onChange"];
151
+ onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
152
152
  /**
153
153
  * 인증 요청 클릭 핸들러
154
154
  */
@@ -166,6 +166,7 @@ export interface EmailInputProps extends Omit<
166
166
  /**
167
167
  * Phone Input props; 전화번호 포맷팅과 인증요청 버튼만 담당한다.
168
168
  * @property {(value:string,digits:string)=>void} [onValueChange] 포맷/숫자 콜백
169
+ * @property {string} [placeholder] 전화번호 placeholder 텍스트
169
170
  * 나머지 props는 InputProps 기반이며 type 관련 속성을 제외한다.
170
171
  */
171
172
  export interface PhoneInputProps extends Omit<
@@ -187,7 +188,7 @@ export interface PhoneInputProps extends Omit<
187
188
  /**
188
189
  * native onChange override
189
190
  */
190
- onChange?: ComponentPropsWithoutRef<"input">["onChange"];
191
+ onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
191
192
  /**
192
193
  * 인증 요청 핸들러
193
194
  */
@@ -200,4 +201,8 @@ export interface PhoneInputProps extends Omit<
200
201
  * 인증 요청 disabled
201
202
  */
202
203
  requestButtonDisabled?: boolean;
204
+ /**
205
+ * 전화번호 placeholder 텍스트
206
+ */
207
+ placeholder?: string;
203
208
  }
@@ -1,4 +1,3 @@
1
- export * from "./verification";
2
1
  export * from "./date";
3
2
  export * from "./address";
4
3
  export * from "./file";
@@ -1,5 +1,5 @@
1
1
  import { forwardRef } from "react";
2
- import type { TableTemplateColumn, TableTemplateProps } from "../types";
2
+ import type { TableContainerProps } from "../types";
3
3
  import TableBody from "./foundation/Body";
4
4
  import TableCell from "./foundation/Cell";
5
5
  import TableHead from "./foundation/Head";
@@ -7,33 +7,12 @@ import TableTh from "./foundation/Th";
7
7
  import TableRoot from "./foundation/Root";
8
8
  import TableRow from "./foundation/Row";
9
9
 
10
- const resolveHeadContent = (column: TableTemplateColumn): React.ReactNode => {
11
- if (typeof column.cellContents !== "undefined") {
12
- return column.cellContents;
13
- }
14
-
15
- if (typeof column.cellChildren !== "undefined") {
16
- return column.cellChildren;
17
- }
18
-
19
- if (typeof column.headContent !== "undefined") {
20
- return column.headContent;
21
- }
22
-
23
- if (typeof column.dataName !== "undefined") {
24
- return column.dataName;
25
- }
26
-
27
- return column.dataKey;
28
- };
29
-
30
10
  /**
31
11
  * Table Preset; 기본 Container 조합 컴포넌트
32
12
  * @component
33
- * @param {TableTemplateProps} props
34
- * @param {TableTemplateColumn[]} [props.columns] colgroup/head 자동 렌더링 column 목록
13
+ * @param {TableContainerProps} props
14
+ * @param {TableColumnData[]} [props.columns] colgroup/head 자동 렌더링 column 목록
35
15
  * @param {boolean} [props.isCustomBody=false] true면 body wrapper 없이 children 직접 렌더링
36
- * @param {"legacy-rem" | "raw"} [props.widthMode="legacy-rem"] number width 해석 방식
37
16
  * @param {React.ReactNode} [props.footer] footer 노드. `<Table.Foot />`를 직접 전달하는 방식을 권장
38
17
  * @param {React.ReactNode} [props.children] table body 콘텐츠
39
18
  * @example
@@ -52,63 +31,50 @@ const resolveHeadContent = (column: TableTemplateColumn): React.ReactNode => {
52
31
  * {rows}
53
32
  * </Table.Container>
54
33
  */
55
- const TableContainer = forwardRef<HTMLTableElement, TableTemplateProps>(
56
- (
57
- {
58
- columns,
59
- isCustomBody = false,
60
- widthMode = "legacy-rem",
61
- footer,
62
- children,
63
- ...tableProps
64
- },
65
- ref,
66
- ) => {
67
- const hasColumns = Array.isArray(columns) && columns.length > 0;
34
+ const TableContainer = forwardRef<HTMLTableElement, TableContainerProps>(
35
+ ({ columns, isCustomBody = false, footer, children, ...tableProps }, ref) => {
36
+ // 변경: optional columns를 안전하게 순회하기 위한 기본 배열을 고정한다.
37
+ const resolvedColumns = columns ?? [];
38
+ // 변경: columns 존재 여부만 단일 플래그로 관리해 렌더 분기 가독성을 유지한다.
39
+ const hasColumns = resolvedColumns.length > 0;
68
40
 
69
41
  return (
70
42
  <TableRoot {...tableProps} ref={ref}>
71
43
  {hasColumns && (
72
44
  <colgroup>
73
- {columns.map(({ key, width, dataKey, className, span }) => {
74
- const normalizedWidth =
75
- typeof width === "number"
76
- ? widthMode === "legacy-rem"
77
- ? `${width / 10}rem`
78
- : width
79
- : width;
80
-
81
- return (
82
- <col
83
- key={`${key}/colgroup`}
84
- className={className}
85
- data-key={dataKey}
86
- span={span}
87
- style={
88
- normalizedWidth
89
- ? {
90
- width: normalizedWidth,
91
- }
92
- : undefined
93
- }
94
- width={typeof width === "number" ? width : undefined}
95
- />
96
- );
97
- })}
45
+ {resolvedColumns.map(({ key, width, dataKey, className }) => (
46
+ <col
47
+ key={`${key}/colgroup`}
48
+ className={className}
49
+ data-key={dataKey}
50
+ style={
51
+ typeof width !== "undefined"
52
+ ? {
53
+ // 변경: width는 number|string 값을 그대로 사용하고, 단위 해석은 사용처에서 결정한다.
54
+ width,
55
+ }
56
+ : undefined
57
+ }
58
+ width={typeof width === "number" ? width : undefined}
59
+ />
60
+ ))}
98
61
  </colgroup>
99
62
  )}
100
63
 
101
64
  {hasColumns && (
102
65
  <TableHead>
103
66
  <TableRow>
104
- {columns.map(column => (
105
- <TableTh key={`${column.key}/head`} data-key={column.dataKey}>
106
- {/* 변경: 헤더 셀 여백/정렬도 Cell 레이어에서 일관 제어한다. */}
107
- <TableCell section="head" alignX={column.align}>
108
- {resolveHeadContent(column)}
109
- </TableCell>
110
- </TableTh>
111
- ))}
67
+ {resolvedColumns.map(
68
+ ({ key, dataKey, alignX, alignY, cellContents }) => (
69
+ <TableTh key={`${key}/head`} data-key={dataKey}>
70
+ {/* 변경: header cell 정렬은 alignX/alignY로만 제어한다. */}
71
+ <TableCell section="head" alignX={alignX} alignY={alignY}>
72
+ {/* 변경: key는 렌더 식별자이므로 헤더 노출값 fallback으로 사용하지 않는다. */}
73
+ {cellContents}
74
+ </TableCell>
75
+ </TableTh>
76
+ ),
77
+ )}
112
78
  </TableRow>
113
79
  </TableHead>
114
80
  )}
@@ -6,13 +6,12 @@ import type { TableCellContentProps } from "../../types";
6
6
  * Table Foundation; Cell 콘텐츠 래퍼 컴포넌트
7
7
  * @component
8
8
  * @param {TableCellContentProps} props
9
- * @param {"left" | "center" | "right"} [props.alignX] cell 콘텐츠 가로 정렬
10
- * @param {"top" | "center" | "bottom"} [props.alignY] cell 콘텐츠 세로 정렬
9
+ * @param {"left" | "center" | "right" | "normal" | "start" | "end" | "flex-start" | "flex-end" | "space-between" | "space-around" | "space-evenly" | "stretch"} [props.alignX] cell 콘텐츠 가로 정렬(CSS `justify-content`로 매핑)
10
+ * @param {"top" | "center" | "bottom" | "normal" | "stretch" | "start" | "end" | "flex-start" | "flex-end" | "self-start" | "self-end" | "baseline"} [props.alignY] cell 콘텐츠 세로 정렬(CSS `align-items`로 매핑)
11
11
  * @param {boolean} [props.noPadding=false] true면 cell 내부 padding 제거
12
12
  * @param {"default" | "none"} [props.padding="default"] cell 내부 padding 제어
13
- * @param {"left" | "center" | "right"} [props.align] cell 콘텐츠 가로 정렬(호환 alias)
14
13
  * @param {string} [props.className] cell content className
15
- * @param {React.ReactNode} [props.children] 셀 내부 콘텐츠
14
+ * @param {React.ReactNode} [props.children] 셀 내부 콘텐츠 `span.table-cell-text.table-${section}-cell-text`
16
15
  */
17
16
  const TableCell = forwardRef<HTMLDivElement, TableCellContentProps>(
18
17
  (
@@ -24,12 +23,11 @@ const TableCell = forwardRef<HTMLDivElement, TableCellContentProps>(
24
23
  alignY,
25
24
  noPadding = false,
26
25
  padding = "default",
27
- align,
28
26
  ...cellProps
29
27
  },
30
28
  ref,
31
29
  ) => {
32
- const resolvedAlignX = alignX ?? align ?? "left";
30
+ const resolvedAlignX = alignX ?? "left";
33
31
  const resolvedAlignY = alignY ?? "center";
34
32
  // 변경: noPadding boolean이 주어지면 padding 옵션보다 우선해 none으로 고정한다.
35
33
  const resolvedPadding = noPadding ? "none" : padding;
@@ -167,22 +167,52 @@
167
167
  }
168
168
 
169
169
  // 변경: Cell.alignX/alignY로 콘텐츠 정렬을 제어한다.
170
- .table-cell-content[data-align-x="left"],
171
- .table-cell-content[data-align="left"] {
170
+ .table-cell-content[data-align-x="left"] {
172
171
  justify-content: flex-start;
173
- text-align: left;
174
172
  }
175
173
 
176
- .table-cell-content[data-align-x="center"],
177
- .table-cell-content[data-align="center"] {
174
+ .table-cell-content[data-align-x="center"] {
178
175
  justify-content: center;
179
- text-align: center;
180
176
  }
181
177
 
182
- .table-cell-content[data-align-x="right"],
183
- .table-cell-content[data-align="right"] {
178
+ .table-cell-content[data-align-x="right"] {
184
179
  justify-content: flex-end;
185
- text-align: right;
180
+ }
181
+
182
+ .table-cell-content[data-align-x="normal"] {
183
+ justify-content: normal;
184
+ }
185
+
186
+ .table-cell-content[data-align-x="start"] {
187
+ justify-content: start;
188
+ }
189
+
190
+ .table-cell-content[data-align-x="end"] {
191
+ justify-content: end;
192
+ }
193
+
194
+ .table-cell-content[data-align-x="flex-start"] {
195
+ justify-content: flex-start;
196
+ }
197
+
198
+ .table-cell-content[data-align-x="flex-end"] {
199
+ justify-content: flex-end;
200
+ }
201
+
202
+ .table-cell-content[data-align-x="space-between"] {
203
+ justify-content: space-between;
204
+ }
205
+
206
+ .table-cell-content[data-align-x="space-around"] {
207
+ justify-content: space-around;
208
+ }
209
+
210
+ .table-cell-content[data-align-x="space-evenly"] {
211
+ justify-content: space-evenly;
212
+ }
213
+
214
+ .table-cell-content[data-align-x="stretch"] {
215
+ justify-content: stretch;
186
216
  }
187
217
 
188
218
  .table-cell-content[data-align-y="top"] {
@@ -197,6 +227,42 @@
197
227
  align-items: flex-end;
198
228
  }
199
229
 
230
+ .table-cell-content[data-align-y="normal"] {
231
+ align-items: normal;
232
+ }
233
+
234
+ .table-cell-content[data-align-y="stretch"] {
235
+ align-items: stretch;
236
+ }
237
+
238
+ .table-cell-content[data-align-y="start"] {
239
+ align-items: start;
240
+ }
241
+
242
+ .table-cell-content[data-align-y="end"] {
243
+ align-items: end;
244
+ }
245
+
246
+ .table-cell-content[data-align-y="flex-start"] {
247
+ align-items: flex-start;
248
+ }
249
+
250
+ .table-cell-content[data-align-y="flex-end"] {
251
+ align-items: flex-end;
252
+ }
253
+
254
+ .table-cell-content[data-align-y="self-start"] {
255
+ align-items: self-start;
256
+ }
257
+
258
+ .table-cell-content[data-align-y="self-end"] {
259
+ align-items: self-end;
260
+ }
261
+
262
+ .table-cell-content[data-align-y="baseline"] {
263
+ align-items: baseline;
264
+ }
265
+
200
266
  // 변경: section 기반 Cell 텍스트는 native cell 토큰과 동일한 스케일을 따르게 한다.
201
267
  .table-cell.table-head-cell .table-cell-text {
202
268
  color: var(--table-th-text-color);