@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.
- package/dist/styles.css +228 -114
- package/package.json +1 -1
- package/src/components/badge/markup/Badge.tsx +3 -5
- package/src/components/badge/styles/index.scss +2 -1
- package/src/components/button/index.tsx +7 -1
- package/src/components/button/markup/Base.tsx +5 -10
- package/src/components/button/markup/Label.tsx +23 -0
- package/src/components/button/markup/index.ts +1 -0
- package/src/components/button/types/index.ts +1 -0
- package/src/components/button/types/label.ts +9 -0
- package/src/components/checkbox/markup/Checkbox.tsx +9 -4
- package/src/components/checkbox/styles/index.scss +6 -4
- package/src/components/chip/index.tsx +1 -2
- package/src/components/chip/markup/Chip.tsx +36 -24
- package/src/components/chip/markup/DefaultStyle.tsx +34 -19
- package/src/components/chip/markup/InputStyle.tsx +17 -7
- package/src/components/chip/markup/Label.tsx +15 -0
- package/src/components/chip/markup/ListRoot.tsx +88 -0
- package/src/components/chip/markup/RemoveButton.tsx +4 -1
- package/src/components/chip/markup/index.tsx +13 -1
- package/src/components/chip/styles/chip.scss +43 -15
- package/src/components/chip/types/options.ts +22 -14
- package/src/components/chip/types/props-internal.ts +9 -5
- package/src/components/chip/types/props.ts +127 -46
- package/src/components/chip/utils/index.ts +1 -1
- package/src/components/dropdown/styles/dropdown.scss +44 -23
- package/src/components/dropdown/styles/variables.scss +25 -0
- package/src/components/dropdown/types/base.ts +2 -2
- package/src/components/form/markup/form-field/Header.tsx +3 -1
- package/src/components/input/markup/file/UploadedChip.tsx +5 -5
- package/src/components/input/types/file.ts +1 -1
- package/src/components/radio/markup/Radio.tsx +9 -4
- package/src/components/radio/styles/index.scss +6 -4
- package/src/components/segmented-control/markup/Label.tsx +22 -0
- package/src/components/segmented-control/markup/List.tsx +2 -3
- package/src/components/segmented-control/markup/index.ts +1 -0
- package/src/components/segmented-control/styles/index.scss +4 -4
- package/src/components/segmented-control/types/index.ts +9 -0
- package/src/components/select/hooks/interaction.ts +5 -5
- package/src/components/select/img/chevron/primary/xsmall.svg +3 -0
- package/src/components/select/markup/Default.tsx +183 -212
- package/src/components/select/markup/foundation/Base.tsx +19 -12
- package/src/components/select/markup/foundation/Icon.tsx +9 -3
- package/src/components/select/markup/foundation/Selected.tsx +115 -10
- package/src/components/select/markup/multiple/Multiple.tsx +63 -135
- package/src/components/select/styles/select.scss +128 -72
- package/src/components/select/styles/variables.scss +11 -0
- package/src/components/select/types/base.ts +3 -2
- package/src/components/select/types/icon.ts +34 -3
- package/src/components/select/types/interaction.ts +1 -1
- package/src/components/select/types/option.ts +0 -80
- package/src/components/select/types/props.ts +167 -92
- package/src/components/select/types/trigger.ts +52 -1
- package/src/components/slot/index.tsx +2 -6
- package/src/components/slot/markup/Text.tsx +34 -0
- package/src/components/slot/markup/index.tsx +7 -0
- package/src/components/slot/types/index.ts +2 -0
- package/src/components/slot/types/text.ts +24 -0
- package/src/components/table/markup/foundation/Cell.tsx +4 -12
- package/src/components/table/markup/foundation/Td.tsx +4 -7
- package/src/components/table/markup/foundation/Text.tsx +16 -0
- package/src/components/table/markup/foundation/Th.tsx +4 -7
- package/src/components/table/markup/foundation/index.tsx +2 -0
- package/src/components/table/types/foundation.ts +9 -0
- package/src/components/tooltip/markup/Message.tsx +3 -1
- package/src/components/tooltip/markup/Text.tsx +21 -0
- package/src/components/tooltip/markup/index.tsx +3 -0
- package/src/components/tooltip/types/index.ts +1 -0
- package/src/components/tooltip/types/text.ts +9 -0
- package/src/index.scss +0 -1
- package/src/index.tsx +0 -1
- package/src/components/chip/utils/class-name.ts +0 -36
- package/src/components/label/hooks/index.ts +0 -4
- package/src/components/label/img/.gitkeep +0 -0
- package/src/components/label/index.scss +0 -1
- package/src/components/label/index.tsx +0 -4
- package/src/components/label/markup/index.tsx +0 -4
- package/src/components/label/styles/index.scss +0 -0
- package/src/components/label/types/index.ts +0 -4
- package/src/components/label/utils/index.ts +0 -4
- /package/src/components/slot/markup/{Component.tsx → Base.tsx} +0 -0
package/package.json
CHANGED
|
@@ -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
|
-
{
|
|
54
|
-
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
</
|
|
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
|
+
}
|
|
@@ -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
|
|
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 =
|
|
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
|
-
|
|
133
|
+
// 변경: label 렌더는 Slot.Text 단일 경로로 통일하고, aria-labelledby는 텍스트일 때만 연결한다.
|
|
134
|
+
<Slot.Text id={labelId} className={CHECKBOX_LABEL_CLASSNAME}>
|
|
130
135
|
{label}
|
|
131
|
-
</
|
|
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
|
|
152
|
-
font-weight: var(--
|
|
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
|
|
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
|
|
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,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
|
|
4
|
+
import ChipClickableStyle from "./DefaultStyle";
|
|
4
5
|
import ChipInputStyle from "./InputStyle";
|
|
5
6
|
|
|
6
|
-
//
|
|
7
|
+
// chipStyle === "input" 조합에서 props를 ChipInputProps로 좁히기 위한 type guard.
|
|
7
8
|
const isChipInputProps = (props: ChipProps): props is ChipInputProps =>
|
|
8
|
-
props.
|
|
9
|
+
props.chipStyle === "input";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
* Chip Markup;
|
|
12
|
+
* Chip Markup; Style별 렌더 분기 Orchestrator 컴포넌트
|
|
12
13
|
* @component
|
|
13
14
|
* @param {ChipProps} props Chip 공통/종류별 props
|
|
14
|
-
* @param {"filter" | "
|
|
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
|
|
17
|
-
* @param {React.ReactNode} [props.leading] assist
|
|
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
|
|
20
|
-
* @param {boolean} [props.disabled] input
|
|
21
|
-
* @param {(event: MouseEvent<HTMLButtonElement>) => void} [props.onRemove] input
|
|
22
|
-
* @param {(event: MouseEvent<HTMLButtonElement>) => void} [props.onClick] interactive
|
|
23
|
-
* @param {"button" | "submit" | "reset"} [props.type] interactive
|
|
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
|
-
* - `
|
|
29
|
-
* - `
|
|
30
|
-
* - native HTML attrs는
|
|
31
|
-
* @returns {JSX.Element}
|
|
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
|
-
* <
|
|
36
|
+
* <ChipDefault chipStyle="filter" fill="solid" selected rounded>
|
|
34
37
|
* 전체
|
|
35
|
-
* </
|
|
38
|
+
* </ChipDefault>
|
|
36
39
|
* @example
|
|
37
|
-
* <
|
|
40
|
+
* <ChipDefault chipStyle="input" onRemove={event => console.log(event.type)}>
|
|
38
41
|
* 첨부파일.pdf
|
|
39
|
-
* </
|
|
42
|
+
* </ChipDefault>
|
|
40
43
|
*/
|
|
41
|
-
const
|
|
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
|
-
<
|
|
62
|
+
<ChipClickableStyle
|
|
63
|
+
{...props}
|
|
64
|
+
className={mergedClassName}
|
|
65
|
+
ref={ref as Ref<HTMLButtonElement>}
|
|
66
|
+
/>
|
|
55
67
|
);
|
|
56
68
|
});
|
|
57
69
|
|
|
58
|
-
|
|
70
|
+
ChipDefault.displayName = "Chip.Default";
|
|
59
71
|
|
|
60
|
-
export {
|
|
72
|
+
export { ChipDefault };
|
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
import { forwardRef } from "react";
|
|
2
2
|
import clsx from "clsx";
|
|
3
|
-
import type {
|
|
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 {
|
|
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
|
|
13
|
+
* @param {React.ReactNode} [props.leading] assist 스타일 좌측 slot
|
|
13
14
|
* @param {string} [props.className] root className
|
|
14
|
-
* @param {"filter" | "
|
|
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
|
-
* <
|
|
21
|
+
* <ChipClickableStyle chipStyle="assist" ref={buttonRef}>
|
|
19
22
|
* Assist
|
|
20
|
-
* </
|
|
23
|
+
* </ChipClickableStyle>
|
|
21
24
|
*/
|
|
22
|
-
const
|
|
25
|
+
const ChipClickableStyle = forwardRef<
|
|
23
26
|
HTMLButtonElement,
|
|
24
|
-
|
|
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
|
-
|
|
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
|
|
41
|
-
const isAssist =
|
|
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
|
|
51
|
-
|
|
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
|
|
75
|
+
<span
|
|
76
|
+
className="chip-leading chip-clickable-leading"
|
|
77
|
+
aria-hidden="true"
|
|
78
|
+
>
|
|
64
79
|
{leading}
|
|
65
80
|
</span>
|
|
66
81
|
) : null}
|
|
67
|
-
<
|
|
82
|
+
<ChipLabel className="chip-clickable-label">{children}</ChipLabel>
|
|
68
83
|
</button>
|
|
69
84
|
);
|
|
70
85
|
},
|
|
71
86
|
);
|
|
72
87
|
|
|
73
|
-
|
|
88
|
+
ChipClickableStyle.displayName = "ChipClickableStyle";
|
|
74
89
|
|
|
75
|
-
export default
|
|
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
|
|
63
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|