@uniai-fe/uds-primitives 0.3.18 → 0.3.20

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 (81) hide show
  1. package/dist/styles.css +228 -114
  2. package/package.json +1 -1
  3. package/src/components/badge/markup/Badge.tsx +3 -5
  4. package/src/components/badge/styles/index.scss +2 -1
  5. package/src/components/button/index.tsx +7 -1
  6. package/src/components/button/markup/Base.tsx +5 -10
  7. package/src/components/button/markup/Label.tsx +23 -0
  8. package/src/components/button/markup/index.ts +1 -0
  9. package/src/components/button/types/index.ts +1 -0
  10. package/src/components/button/types/label.ts +9 -0
  11. package/src/components/checkbox/markup/Checkbox.tsx +9 -4
  12. package/src/components/checkbox/styles/index.scss +6 -4
  13. package/src/components/chip/index.tsx +1 -2
  14. package/src/components/chip/markup/Chip.tsx +36 -24
  15. package/src/components/chip/markup/DefaultStyle.tsx +34 -19
  16. package/src/components/chip/markup/InputStyle.tsx +17 -7
  17. package/src/components/chip/markup/Label.tsx +15 -0
  18. package/src/components/chip/markup/ListRoot.tsx +88 -0
  19. package/src/components/chip/markup/RemoveButton.tsx +4 -1
  20. package/src/components/chip/markup/index.tsx +13 -1
  21. package/src/components/chip/styles/chip.scss +43 -15
  22. package/src/components/chip/types/options.ts +22 -14
  23. package/src/components/chip/types/props-internal.ts +9 -5
  24. package/src/components/chip/types/props.ts +127 -46
  25. package/src/components/chip/utils/index.ts +1 -1
  26. package/src/components/dropdown/styles/dropdown.scss +44 -23
  27. package/src/components/dropdown/styles/variables.scss +25 -0
  28. package/src/components/dropdown/types/base.ts +2 -2
  29. package/src/components/form/markup/form-field/Header.tsx +3 -1
  30. package/src/components/input/markup/file/UploadedChip.tsx +5 -5
  31. package/src/components/input/types/file.ts +1 -1
  32. package/src/components/radio/markup/Radio.tsx +9 -4
  33. package/src/components/radio/styles/index.scss +6 -4
  34. package/src/components/segmented-control/markup/Label.tsx +22 -0
  35. package/src/components/segmented-control/markup/List.tsx +2 -3
  36. package/src/components/segmented-control/markup/index.ts +1 -0
  37. package/src/components/segmented-control/styles/index.scss +4 -4
  38. package/src/components/segmented-control/types/index.ts +9 -0
  39. package/src/components/select/hooks/interaction.ts +5 -5
  40. package/src/components/select/img/chevron/primary/xsmall.svg +3 -0
  41. package/src/components/select/markup/Default.tsx +183 -212
  42. package/src/components/select/markup/foundation/Base.tsx +19 -12
  43. package/src/components/select/markup/foundation/Icon.tsx +9 -3
  44. package/src/components/select/markup/foundation/Selected.tsx +115 -10
  45. package/src/components/select/markup/multiple/Multiple.tsx +63 -135
  46. package/src/components/select/styles/select.scss +128 -72
  47. package/src/components/select/styles/variables.scss +11 -0
  48. package/src/components/select/types/base.ts +3 -2
  49. package/src/components/select/types/icon.ts +34 -3
  50. package/src/components/select/types/interaction.ts +1 -1
  51. package/src/components/select/types/option.ts +0 -80
  52. package/src/components/select/types/props.ts +167 -92
  53. package/src/components/select/types/trigger.ts +52 -1
  54. package/src/components/slot/index.tsx +2 -6
  55. package/src/components/slot/markup/Text.tsx +34 -0
  56. package/src/components/slot/markup/index.tsx +7 -0
  57. package/src/components/slot/types/index.ts +2 -0
  58. package/src/components/slot/types/text.ts +24 -0
  59. package/src/components/table/markup/foundation/Cell.tsx +4 -12
  60. package/src/components/table/markup/foundation/Td.tsx +4 -7
  61. package/src/components/table/markup/foundation/Text.tsx +16 -0
  62. package/src/components/table/markup/foundation/Th.tsx +4 -7
  63. package/src/components/table/markup/foundation/index.tsx +2 -0
  64. package/src/components/table/types/foundation.ts +9 -0
  65. package/src/components/tooltip/markup/Message.tsx +3 -1
  66. package/src/components/tooltip/markup/Text.tsx +21 -0
  67. package/src/components/tooltip/markup/index.tsx +3 -0
  68. package/src/components/tooltip/types/index.ts +1 -0
  69. package/src/components/tooltip/types/text.ts +9 -0
  70. package/src/index.scss +0 -1
  71. package/src/index.tsx +0 -1
  72. package/src/components/chip/utils/class-name.ts +0 -36
  73. package/src/components/label/hooks/index.ts +0 -4
  74. package/src/components/label/img/.gitkeep +0 -0
  75. package/src/components/label/index.scss +0 -1
  76. package/src/components/label/index.tsx +0 -4
  77. package/src/components/label/markup/index.tsx +0 -4
  78. package/src/components/label/styles/index.scss +0 -0
  79. package/src/components/label/types/index.ts +0 -4
  80. package/src/components/label/utils/index.ts +0 -4
  81. /package/src/components/slot/markup/{Component.tsx → Base.tsx} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-primitives",
