@uniai-fe/uds-primitives 0.3.15 → 0.3.17

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 CHANGED
@@ -1760,6 +1760,7 @@
1760
1760
  box-sizing: border-box;
1761
1761
  margin: 0;
1762
1762
  width: fit-content;
1763
+ max-width: 100%;
1763
1764
  transition: background-color 0.16s ease, color 0.16s ease, border-color 0.16s ease, opacity 0.16s ease;
1764
1765
  }
1765
1766
 
@@ -1863,12 +1864,13 @@ figure.chip {
1863
1864
  }
1864
1865
 
1865
1866
  .chip-label {
1866
- display: flex;
1867
- align-items: center;
1868
- gap: var(--theme-chip-label-gap);
1869
1867
  color: inherit;
1870
- line-height: 1;
1868
+ line-height: 1em;
1871
1869
  white-space: nowrap;
1870
+ width: fit-content;
1871
+ max-width: 100%;
1872
+ overflow: hidden;
1873
+ text-overflow: ellipsis;
1872
1874
  }
1873
1875
 
1874
1876
  .chip-remove-button {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/uds-primitives",
3
- "version": "0.3.15",
3
+ "version": "0.3.17",
4
4
  "description": "UNIAI Design System; Primitives Components Package",
5
5
  "type": "module",
6
6
  "private": false,
@@ -20,13 +20,15 @@ export default function CalendarCore({
20
20
  onChange,
21
21
  datePickerProps,
22
22
  }: CalendarGridProps) {
23
- const { valueFormat: _deprecatedValueFormat, ...safeDatePickerProps } =
24
- (datePickerProps ?? {}) as CalendarDatePickerProps & {
25
- /**
26
- * deprecated DatePicker 표시 포맷 옵션
27
- */
28
- valueFormat?: unknown;
29
- };
23
+ const { valueFormat, ...safeDatePickerProps } = (datePickerProps ??
24
+ {}) as CalendarDatePickerProps & {
25
+ /**
26
+ * deprecated DatePicker 표시 포맷 옵션
27
+ */
28
+ valueFormat?: unknown;
29
+ };
30
+ // 변경: valueFormat은 의도적으로 폐기된 옵션이므로 명시적으로 무시한다.
31
+ void valueFormat;
30
32
 
31
33
  // 기본 DatePicker 옵션/스타일 책임을 Calendar.Core에 고정한다.
32
34
  const resolvedDatePickerProps = {
@@ -1,127 +1,55 @@
1
1
  import { forwardRef, type Ref } from "react";
2
- import RemoveIcon from "../img/remove.svg";
3
2
  import type { ChipInputProps, ChipProps } from "../types";
4
- import {
5
- CHIP_LABEL_CLASSNAME,
6
- CHIP_LEADING_CLASSNAME,
7
- CHIP_REMOVE_BUTTON_CLASSNAME,
8
- composeChipClassName,
9
- } from "../utils";
3
+ import ChipDefaultStyle from "./DefaultStyle";
4
+ import ChipInputStyle from "./InputStyle";
10
5
 
11
6
  // kind === "input" 조합에서 props를 ChipInputProps로 좁히기 위한 type guard.
12
7
  const isChipInputProps = (props: ChipProps): props is ChipInputProps =>
13
8
  props.kind === "input";
14
9
 
15
10
  /**
16
- * Chip 종류별로 interactive/input 구조가 달라 별도 분기한다.
11
+ * Chip Markup; Kind별 렌더 분기 Orchestrator 컴포넌트
17
12
  * @component
18
- * @param {ChipProps} props
19
- * @param {"filter" | "filter-rounded" | "assist" | "input"} [props.kind="filter"] chip kind.
20
- * @param {boolean} [props.selected] 선택 상태. filter 계열에서 aria-pressed로 노출.
21
- * @param {React.ReactNode} [props.leading] assist kind 전용 leading slot.
22
- * @param {"default" | "table"} [props.size="default"] chip size 축. table은 셀 콘텐츠용 compact 규격이다.
23
- * @param {string} [props.removeButtonLabel="선택 항목 삭제"] input kind 제거 버튼 라벨.
24
- * @param {boolean} [props.disabled] input kind 상호작용 비활성화 여부. true이면 remove 버튼을 렌더링하지 않는다.
25
- * @param {(event: MouseEvent<HTMLButtonElement>) => void} [props.onRemove] input kind 제거 핸들러.
26
- * @param {string} [props.className] root className.
13
+ * @param {ChipProps} props Chip 공통/종류별 props
14
+ * @param {"filter" | "filter-rounded" | "assist" | "input"} [props.kind="filter"] Chip 종류
15
+ * @param {React.ReactNode} [props.children] Chip 라벨/콘텐츠
16
+ * @param {boolean} [props.selected] 선택 상태; filter 계열에서 `aria-pressed`로 반영
17
+ * @param {React.ReactNode} [props.leading] assist kind 좌측 slot
18
+ * @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
24
+ * @param {string} [props.className] root className
25
+ * @param {string} [props.id] root id
26
+ * @param {string} [props.title] root title
27
+ * @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 요소
32
+ * @example
33
+ * <Chip kind="filter" selected>
34
+ * 전체
35
+ * </Chip>
36
+ * @example
37
+ * <Chip kind="input" onRemove={event => console.log(event.type)}>
38
+ * 첨부파일.pdf
39
+ * </Chip>
27
40
  */
28
41
  const Chip = forwardRef<HTMLElement, ChipProps>((props, ref) => {
29
42
  if (isChipInputProps(props)) {
30
- // input kind는 figure + remove 버튼 조합으로 구성한다.
31
- const {
32
- children,
33
- className,
34
- removeButtonLabel = "선택 항목 삭제",
35
- onRemove,
36
- disabled = false,
37
- selected,
38
- leading,
39
- size = "default",
40
- ...restProps
41
- } = props;
42
- const removable = !disabled && typeof onRemove === "function";
43
- const hasLeading = Boolean(leading);
44
- const combinedClassName = composeChipClassName({
45
- kind: "input",
46
- selected,
47
- hasLeading,
48
- removable,
49
- className,
50
- });
51
-
52
43
  return (
53
- <figure
54
- {...restProps}
55
- ref={ref as Ref<HTMLElementTagNameMap["figure"]>}
56
- className={combinedClassName}
57
- data-kind="input"
58
- data-removable={removable ? "true" : "false"}
59
- data-size={size}
60
- data-selected={selected ? "true" : undefined}
61
- data-has-leading={hasLeading ? "true" : undefined}
62
- data-disabled={disabled ? "true" : undefined}
63
- aria-disabled={disabled ? "true" : undefined}
64
- >
65
- <span className={CHIP_LABEL_CLASSNAME}>{children}</span>
66
- {removable ? (
67
- <button
68
- type="button"
69
- className={CHIP_REMOVE_BUTTON_CLASSNAME}
70
- aria-label={removeButtonLabel}
71
- onClick={onRemove}
72
- >
73
- <RemoveIcon aria-hidden="true" />
74
- </button>
75
- ) : null}
76
- </figure>
44
+ <ChipInputStyle
45
+ {...props}
46
+ forwardedRef={ref as Ref<HTMLElementTagNameMap["figure"]>}
47
+ />
77
48
  );
78
49
  }
79
50
 
80
- const {
81
- children,
82
- selected,
83
- leading,
84
- className,
85
- kind,
86
- size = "default",
87
- type,
88
- ...restProps
89
- } = props;
90
- const resolvedKind = kind ?? "filter";
91
- const isAssist = resolvedKind === "assist";
92
- const hasLeading = isAssist && Boolean(leading);
93
- const combinedClassName = composeChipClassName({
94
- kind: resolvedKind,
95
- selected,
96
- hasLeading,
97
- removable: false,
98
- className,
99
- });
100
-
101
51
  return (
102
- <button
103
- {...restProps}
104
- ref={ref as Ref<HTMLButtonElement>}
105
- type={type ?? "button"}
106
- className={combinedClassName}
107
- data-kind={resolvedKind}
108
- data-size={size}
109
- data-selected={selected ? "true" : undefined}
110
- data-has-leading={hasLeading ? "true" : undefined}
111
- aria-pressed={
112
- typeof selected === "boolean" &&
113
- (resolvedKind === "filter" || resolvedKind === "filter-rounded")
114
- ? selected
115
- : undefined
116
- }
117
- >
118
- {isAssist && leading ? (
119
- <span className={CHIP_LEADING_CLASSNAME} aria-hidden="true">
120
- {leading}
121
- </span>
122
- ) : null}
123
- <span className={CHIP_LABEL_CLASSNAME}>{children}</span>
124
- </button>
52
+ <ChipDefaultStyle {...props} forwardedRef={ref as Ref<HTMLButtonElement>} />
125
53
  );
126
54
  });
127
55
 
@@ -0,0 +1,62 @@
1
+ import clsx from "clsx";
2
+ import type { ChipDefaultStyleComponentProps } from "../types";
3
+
4
+ /**
5
+ * Chip Markup; Default/Interactive 스타일 렌더 컴포넌트
6
+ * @component
7
+ * @param {ChipDefaultStyleComponentProps} props interactive 스타일 props
8
+ * @param {Ref<HTMLButtonElement>} props.forwardedRef button ref
9
+ * @param {React.ReactNode} [props.children] chip 라벨 콘텐츠
10
+ * @param {boolean} [props.selected] 선택 상태
11
+ * @param {React.ReactNode} [props.leading] assist kind 좌측 slot
12
+ * @param {string} [props.className] root className
13
+ * @param {"filter" | "filter-rounded" | "assist"} [props.kind] interactive kind
14
+ * @param {"default" | "table"} [props.size] size 축
15
+ * @param {"button" | "submit" | "reset"} [props.type] button type
16
+ * @example
17
+ * <ChipDefaultStyle kind="assist" forwardedRef={buttonRef}>
18
+ * Assist
19
+ * </ChipDefaultStyle>
20
+ */
21
+ export default function ChipDefaultStyle({
22
+ forwardedRef,
23
+ children,
24
+ selected,
25
+ leading,
26
+ className,
27
+ kind,
28
+ size = "default",
29
+ type,
30
+ ...restProps
31
+ }: ChipDefaultStyleComponentProps) {
32
+ // 변경: props 구조분해는 함수 시그니처에서만 수행하고, 본문 내 `const { ... } = props` 패턴을 제거한다.
33
+ const resolvedKind = kind ?? "filter";
34
+ const isAssist = resolvedKind === "assist";
35
+ const hasLeading = isAssist && Boolean(leading);
36
+
37
+ return (
38
+ <button
39
+ {...restProps}
40
+ ref={forwardedRef}
41
+ type={type ?? "button"}
42
+ className={clsx("chip", className)}
43
+ data-kind={resolvedKind}
44
+ data-size={size}
45
+ data-selected={selected ? "true" : undefined}
46
+ data-has-leading={hasLeading ? "true" : undefined}
47
+ aria-pressed={
48
+ typeof selected === "boolean" &&
49
+ (resolvedKind === "filter" || resolvedKind === "filter-rounded")
50
+ ? selected
51
+ : undefined
52
+ }
53
+ >
54
+ {isAssist && leading ? (
55
+ <span className="chip-leading" aria-hidden="true">
56
+ {leading}
57
+ </span>
58
+ ) : null}
59
+ <span className="chip-label">{children}</span>
60
+ </button>
61
+ );
62
+ }
@@ -0,0 +1,72 @@
1
+ import clsx from "clsx";
2
+ import type { MouseEvent } from "react";
3
+ import type { ChipInputStyleProps } from "../types";
4
+ import ChipRemoveButton from "./RemoveButton";
5
+
6
+ /**
7
+ * Chip Markup; Input 스타일 렌더 컴포넌트
8
+ * @component
9
+ * @param {ChipInputStyleProps} props input 스타일 props
10
+ * @param {Ref<HTMLElementTagNameMap["figure"]>} props.forwardedRef figure ref
11
+ * @param {React.ReactNode} [props.children] chip 라벨 콘텐츠
12
+ * @param {string} [props.className] root className
13
+ * @param {string} [props.removeButtonLabel="선택 항목 삭제"] remove 버튼 라벨
14
+ * @param {(event: MouseEvent<HTMLButtonElement>) => void} [props.onRemove] remove 클릭 핸들러
15
+ * @param {boolean} [props.disabled] 비활성화 여부
16
+ * @param {boolean} [props.selected] 선택 상태
17
+ * @param {React.ReactNode} [props.leading] leading slot 존재 여부 판단용 값
18
+ * @param {"default" | "table"} [props.size] size 축
19
+ * @example
20
+ * <ChipInputStyle
21
+ * forwardedRef={figureRef}
22
+ * onRemove={event => console.log(event.type)}
23
+ * >
24
+ * report.pdf
25
+ * </ChipInputStyle>
26
+ */
27
+ export default function ChipInputStyle({
28
+ forwardedRef,
29
+ children,
30
+ className,
31
+ removeButtonLabel = "선택 항목 삭제",
32
+ onRemove,
33
+ disabled = false,
34
+ selected,
35
+ leading,
36
+ size = "default",
37
+ ...restProps
38
+ }: ChipInputStyleProps) {
39
+ // 변경: props 구조분해는 함수 시그니처에서만 수행하고, 본문 내 `const { ... } = props` 패턴을 제거한다.
40
+ const removable = !disabled && typeof onRemove === "function";
41
+ const hasLeading = Boolean(leading);
42
+ /**
43
+ * 변경: remove 버튼 클릭이 chip root click으로 버블링되지 않도록 차단한다.
44
+ */
45
+ const handleRemoveClick = (event: MouseEvent<HTMLButtonElement>) => {
46
+ event.stopPropagation();
47
+ onRemove?.(event);
48
+ };
49
+
50
+ return (
51
+ <figure
52
+ {...restProps}
53
+ ref={forwardedRef}
54
+ className={clsx("chip", className)}
55
+ data-kind="input"
56
+ data-removable={removable ? "true" : "false"}
57
+ data-size={size}
58
+ data-selected={selected ? "true" : undefined}
59
+ data-has-leading={hasLeading ? "true" : undefined}
60
+ data-disabled={disabled ? "true" : undefined}
61
+ aria-disabled={disabled ? "true" : undefined}
62
+ >
63
+ <span className="chip-label">{children}</span>
64
+ {removable ? (
65
+ <ChipRemoveButton
66
+ removeButtonLabel={removeButtonLabel}
67
+ onRemove={handleRemoveClick}
68
+ />
69
+ ) : null}
70
+ </figure>
71
+ );
72
+ }
@@ -0,0 +1,30 @@
1
+ import RemoveIcon from "../img/remove.svg";
2
+ import type { ChipRemoveButtonProps } from "../types";
3
+
4
+ /**
5
+ * Chip Markup; Input remove 버튼 컴포넌트
6
+ * @component
7
+ * @param {ChipRemoveButtonProps} props remove 버튼 props
8
+ * @param {string} props.removeButtonLabel 접근성 라벨
9
+ * @param {(event: MouseEvent<HTMLButtonElement>) => void} props.onRemove remove 클릭 핸들러
10
+ * @example
11
+ * <ChipRemoveButton
12
+ * removeButtonLabel="첨부파일 삭제"
13
+ * onRemove={event => console.log(event.type)}
14
+ * />
15
+ */
16
+ export default function ChipRemoveButton({
17
+ removeButtonLabel,
18
+ onRemove,
19
+ }: ChipRemoveButtonProps) {
20
+ return (
21
+ <button
22
+ type="button"
23
+ className="chip-remove-button"
24
+ aria-label={removeButtonLabel}
25
+ onClick={onRemove}
26
+ >
27
+ <RemoveIcon aria-hidden="true" />
28
+ </button>
29
+ );
30
+ }
@@ -23,6 +23,7 @@
23
23
  box-sizing: border-box;
24
24
  margin: 0;
25
25
  width: fit-content;
26
+ max-width: 100%;
26
27
  transition:
27
28
  background-color 0.16s ease,
28
29
  color 0.16s ease,
@@ -132,12 +133,16 @@ figure.chip {
132
133
  }
133
134
 
134
135
  .chip-label {
135
- display: flex;
136
- align-items: center;
137
- gap: var(--theme-chip-label-gap);
136
+ // display: flex;
137
+ // align-items: center;
138
+ // gap: var(--theme-chip-label-gap);
138
139
  color: inherit;
139
- line-height: 1;
140
+ line-height: 1em;
140
141
  white-space: nowrap;
142
+ width: fit-content;
143
+ max-width: 100%;
144
+ overflow: hidden;
145
+ text-overflow: ellipsis;
141
146
  }
142
147
 
143
148
  .chip-remove-button {
@@ -1,2 +1,3 @@
1
1
  export * from "./options";
2
2
  export type * from "./props";
3
+ export type * from "./props-internal";
@@ -0,0 +1,83 @@
1
+ import type { Ref } from "react";
2
+ import type { MouseEvent } from "react";
3
+ import type { ChipInputProps, ChipProps } from "./props";
4
+
5
+ /**
6
+ * Chip Types; Interactive 스타일 props
7
+ * @typedef {Exclude<ChipProps, ChipInputProps>} ChipDefaultStyleProps
8
+ * @description
9
+ * - `ChipProps`에서 `kind="input"` 조합을 제외한 interactive 전용 props 집합이다.
10
+ * - `button` native attrs(`onClick`, `id`, `title`, `aria-*`, `data-*`)를 함께 포함한다.
11
+ */
12
+ export type ChipDefaultStyleProps = Exclude<ChipProps, ChipInputProps>;
13
+
14
+ /**
15
+ * Chip Types; Input 스타일 figure ref 타입
16
+ * @typedef {Ref<HTMLElementTagNameMap["figure"]>} ChipInputFigureRef
17
+ */
18
+ export type ChipInputFigureRef = Ref<HTMLElementTagNameMap["figure"]>;
19
+
20
+ /**
21
+ * Chip Types; Interactive 스타일 button ref 타입
22
+ * @typedef {Ref<HTMLButtonElement>} ChipDefaultButtonRef
23
+ */
24
+ export type ChipDefaultButtonRef = Ref<HTMLButtonElement>;
25
+
26
+ /**
27
+ * Chip Types; remove 버튼 props
28
+ * @property {string} removeButtonLabel 접근성 라벨
29
+ * @property {(event: MouseEvent<HTMLButtonElement>) => void} onRemove remove 클릭 핸들러
30
+ */
31
+ export interface ChipRemoveButtonProps {
32
+ /**
33
+ * 접근성 라벨
34
+ */
35
+ removeButtonLabel: string;
36
+ /**
37
+ * remove 클릭 핸들러
38
+ */
39
+ onRemove: (event: MouseEvent<HTMLButtonElement>) => void;
40
+ }
41
+
42
+ /**
43
+ * Chip Types; Input 스타일 컴포넌트 props
44
+ * @extends ChipInputProps
45
+ * @property {Ref<HTMLElementTagNameMap["figure"]>} forwardedRef figure ref
46
+ * @property {ReactNode} [children] chip 라벨 콘텐츠
47
+ * @property {string} [className] root className
48
+ * @property {string} [removeButtonLabel] remove 버튼 접근성 라벨
49
+ * @property {(event: MouseEvent<HTMLButtonElement>) => void} [onRemove] remove 클릭 핸들러
50
+ * @property {boolean} [disabled] 비활성화 여부
51
+ * @property {boolean} [selected] 선택 상태
52
+ * @property {ReactNode} [leading] leading slot
53
+ * @property {"default" | "table"} [size] size 축
54
+ * @description
55
+ * - `ChipInputProps`를 상속하므로 `figure` native attrs(`id`, `title`, `aria-*`, `data-*`)를 함께 포함한다.
56
+ */
57
+ export interface ChipInputStyleProps extends ChipInputProps {
58
+ /**
59
+ * figure ref
60
+ */
61
+ forwardedRef: ChipInputFigureRef;
62
+ }
63
+
64
+ /**
65
+ * Chip Types; Interactive 스타일 컴포넌트 props
66
+ * @extends ChipDefaultStyleProps
67
+ * @property {Ref<HTMLButtonElement>} forwardedRef button ref
68
+ * @property {ReactNode} [children] chip 라벨 콘텐츠
69
+ * @property {boolean} [selected] 선택 상태
70
+ * @property {ReactNode} [leading] assist kind leading slot
71
+ * @property {string} [className] root className
72
+ * @property {"filter" | "filter-rounded" | "assist"} [kind] interactive kind
73
+ * @property {"default" | "table"} [size] size 축
74
+ * @property {"button" | "submit" | "reset"} [type] button type
75
+ * @description
76
+ * - `ChipDefaultStyleProps`를 상속하므로 `button` native attrs(`onClick`, `id`, `title`, `aria-*`, `data-*`)를 함께 포함한다.
77
+ */
78
+ export interface ChipDefaultStyleComponentProps extends ChipDefaultStyleProps {
79
+ /**
80
+ * button ref
81
+ */
82
+ forwardedRef: ChipDefaultButtonRef;
83
+ }
@@ -44,6 +44,8 @@ import type { UseInputFileOptions, UseInputFileResult } from "../types";
44
44
  * {meta.name}
45
45
  * </Input.File.List.Item>
46
46
  * ));
47
+ *
48
+ * // 5) API 호출은 templates 또는 서비스 레이어에서 분리 구현한다.
47
49
  * ```
48
50
  */
49
51
  export const useInputFile = ({