3
- "version": "0.3.18",
3
+ "version": "0.3.20",
4
4
  "description": "UNIAI Design System; Primitives Components Package",
5
5
  "type": "module",
6
6
  "private": false,
@@ -1,6 +1,7 @@
1
1
  import { forwardRef } from "react";
2
2
  import type { BadgeProps } from "../types";
3
3
  import { composeBadgeClassName } from "../utils";
4
+ import { Slot } from "../../slot";
4
5
 
5
6
  /**
6
7
  * Badge 컴포넌트; size/style/intent 축을 data attribute로 노출한다.
@@ -50,11 +51,8 @@ const Badge = forwardRef<HTMLElementTagNameMap["figure"], BadgeProps>(
50
51
  data-has-label={hasLabel ? "true" : undefined}
51
52
  >
52
53
  {renderDot}
53
- {["string", "number"].includes(typeof children) ? (
54
- <span className="badge-label">{children}</span>
55
- ) : (
56
- children
57
- )}
54
+ {/* 변경: badge 라벨 텍스트 래핑 규칙을 Slot.Text로 통일한다. */}
55
+ <Slot.Text className="badge-label">{children}</Slot.Text>
58
56
  </figure>
59
57
  );
60
58
  },
@@ -6,7 +6,8 @@
6
6
  --theme-badge-radius: var(--theme-radius-medium-1, 6px);
7
7
  --theme-badge-font-family: var(--font-caption-medium-family, inherit);
8
8
  --theme-badge-font-size: var(--font-caption-medium-size, 11px);
9
- --theme-badge-font-weight: var(--font-caption-medium-weight, 400);
9
+ // 변경: Badge label font-weight 디자인 규칙에 맞춰 400으로 고정한다.
10
+ --theme-badge-font-weight: 400;
10
11
  --theme-badge-line-height: var(--font-caption-medium-line-height, 1.5);
11
12
  --theme-badge-letter-spacing: var(--font-caption-medium-letter-spacing, 0);
12
13
  --theme-badge-dot-size: var(--spacing-gap-3, 8px);
@@ -3,12 +3,18 @@
3
3
  */
4
4
  import "./index.scss";
5
5
 
6
- import { ButtonDefault, ButtonText, ButtonRounded } from "./markup";
6
+ import {
7
+ ButtonDefault,
8
+ ButtonText,
9
+ ButtonRounded,
10
+ ButtonLabel,
11
+ } from "./markup";
7
12
 
8
13
  export const Button = {
9
14
  Default: ButtonDefault,
10
15
  Text: ButtonText,
11
16
  Rounded: ButtonRounded,
17
+ Label: ButtonLabel,
12
18
  };
13
19
 
14
20
  export * from "./hooks";
@@ -3,7 +3,8 @@
3
3
  import clsx from "clsx";
4
4
  import { forwardRef } from "react";
5
5
  import type { ButtonProps } from "../types";
6
- import { SlotComponent } from "../../slot";
6
+ import { Slot } from "../../slot";
7
+ import ButtonLabel from "./Label";
7
8
  import {
8
9
  getFormFieldWidthAttr,
9
10
  getFormFieldWidthValue,
@@ -85,7 +86,7 @@ const ButtonDefault = forwardRef<HTMLElement, ButtonProps>(
85
86
  : style;
86
87
 
87
88
  return (
88
- <SlotComponent
89
+ <Slot.Base
89
90
  className={clsx(
90
91
  "button",
91
92
  `button-fill-${fill}`,
@@ -112,19 +113,13 @@ const ButtonDefault = forwardRef<HTMLElement, ButtonProps>(
112
113
  {left}
113
114
  </span>
114
115
  )}
115
- {typeof children === "string" || typeof children === "number" ? (
116
- <span className="button-label" data-slot="label">
117
- {children}
118
- </span>
119
- ) : (
120
- children
121
- )}
116
+ <ButtonLabel data-slot="label">{children}</ButtonLabel>
122
117
  {right && (
123
118
  <span className="button-right" data-slot="right">
124
119
  {right}
125
120
  </span>
126
121
  )}
127
- </SlotComponent>
122
+ </Slot.Base>
128
123
  );
129
124
  },
130
125
  );
@@ -0,0 +1,23 @@
1
+ import clsx from "clsx";
2
+ import { Slot } from "../../slot";
3
+ import type { ButtonLabelProps } from "../types/label";
4
+
5
+ /**
6
+ * ButtonLabel; Button namespace에서 재사용 가능한 label 래퍼.
7
+ * @component
8
+ * @param {ButtonLabelProps} props
9
+ * @param {React.ElementType} [props.as="span"] 렌더링할 요소.
10
+ * @param {React.ReactNode} [props.children] 문자열/숫자는 button-label span으로 렌더하고, 그 외 ReactNode는 그대로 반환한다.
11
+ * @param {string} [props.className] button-label과 병합할 className.
12
+ * @example
13
+ * <Button.Label>확인</Button.Label>
14
+ */
15
+ export default function ButtonLabel({
16
+ className,
17
+ ...restProps
18
+ }: ButtonLabelProps) {
19
+ // Button 전용 라벨 클래스는 namespace 컴포넌트에서 고정 주입한다.
20
+ return (
21
+ <Slot.Text className={clsx("button-label", className)} {...restProps} />
22
+ );
23
+ }
@@ -1,3 +1,4 @@
1
1
  export { ButtonDefault } from "./Base";
2
2
  export { ButtonText } from "./Text";
3
3
  export { ButtonRounded } from "./Rounded";
4
+ export { default as ButtonLabel } from "./Label";
@@ -2,4 +2,5 @@
2
2
  * Button Types; barrel export 전용 파일
3
3
  */
4
4
  export type * from "./props";
5
+ export type * from "./label";
5
6
  export * from "./options";
@@ -0,0 +1,9 @@
1
+ import type { SlotTextProps } from "../../slot";
2
+
3
+ /**
4
+ * ButtonLabelProps; Button 전용 label 래퍼 props.
5
+ * @property {React.ElementType} [as] 렌더링할 요소. 기본값은 span.
6
+ * @property {React.ReactNode} [children] 문자열/숫자는 래핑하고, 그 외 ReactNode는 그대로 반환한다.
7
+ * @property {string} [className] 기본 button-label 클래스와 병합할 className.
8
+ */
9
+ export type ButtonLabelProps = SlotTextProps;
@@ -4,13 +4,14 @@ import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
4
4
  import type { CheckboxFieldProps, CheckboxProps, CheckboxSize } from "../types";
5
5
  import CheckLargeIcon from "../img/check-large.svg";
6
6
  import CheckMediumIcon from "../img/check-medium.svg";
7
+ import { Slot } from "../../slot";
7
8
 
8
9
  const CHECKBOX_CLASSNAME = "checkbox";
9
10
  const CHECKBOX_SURFACE_CLASSNAME = "checkbox-surface";
10
11
  const CHECKBOX_INDICATOR_CLASSNAME = "checkbox-indicator";
11
12
  const CHECKBOX_FIELD_CLASSNAME = "checkbox-field";
12
13
  const CHECKBOX_LABEL_WRAPPER_CLASSNAME = "checkbox-label-wrapper";
13
- const CHECKBOX_LABEL_TEXT_CLASSNAME = "checkbox-label-text";
14
+ const CHECKBOX_LABEL_CLASSNAME = "checkbox-label";
14
15
  const CHECKBOX_HELPER_CLASSNAME = "checkbox-helper";
15
16
 
16
17
  const getIndicatorIcon = (size: CheckboxSize) =>
@@ -95,7 +96,10 @@ const CheckboxField = forwardRef<HTMLButtonElement, CheckboxFieldProps>(
95
96
  ) {
96
97
  const generatedId = useId();
97
98
  const checkboxId = id ?? generatedId;
98
- const labelId = label ? `${checkboxId}-label` : undefined;
99
+ const labelId =
100
+ label && ["string", "number"].includes(typeof label)
101
+ ? `${checkboxId}-label`
102
+ : undefined;
99
103
  const helperId = helperText ? `${checkboxId}-helper` : undefined;
100
104
  // Label/helper는 Root 외부에서 htmlFor/aria 연결을 유지한다.
101
105
 
@@ -126,9 +130,10 @@ const CheckboxField = forwardRef<HTMLButtonElement, CheckboxFieldProps>(
126
130
  ref={ref}
127
131
  />
128
132
  {label ? (
129
- <span id={labelId} className={CHECKBOX_LABEL_TEXT_CLASSNAME}>
133
+ // 변경: label 렌더는 Slot.Text 단일 경로로 통일하고, aria-labelledby는 텍스트일 때만 연결한다.
134
+ <Slot.Text id={labelId} className={CHECKBOX_LABEL_CLASSNAME}>
130
135
  {label}
131
- </span>
136
+ </Slot.Text>
132
137
  ) : null}
133
138
  </label>
134
139
  {helperText ? (
@@ -15,6 +15,8 @@
15
15
  --theme-checkbox-surface-selected-disabled: rgba(26, 106, 255, 0.28);
16
16
  --theme-checkbox-label-color: var(--color-label-strong);
17
17
  --theme-checkbox-label-disabled: var(--color-label-disabled);
18
+ // 변경: Checkbox label font-weight는 디자인 규칙에 맞춰 400으로 고정한다.
19
+ --theme-checkbox-label-font-weight: 400;
18
20
  --theme-checkbox-helper-color: var(--color-label-neutral);
19
21
  --theme-checkbox-helper-disabled: var(--color-label-disabled);
20
22
  --theme-checkbox-icon-default: transparent;
@@ -148,17 +150,17 @@
148
150
  cursor: not-allowed;
149
151
  }
150
152
 
151
- .checkbox-label-text {
152
- font-weight: var(--font-body-medium-weight);
153
+ .checkbox-label {
154
+ font-weight: var(--theme-checkbox-label-font-weight);
153
155
  user-select: none;
154
156
  }
155
157
 
156
- .checkbox-field[data-size="medium"] .checkbox-label-text {
158
+ .checkbox-field[data-size="medium"] .checkbox-label {
157
159
  font-size: var(--font-body-xsmall-size);
158
160
  line-height: var(--font-body-xsmall-line-height);
159
161
  }
160
162
 
161
- .checkbox-field[data-size="large"] .checkbox-label-text {
163
+ .checkbox-field[data-size="large"] .checkbox-label {
162
164
  font-size: var(--font-body-medium-size);
163
165
  line-height: var(--font-body-medium-line-height);
164
166
  }
@@ -1,9 +1,8 @@
1
1
  /**
2
- * chips 카테고리 배럴 placeholder: 실제 구현은 markup/ 하위에 추가한다.
2
+ * chips 카테고리 배럴
3
3
  */
4
4
  import "./index.scss";
5
5
 
6
6
  export * from "./markup";
7
7
  export * from "./hooks";
8
8
  export type * from "./types";
9
- export * from "./utils";
@@ -1,48 +1,55 @@
1
1
  import { forwardRef, type Ref } from "react";
2
+ import clsx from "clsx";
2
3
  import type { ChipInputProps, ChipProps } from "../types";
3
- import ChipDefaultStyle from "./DefaultStyle";
4
+ import ChipClickableStyle from "./DefaultStyle";
4
5
  import ChipInputStyle from "./InputStyle";
5
6
 
6
- // kind === "input" 조합에서 props를 ChipInputProps로 좁히기 위한 type guard.
7
+ // chipStyle === "input" 조합에서 props를 ChipInputProps로 좁히기 위한 type guard.
7
8
  const isChipInputProps = (props: ChipProps): props is ChipInputProps =>
8
- props.kind === "input";
9
+ props.chipStyle === "input";
9
10
 
10
11
  /**
11
- * Chip Markup; Kind별 렌더 분기 Orchestrator 컴포넌트
12
+ * Chip Markup; Style별 렌더 분기 Orchestrator 컴포넌트
12
13
  * @component
13
14
  * @param {ChipProps} props Chip 공통/종류별 props
14
- * @param {"filter" | "filter-rounded" | "assist" | "input"} [props.kind="filter"] Chip 종류
15
+ * @param {"filter" | "assist" | "input"} [props.chipStyle="filter"] Chip 스타일
16
+ * @param {"solid" | "outlined"} [props.fill="solid"] Chip 채움 스타일
17
+ * @param {boolean} [props.rounded=false] pill radius 적용 여부
15
18
  * @param {React.ReactNode} [props.children] Chip 라벨/콘텐츠
16
- * @param {boolean} [props.selected] 선택 상태; filter 계열에서 `aria-pressed`로 반영
17
- * @param {React.ReactNode} [props.leading] assist kind 좌측 slot
19
+ * @param {boolean} [props.selected] 선택 상태; filter 스타일에서 `aria-pressed`로 반영
20
+ * @param {React.ReactNode} [props.leading] assist 스타일 좌측 slot
18
21
  * @param {"default" | "table"} [props.size="default"] 사이즈 축
19
- * @param {string} [props.removeButtonLabel="선택 항목 삭제"] input kind remove 버튼 라벨
20
- * @param {boolean} [props.disabled] input kind 비활성화 여부
21
- * @param {(event: MouseEvent<HTMLButtonElement>) => void} [props.onRemove] input kind remove 핸들러
22
- * @param {(event: MouseEvent<HTMLButtonElement>) => void} [props.onClick] interactive kind click 핸들러
23
- * @param {"button" | "submit" | "reset"} [props.type] interactive kind button type
22
+ * @param {string} [props.removeButtonLabel="선택 항목 삭제"] input 스타일 remove 버튼 라벨
23
+ * @param {boolean} [props.disabled] input 스타일 비활성화 여부
24
+ * @param {(event: MouseEvent<HTMLButtonElement>) => void} [props.onRemove] input 스타일 remove 핸들러
25
+ * @param {(event: MouseEvent<HTMLButtonElement>) => void} [props.onClick] interactive 스타일 click 핸들러
26
+ * @param {"button" | "submit" | "reset"} [props.type] interactive 스타일 button type
24
27
  * @param {string} [props.className] root className
25
28
  * @param {string} [props.id] root id
26
29
  * @param {string} [props.title] root title
27
30
  * @description
28
- * - `kind="input"`이면 `<figure>`를 렌더하고, 나머지 kind는 `<button>`을 렌더한다.
29
- * - `kind="input"`일 때 remove 버튼 클릭 이벤트는 root click으로 버블링되지 않도록 내부에서 차단한다.
30
- * - native HTML attrs는 kind에 맞는 root 요소로 그대로 전달된다.
31
- * @returns {JSX.Element} kind에 맞는 Chip root 요소
31
+ * - `chipStyle="input"`이면 `<figure>`를 렌더하고, 나머지 스타일은 `<button>`을 렌더한다.
32
+ * - `chipStyle="input"`일 때 remove 버튼 클릭 이벤트는 root click으로 버블링되지 않도록 내부에서 차단한다.
33
+ * - native HTML attrs는 style에 맞는 root 요소로 그대로 전달된다.
34
+ * @returns {JSX.Element} style에 맞는 Chip root 요소
32
35
  * @example
33
- * <Chip kind="filter" selected>
36
+ * <ChipDefault chipStyle="filter" fill="solid" selected rounded>
34
37
  * 전체
35
- * </Chip>
38
+ * </ChipDefault>
36
39
  * @example
37
- * <Chip kind="input" onRemove={event => console.log(event.type)}>
40
+ * <ChipDefault chipStyle="input" onRemove={event => console.log(event.type)}>
38
41
  * 첨부파일.pdf
39
- * </Chip>
42
+ * </ChipDefault>
40
43
  */
41
- const Chip = forwardRef<HTMLElement, ChipProps>((props, ref) => {
44
+ const ChipDefault = forwardRef<HTMLElement, ChipProps>((props, ref) => {
45
+ const mergedClassName = clsx("chip-default", props.className);
46
+
42
47
  if (isChipInputProps(props)) {
43
48
  return (
44
49
  <ChipInputStyle
45
50
  {...props}
51
+ // 변경: 역할 className을 루트 컴포넌트에서 누적 전달한다.
52
+ className={mergedClassName}
46
53
  // 변경: 하위 스타일 컴포넌트가 forwardRef를 사용하므로 ref를 직접 전달한다.
47
54
  ref={ref as Ref<HTMLElementTagNameMap["figure"]>}
48
55
  />
@@ -50,11 +57,16 @@ const Chip = forwardRef<HTMLElement, ChipProps>((props, ref) => {
50
57
  }
51
58
 
52
59
  return (
60
+ // 변경: 역할 className을 루트 컴포넌트에서 누적 전달한다.
53
61
  // 변경: interactive 경로도 forwardRef 기반 ref 전달로 통일한다.
54
- <ChipDefaultStyle {...props} ref={ref as Ref<HTMLButtonElement>} />
62
+ <ChipClickableStyle
63
+ {...props}
64
+ className={mergedClassName}
65
+ ref={ref as Ref<HTMLButtonElement>}
66
+ />
55
67
  );
56
68
  });
57
69
 
58
- Chip.displayName = "Chip";
70
+ ChipDefault.displayName = "Chip.Default";
59
71
 
60
- export { Chip };
72
+ export { ChipDefault };
@@ -1,27 +1,30 @@
1
1
  import { forwardRef } from "react";
2
2
  import clsx from "clsx";
3
- import type { ChipDefaultStyleComponentProps } from "../types";
3
+ import type { ChipClickableStyleComponentProps } from "../types";
4
+ import ChipLabel from "./Label";
4
5
 
5
6
  /**
6
7
  * Chip Markup; Default/Interactive 스타일 렌더 컴포넌트
7
8
  * @component
8
- * @param {ChipDefaultStyleComponentProps} props interactive 스타일 props
9
+ * @param {ChipClickableStyleComponentProps} props interactive 스타일 props
9
10
  * @param {Ref<HTMLButtonElement>} ref button ref
10
11
  * @param {React.ReactNode} [props.children] chip 라벨 콘텐츠
11
12
  * @param {boolean} [props.selected] 선택 상태
12
- * @param {React.ReactNode} [props.leading] assist kind 좌측 slot
13
+ * @param {React.ReactNode} [props.leading] assist 스타일 좌측 slot
13
14
  * @param {string} [props.className] root className
14
- * @param {"filter" | "filter-rounded" | "assist"} [props.kind] interactive kind
15
+ * @param {"filter" | "assist"} [props.chipStyle] interactive style
15
16
  * @param {"default" | "table"} [props.size] size 축
17
+ * @param {"solid" | "outlined"} [props.fill] 채움 스타일
18
+ * @param {boolean} [props.rounded] pill radius 적용 여부
16
19
  * @param {"button" | "submit" | "reset"} [props.type] button type
17
20
  * @example
18
- * <ChipDefaultStyle kind="assist" ref={buttonRef}>
21
+ * <ChipClickableStyle chipStyle="assist" ref={buttonRef}>
19
22
  * Assist
20
- * </ChipDefaultStyle>
23
+ * </ChipClickableStyle>
21
24
  */
22
- const ChipDefaultStyle = forwardRef<
25
+ const ChipClickableStyle = forwardRef<
23
26
  HTMLButtonElement,
24
- ChipDefaultStyleComponentProps
27
+ ChipClickableStyleComponentProps
25
28
  >(
26
29
  (
27
30
  {
@@ -29,16 +32,18 @@ const ChipDefaultStyle = forwardRef<
29
32
  selected,
30
33
  leading,
31
34
  className,
32
- kind,
35
+ chipStyle,
33
36
  size = "default",
37
+ fill = "solid",
38
+ rounded = false,
34
39
  type,
35
40
  ...restProps
36
41
  },
37
42
  ref,
38
43
  ) => {
39
44
  // 변경: props 구조분해는 함수 시그니처에서만 수행하고, 본문 내 `const { ... } = props` 패턴을 제거한다.
40
- const resolvedKind = kind ?? "filter";
41
- const isAssist = resolvedKind === "assist";
45
+ const resolvedChipStyle = chipStyle ?? "filter";
46
+ const isAssist = resolvedChipStyle === "assist";
42
47
  const hasLeading = isAssist && Boolean(leading);
43
48
 
44
49
  return (
@@ -47,29 +52,39 @@ const ChipDefaultStyle = forwardRef<
47
52
  // 변경: forwardedRef custom prop 대신 forwardRef의 ref를 직접 연결한다.
48
53
  ref={ref}
49
54
  type={type ?? "button"}
50
- className={clsx("chip", className)}
51
- data-kind={resolvedKind}
55
+ // 변경: 기반 class + 역할 class + 외부 className 순서로 누적한다.
56
+ className={clsx(
57
+ "chip",
58
+ "chip-clickable-style",
59
+ "chip-clickable-root",
60
+ className,
61
+ )}
62
+ data-style={resolvedChipStyle}
63
+ data-fill={fill}
64
+ data-rounded={rounded ? "true" : undefined}
52
65
  data-size={size}
53
66
  data-selected={selected ? "true" : undefined}
54
67
  data-has-leading={hasLeading ? "true" : undefined}
55
68
  aria-pressed={
56
- typeof selected === "boolean" &&
57
- (resolvedKind === "filter" || resolvedKind === "filter-rounded")
69
+ typeof selected === "boolean" && resolvedChipStyle === "filter"
58
70
  ? selected
59
71
  : undefined
60
72
  }
61
73
  >
62
74
  {isAssist && leading ? (
63
- <span className="chip-leading" aria-hidden="true">
75
+ <span
76
+ className="chip-leading chip-clickable-leading"
77
+ aria-hidden="true"
78
+ >
64
79
  {leading}
65
80
  </span>
66
81
  ) : null}
67
- <span className="chip-label">{children}</span>
82
+ <ChipLabel className="chip-clickable-label">{children}</ChipLabel>
68
83
  </button>
69
84
  );
70
85
  },
71
86
  );
72
87
 
73
- ChipDefaultStyle.displayName = "ChipDefaultStyle";
88
+ ChipClickableStyle.displayName = "ChipClickableStyle";
74
89
 
75
- export default ChipDefaultStyle;
90
+ export default ChipClickableStyle;
@@ -3,6 +3,7 @@ import clsx from "clsx";
3
3
  import type { MouseEvent } from "react";
4
4
  import type { ChipInputStyleProps } from "../types";
5
5
  import ChipRemoveButton from "./RemoveButton";
6
+ import ChipLabel from "./Label";
6
7
 
7
8
  /**
8
9
  * Chip Markup; Input 스타일 렌더 컴포넌트
@@ -15,8 +16,9 @@ import ChipRemoveButton from "./RemoveButton";
15
16
  * @param {(event: MouseEvent<HTMLButtonElement>) => void} [props.onRemove] remove 클릭 핸들러
16
17
  * @param {boolean} [props.disabled] 비활성화 여부
17
18
  * @param {boolean} [props.selected] 선택 상태
18
- * @param {React.ReactNode} [props.leading] leading slot 존재 여부 판단용 값
19
19
  * @param {"default" | "table"} [props.size] size 축
20
+ * @param {"solid" | "outlined"} [props.fill] 채움 스타일
21
+ * @param {boolean} [props.rounded] pill radius 적용 여부
20
22
  * @example
21
23
  * <ChipInputStyle
22
24
  * ref={figureRef}
@@ -37,15 +39,15 @@ const ChipInputStyle = forwardRef<
37
39
  onRemove,
38
40
  disabled = false,
39
41
  selected,
40
- leading,
41
42
  size = "default",
43
+ fill = "solid",
44
+ rounded = false,
42
45
  ...restProps
43
46
  },
44
47
  ref,
45
48
  ) => {
46
49
  // 변경: props 구조분해는 함수 시그니처에서만 수행하고, 본문 내 `const { ... } = props` 패턴을 제거한다.
47
50
  const removable = !disabled && typeof onRemove === "function";
48
- const hasLeading = Boolean(leading);
49
51
  /**
50
52
  * 변경: remove 버튼 클릭이 chip root click으로 버블링되지 않도록 차단한다.
51
53
  */
@@ -59,18 +61,26 @@ const ChipInputStyle = forwardRef<
59
61
  {...restProps}
60
62
  // 변경: forwardedRef custom prop 대신 forwardRef의 ref를 직접 연결한다.
61
63
  ref={ref}
62
- className={clsx("chip", className)}
63
- data-kind="input"
64
+ // 변경: 기반 class + 역할 class + 외부 className 순서로 누적한다.
65
+ className={clsx(
66
+ "chip",
67
+ "chip-input-style",
68
+ "chip-input-root",
69
+ className,
70
+ )}
71
+ data-style="input"
72
+ data-fill={fill}
73
+ data-rounded={rounded ? "true" : undefined}
64
74
  data-removable={removable ? "true" : "false"}
65
75
  data-size={size}
66
76
  data-selected={selected ? "true" : undefined}
67
- data-has-leading={hasLeading ? "true" : undefined}
68
77
  data-disabled={disabled ? "true" : undefined}
69
78
  aria-disabled={disabled ? "true" : undefined}
70
79
  >
71
- <span className="chip-label">{children}</span>
80
+ <ChipLabel className="chip-input-label">{children}</ChipLabel>
72
81
  {removable ? (
73
82
  <ChipRemoveButton
83
+ className="chip-input-remove-button"
74
84
  removeButtonLabel={removeButtonLabel}
75
85
  onRemove={handleRemoveClick}
76
86
  />
@@ -0,0 +1,15 @@
1
+ import clsx from "clsx";
2
+ import { Slot } from "../../slot";
3
+ import type { ChipLabelProps } from "../types/props";
4
+
5
+ /**
6
+ * Chip Label; Chip 라벨 텍스트 렌더 컴포넌트.
7
+ * @component
8
+ * @param {ChipLabelProps} props
9
+ * @param {React.ReactNode} [props.children] 라벨 콘텐츠
10
+ * @param {string} [props.className] chip-label과 병합할 className
11
+ */
12
+ export default function ChipLabel({ className, ...restProps }: ChipLabelProps) {
13
+ // Chip 라벨 스타일 일관성을 위해 모듈 전용 label 컴포넌트를 사용한다.
14
+ return <Slot.Text className={clsx("chip-label", className)} {...restProps} />;
15
+ }
@@ -0,0 +1,88 @@
1
+ import { type CSSProperties } from "react";
2
+ import clsx from "clsx";
3
+ import type { ChipListRootProps } from "../types";
4
+ import ChipClickableStyle from "./DefaultStyle";
5
+ import ChipInputStyle from "./InputStyle";
6
+
7
+ /**
8
+ * Chip Markup; List Root 템플릿 컴포넌트
9
+ * @component
10
+ * @param {ChipListRootProps<OptionData>} props list root props
11
+ * @param {ChipListItemData<OptionData>[]} props.items 렌더링할 chip 엔트리 목록
12
+ * @param {"filter" | "assist" | "input"} [props.chipStyle="filter"] item 공통 chip 스타일
13
+ * @param {"solid" | "outlined"} [props.fill="solid"] item 공통 chip 채움 스타일
14
+ * @param {boolean} [props.rounded=false] item 공통 pill radius 적용 여부
15
+ * @param {"default" | "table"} [props.size="default"] item 공통 chip 사이즈 축
16
+ * @param {CSSProperties["gap"]} [props.gap] list gap
17
+ * @example
18
+ * <Chip.List
19
+ * chipStyle="filter"
20
+ * rounded
21
+ * items={[
22
+ * { id: "all", value: "all", label: "전체", selected: true },
23
+ * { id: "notice", value: "notice", label: "알림" },
24
+ * ]}
25
+ * />
26
+ */
27
+ function ChipListRoot<OptionData = unknown>({
28
+ items,
29
+ chipStyle = "filter",
30
+ fill = "solid",
31
+ rounded = false,
32
+ size = "default",
33
+ gap,
34
+ className,
35
+ style,
36
+ ...restProps
37
+ }: ChipListRootProps<OptionData>) {
38
+ const mergedStyle = {
39
+ ...style,
40
+ ...(typeof gap === "undefined" ? {} : { gap }),
41
+ } as CSSProperties;
42
+
43
+ return (
44
+ <ul
45
+ {...restProps}
46
+ // 변경: list root 역할 class를 기본으로 누적한다.
47
+ className={clsx("chip-list", "chip-list-root", className)}
48
+ style={mergedStyle}
49
+ >
50
+ {items.map(item => (
51
+ <li key={item.id} className="chip-list-item chip-list-entry">
52
+ {chipStyle === "input" ? (
53
+ <ChipInputStyle
54
+ chipStyle="input"
55
+ fill={fill}
56
+ rounded={rounded}
57
+ size={size}
58
+ selected={item.selected}
59
+ disabled={item.disabled}
60
+ title={item.title}
61
+ className={clsx("chip-list-chip", item.className)}
62
+ onRemove={item.onRemove}
63
+ removeButtonLabel={item.removeButtonLabel}
64
+ >
65
+ {item.label}
66
+ </ChipInputStyle>
67
+ ) : (
68
+ <ChipClickableStyle
69
+ chipStyle={chipStyle}
70
+ fill={fill}
71
+ rounded={rounded}
72
+ size={size}
73
+ selected={item.selected}
74
+ disabled={item.disabled}
75
+ title={item.title}
76
+ className={clsx("chip-list-chip", item.className)}
77
+ onClick={item.onClick}
78
+ >
79
+ {item.label}
80
+ </ChipClickableStyle>
81
+ )}
82
+ </li>
83
+ ))}
84
+ </ul>
85
+ );
86
+ }
87
+
88
+ export { ChipListRoot };
@@ -1,3 +1,4 @@
1
+ import clsx from "clsx";
1
2
  import RemoveIcon from "../img/remove.svg";
2
3
  import type { ChipRemoveButtonProps } from "../types";
3
4
 
@@ -14,13 +15,15 @@ import type { ChipRemoveButtonProps } from "../types";
14
15
  * />
15
16
  */
16
17
  export default function ChipRemoveButton({
18
+ className,
17
19
  removeButtonLabel,
18
20
  onRemove,
19
21
  }: ChipRemoveButtonProps) {
20
22
  return (
21
23
  <button
22
24
  type="button"
23
- className="chip-remove-button"
25
+ // 변경: remove 버튼도 역할 class를 누적할 수 있게 열어둔다.
26
+ className={clsx("chip-remove-button", className)}
24
27
  aria-label={removeButtonLabel}
25
28
  onClick={onRemove}
26
29
  >
@@ -1 +1,13 @@
1
- export { Chip } from "./Chip";
1
+ import { ChipDefault } from "./Chip";
2
+ import ChipClickableStyle from "./DefaultStyle";
3
+ import ChipInputStyle from "./InputStyle";
4
+ import ChipLabel from "./Label";
5
+ import { ChipListRoot } from "./ListRoot";
6
+
7
+ export const Chip = {
8
+ Default: ChipDefault,
9
+ ClickableStyle: ChipClickableStyle,
10
+ InputStyle: ChipInputStyle,
11
+ Label: ChipLabel,
12
+ List: ChipListRoot,
13
+ };