@uniai-fe/uds-primitives 0.3.17 → 0.3.19
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 +224 -160
- package/package.json +18 -9
- package/src/components/alternate/styles/alternate.scss +11 -8
- 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 +39 -25
- package/src/components/chip/markup/DefaultStyle.tsx +76 -48
- package/src/components/chip/markup/InputStyle.tsx +71 -48
- 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 +15 -51
- package/src/components/chip/types/props.ts +127 -46
- package/src/components/chip/utils/index.ts +1 -1
- 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/styles/foundation.scss +15 -0
- package/src/components/input/styles/variables.scss +16 -3
- 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/markup/foundation/Base.tsx +3 -8
- package/src/components/select/markup/foundation/Selected.tsx +3 -2
- package/src/components/select/markup/multiple/Multiple.tsx +143 -9
- package/src/components/select/styles/select.scss +1 -1
- package/src/components/select/styles/variables.scss +13 -12
- package/src/components/select/types/props.ts +21 -2
- 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/styles/foundation.scss +384 -310
- 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
|
@@ -8,6 +8,7 @@ import { Dropdown } from "../../../dropdown/markup";
|
|
|
8
8
|
import type { DropdownSize } from "../../../dropdown/types";
|
|
9
9
|
import type { SelectMultipleComponentProps } from "../../types/props";
|
|
10
10
|
import type { SelectMultipleTag } from "../../types/multiple";
|
|
11
|
+
import type { SelectDropdownOption } from "../../types/option";
|
|
11
12
|
import { SelectMultipleSelectedChip } from "./SelectedChip";
|
|
12
13
|
import { SelectTriggerBase, SelectTriggerSelected } from "../foundation";
|
|
13
14
|
import { useSelectDropdownOpenState } from "../../hooks";
|
|
@@ -15,6 +16,8 @@ import { useSelectDropdownOpenState } from "../../hooks";
|
|
|
15
16
|
const isSameIdList = (previousIds: string[], nextIds: string[]) =>
|
|
16
17
|
previousIds.length === nextIds.length &&
|
|
17
18
|
previousIds.every((selectedId, index) => selectedId === nextIds[index]);
|
|
19
|
+
// 변경: synthetic "전체" 옵션 id의 기본 키다. 실제 options id와 충돌하면 suffix를 붙여 회피한다.
|
|
20
|
+
const SELECT_MULTIPLE_ALL_OPTION_BASE_ID = "__select_multiple_all__";
|
|
18
21
|
|
|
19
22
|
/**
|
|
20
23
|
* Select trigger for multi select; 선택된 tag들을 chip 형태로 렌더링한다.
|
|
@@ -44,6 +47,8 @@ const isSameIdList = (previousIds: string[], nextIds: string[]) =>
|
|
|
44
47
|
* @param {boolean} [props.open] controlled open 상태
|
|
45
48
|
* @param {boolean} [props.defaultOpen] uncontrolled 초기 open 상태
|
|
46
49
|
* @param {(open: boolean) => void} [props.onOpenChange] open 상태 변경 콜백
|
|
50
|
+
* @param {boolean} [props.showSelectAllOption] dropdown 첫 번째에 "전체" 옵션 노출 여부
|
|
51
|
+
* @param {React.ReactNode} [props.selectAllLabel="전체"] 전체 옵션 라벨
|
|
47
52
|
*/
|
|
48
53
|
const SelectMultipleTrigger = forwardRef<
|
|
49
54
|
HTMLElement,
|
|
@@ -76,6 +81,8 @@ const SelectMultipleTrigger = forwardRef<
|
|
|
76
81
|
open,
|
|
77
82
|
defaultOpen,
|
|
78
83
|
onOpenChange,
|
|
84
|
+
showSelectAllOption,
|
|
85
|
+
selectAllLabel = "전체",
|
|
79
86
|
...rest
|
|
80
87
|
},
|
|
81
88
|
ref,
|
|
@@ -104,6 +111,39 @@ const SelectMultipleTrigger = forwardRef<
|
|
|
104
111
|
() => new Map(options.map(option => [option.id, option])),
|
|
105
112
|
[options],
|
|
106
113
|
);
|
|
114
|
+
// 변경: 전체 선택 대상은 disabled option을 제외한 "선택 가능 옵션"으로 한정한다.
|
|
115
|
+
const selectableOptions = useMemo(
|
|
116
|
+
() => options.filter(option => !option.disabled),
|
|
117
|
+
[options],
|
|
118
|
+
);
|
|
119
|
+
// 변경: 전체 선택 토글 계산을 위해 선택 가능 옵션 id 배열을 별도로 보관한다.
|
|
120
|
+
const selectableOptionIds = useMemo(
|
|
121
|
+
() => selectableOptions.map(option => option.id),
|
|
122
|
+
[selectableOptions],
|
|
123
|
+
);
|
|
124
|
+
// 변경: 전체 해제 시 빠른 membership 체크를 위해 Set 형태도 함께 보관한다.
|
|
125
|
+
const selectableOptionIdSet = useMemo(
|
|
126
|
+
() => new Set(selectableOptionIds),
|
|
127
|
+
[selectableOptionIds],
|
|
128
|
+
);
|
|
129
|
+
const allOptionId = useMemo(() => {
|
|
130
|
+
// 현재 전달된 옵션 id 집합을 먼저 만든다.
|
|
131
|
+
const existingIdSet = new Set(options.map(option => option.id));
|
|
132
|
+
// "__select_multiple_all__"가 비어 있으면 그대로 사용한다.
|
|
133
|
+
if (!existingIdSet.has(SELECT_MULTIPLE_ALL_OPTION_BASE_ID)) {
|
|
134
|
+
return SELECT_MULTIPLE_ALL_OPTION_BASE_ID;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 충돌 시 "__select_multiple_all___1", "_2" 형태로 suffix를 증가시키며 빈 id를 찾는다.
|
|
138
|
+
let offset = 1;
|
|
139
|
+
let nextId = `${SELECT_MULTIPLE_ALL_OPTION_BASE_ID}_${offset}`;
|
|
140
|
+
while (existingIdSet.has(nextId)) {
|
|
141
|
+
offset += 1;
|
|
142
|
+
nextId = `${SELECT_MULTIPLE_ALL_OPTION_BASE_ID}_${offset}`;
|
|
143
|
+
}
|
|
144
|
+
// 최종적으로 충돌하지 않는 synthetic all-option id를 반환한다.
|
|
145
|
+
return nextId;
|
|
146
|
+
}, [options]);
|
|
107
147
|
|
|
108
148
|
/**
|
|
109
149
|
* 4) uncontrolled 초기 선택값 계산
|
|
@@ -154,17 +194,46 @@ const SelectMultipleTrigger = forwardRef<
|
|
|
154
194
|
* - controlled: selectedOptionIds 우선
|
|
155
195
|
* - uncontrolled: 내부 state 사용
|
|
156
196
|
*/
|
|
157
|
-
const resolvedSelectedIds = useMemo(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
197
|
+
const resolvedSelectedIds = useMemo(() => {
|
|
198
|
+
const sourceSelectedIds = isSelectionControlled
|
|
199
|
+
? (selectedOptionIds ?? [])
|
|
200
|
+
: uncontrolledSelectedOptionIds;
|
|
201
|
+
|
|
202
|
+
// 변경: 실제 option id만 유지해 내부 synthetic all-option id가 상태에 저장되지 않도록 차단한다.
|
|
203
|
+
// 이 필터 덕분에 최종 선택 상태는 "실 데이터 옵션"만 source of truth로 유지된다.
|
|
204
|
+
return sourceSelectedIds.filter(selectedId => optionMap.has(selectedId));
|
|
205
|
+
}, [
|
|
206
|
+
isSelectionControlled,
|
|
207
|
+
optionMap,
|
|
208
|
+
selectedOptionIds,
|
|
209
|
+
uncontrolledSelectedOptionIds,
|
|
210
|
+
]);
|
|
164
211
|
const selectedIdSet = useMemo(
|
|
165
212
|
() => new Set(resolvedSelectedIds),
|
|
166
213
|
[resolvedSelectedIds],
|
|
167
214
|
);
|
|
215
|
+
const isAllSelectableOptionsSelected = useMemo(
|
|
216
|
+
// 선택 가능 옵션이 1개 이상이고, 그 id가 전부 selectedIdSet에 포함될 때만 true다.
|
|
217
|
+
() =>
|
|
218
|
+
selectableOptionIds.length > 0 &&
|
|
219
|
+
selectableOptionIds.every(selectableId =>
|
|
220
|
+
selectedIdSet.has(selectableId),
|
|
221
|
+
),
|
|
222
|
+
[selectableOptionIds, selectedIdSet],
|
|
223
|
+
);
|
|
224
|
+
const selectAllOption = useMemo<SelectDropdownOption>(
|
|
225
|
+
// 변경: "전체" 옵션은 렌더링을 위한 synthetic option 객체다.
|
|
226
|
+
// 실제 options 배열에 저장하지 않고 render 단계에서만 주입한다.
|
|
227
|
+
() => ({
|
|
228
|
+
id: allOptionId,
|
|
229
|
+
value: allOptionId,
|
|
230
|
+
label: selectAllLabel,
|
|
231
|
+
multiple: true,
|
|
232
|
+
// 선택 가능한 옵션이 하나도 없으면 "전체" 항목도 disabled로 처리한다.
|
|
233
|
+
disabled: selectableOptionIds.length === 0,
|
|
234
|
+
}),
|
|
235
|
+
[allOptionId, selectAllLabel, selectableOptionIds.length],
|
|
236
|
+
);
|
|
168
237
|
|
|
169
238
|
/**
|
|
170
239
|
* 8) 표시 라벨 계산(보조)
|
|
@@ -248,6 +317,16 @@ const SelectMultipleTrigger = forwardRef<
|
|
|
248
317
|
*/
|
|
249
318
|
const panelSize = (dropdownSize ?? size) as DropdownSize;
|
|
250
319
|
const hasOptions = options.length > 0;
|
|
320
|
+
// "전체" 항목은 옵션이 존재할 때만 의미가 있으므로 hasOptions와 함께 gating한다.
|
|
321
|
+
const shouldRenderSelectAllOption = Boolean(
|
|
322
|
+
showSelectAllOption && hasOptions,
|
|
323
|
+
);
|
|
324
|
+
const renderedOptions = useMemo(
|
|
325
|
+
// 변경: all-option은 dropdown 첫 행 고정 요구사항에 맞춰 항상 배열 맨 앞에 주입한다.
|
|
326
|
+
() =>
|
|
327
|
+
shouldRenderSelectAllOption ? [selectAllOption, ...options] : options,
|
|
328
|
+
[options, selectAllOption, shouldRenderSelectAllOption],
|
|
329
|
+
);
|
|
251
330
|
const MAX_VISIBLE_TAGS = 3;
|
|
252
331
|
const visibleTags = hasTags ? derivedTags.slice(0, MAX_VISIBLE_TAGS) : [];
|
|
253
332
|
const overflowCount = hasTags
|
|
@@ -262,6 +341,55 @@ const SelectMultipleTrigger = forwardRef<
|
|
|
262
341
|
* - legacy onOptionSelect는 하위호환으로 유지
|
|
263
342
|
*/
|
|
264
343
|
const handleOptionSelect = (optionId: string) => {
|
|
344
|
+
// 현재 클릭된 옵션이 synthetic all-option인지 먼저 판별한다.
|
|
345
|
+
const isSelectAllOption =
|
|
346
|
+
shouldRenderSelectAllOption && optionId === allOptionId;
|
|
347
|
+
if (isSelectAllOption) {
|
|
348
|
+
// all-option 클릭 분기
|
|
349
|
+
// - 이미 전체 선택 상태면: selectable option들만 제거(전체 해제)
|
|
350
|
+
// - 일부 선택 상태면: selectable option들을 전부 합집합으로 추가(전체 선택)
|
|
351
|
+
const nextSelectedOptionIds = isAllSelectableOptionsSelected
|
|
352
|
+
? resolvedSelectedIds.filter(
|
|
353
|
+
selectedId => !selectableOptionIdSet.has(selectedId),
|
|
354
|
+
)
|
|
355
|
+
: Array.from(
|
|
356
|
+
new Set([...resolvedSelectedIds, ...selectableOptionIds]),
|
|
357
|
+
);
|
|
358
|
+
// 최종 id 배열을 실제 option 객체 배열로 역매핑해 payload 계약을 유지한다.
|
|
359
|
+
const nextSelectedOptions = nextSelectedOptionIds
|
|
360
|
+
.map(selectedId => optionMap.get(selectedId))
|
|
361
|
+
.filter(
|
|
362
|
+
(
|
|
363
|
+
selectedOption,
|
|
364
|
+
): selectedOption is NonNullable<typeof selectedOption> =>
|
|
365
|
+
Boolean(selectedOption),
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
// uncontrolled 모드에서는 내부 state를 즉시 동기화한다.
|
|
369
|
+
if (!isSelectionControlled) {
|
|
370
|
+
setUncontrolledSelectedOptionIds(nextSelectedOptionIds);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// onChange payload는 기존 multiple 계약을 그대로 사용한다.
|
|
374
|
+
// currentOption은 synthetic all-option으로 전달해 "전체 선택 액션"임을 호출부에서 구분 가능하게 한다.
|
|
375
|
+
onChange?.({
|
|
376
|
+
mode: "multiple",
|
|
377
|
+
selectedOptionIds: nextSelectedOptionIds,
|
|
378
|
+
selectedValues: nextSelectedOptions.map(
|
|
379
|
+
selectedOption => selectedOption.value,
|
|
380
|
+
),
|
|
381
|
+
selectedOptions: nextSelectedOptions,
|
|
382
|
+
currentOption: selectAllOption,
|
|
383
|
+
isSelected: !isAllSelectableOptionsSelected,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// legacy 하위호환 콜백도 동일하게 호출한다.
|
|
387
|
+
onOptionSelect?.(selectAllOption);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// 일반 옵션 클릭 분기
|
|
392
|
+
// 이미 선택된 id면 제거, 미선택 id면 추가하는 기본 multiple toggle 로직이다.
|
|
265
393
|
const wasSelected = selectedIdSet.has(optionId);
|
|
266
394
|
const nextSelectedOptionIds = wasSelected
|
|
267
395
|
? resolvedSelectedIds.filter(selectedId => selectedId !== optionId)
|
|
@@ -370,7 +498,7 @@ const SelectMultipleTrigger = forwardRef<
|
|
|
370
498
|
{hasOptions ? (
|
|
371
499
|
<>
|
|
372
500
|
{/* multi select 전용 옵션을 Dropdown.Menu.Item으로 노출한다. */}
|
|
373
|
-
{
|
|
501
|
+
{renderedOptions.map(option => (
|
|
374
502
|
<Dropdown.Menu.Item
|
|
375
503
|
key={option.id}
|
|
376
504
|
label={option.label}
|
|
@@ -379,7 +507,13 @@ const SelectMultipleTrigger = forwardRef<
|
|
|
379
507
|
left={option.left}
|
|
380
508
|
right={option.right}
|
|
381
509
|
multiple
|
|
382
|
-
isSelected={
|
|
510
|
+
isSelected={
|
|
511
|
+
// synthetic all-option의 checked 상태는 "전체가 선택되었는가"로 계산한다.
|
|
512
|
+
// 일반 option은 기존 selectedIdSet membership으로 계산한다.
|
|
513
|
+
option.id === allOptionId
|
|
514
|
+
? isAllSelectableOptionsSelected
|
|
515
|
+
: selectedIdSet.has(option.id)
|
|
516
|
+
}
|
|
383
517
|
onSelect={event => {
|
|
384
518
|
if (option.disabled || isInteractionBlocked) {
|
|
385
519
|
event.preventDefault();
|
|
@@ -66,18 +66,19 @@
|
|
|
66
66
|
--input-table-text-large-line-height
|
|
67
67
|
);
|
|
68
68
|
--select-table-text-large-weight: var(--input-table-text-large-weight);
|
|
69
|
-
|
|
70
|
-
--select-text-small-
|
|
71
|
-
--select-text-small-
|
|
72
|
-
--select-text-small-
|
|
73
|
-
--select-text-
|
|
74
|
-
--select-text-medium-
|
|
75
|
-
--select-text-medium-
|
|
76
|
-
--select-text-medium-
|
|
77
|
-
--select-text-
|
|
78
|
-
--select-text-large-
|
|
79
|
-
--select-text-large-
|
|
80
|
-
--select-text-large-
|
|
69
|
+
/* 변경: trigger 텍스트 토큰을 input text 토큰과 직접 매핑해 size별 typography를 동기화한다. */
|
|
70
|
+
--select-text-small-size: var(--input-text-small-size);
|
|
71
|
+
--select-text-small-line-height: var(--input-text-small-line-height);
|
|
72
|
+
--select-text-small-letter-spacing: var(--input-text-small-letter-spacing);
|
|
73
|
+
--select-text-small-weight: var(--input-text-small-weight);
|
|
74
|
+
--select-text-medium-size: var(--input-text-medium-size);
|
|
75
|
+
--select-text-medium-line-height: var(--input-text-medium-line-height);
|
|
76
|
+
--select-text-medium-letter-spacing: var(--input-text-medium-letter-spacing);
|
|
77
|
+
--select-text-medium-weight: var(--input-text-medium-weight);
|
|
78
|
+
--select-text-large-size: var(--input-text-large-size);
|
|
79
|
+
--select-text-large-line-height: var(--input-text-large-line-height);
|
|
80
|
+
--select-text-large-letter-spacing: var(--input-text-large-letter-spacing);
|
|
81
|
+
--select-text-large-weight: var(--input-text-large-weight);
|
|
81
82
|
|
|
82
83
|
--select-icon-size-small: 1.6rem;
|
|
83
84
|
--select-icon-size-medium: 2rem;
|
|
@@ -257,9 +257,25 @@ export type SelectDefaultComponentProps = SelectTriggerDefaultProps &
|
|
|
257
257
|
SelectDropdownBehaviorProps &
|
|
258
258
|
SelectWidthOption;
|
|
259
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Select.Multiple 전체 선택 옵션 props
|
|
262
|
+
* @property {boolean} [showSelectAllOption] dropdown 첫 행에 "전체" 옵션 노출 여부
|
|
263
|
+
* @property {ReactNode} [selectAllLabel] "전체" 옵션 라벨 커스터마이징
|
|
264
|
+
*/
|
|
265
|
+
export interface SelectMultipleAllOptionProps {
|
|
266
|
+
/**
|
|
267
|
+
* dropdown 첫 행에 "전체" 옵션 노출 여부
|
|
268
|
+
*/
|
|
269
|
+
showSelectAllOption?: boolean;
|
|
270
|
+
/**
|
|
271
|
+
* "전체" 옵션 라벨 커스터마이징
|
|
272
|
+
*/
|
|
273
|
+
selectAllLabel?: ReactNode;
|
|
274
|
+
}
|
|
275
|
+
|
|
260
276
|
/**
|
|
261
277
|
* Select.Multiple 컴포넌트 props
|
|
262
|
-
* @typedef {SelectTriggerMultipleProps & SelectDropdownConfigProps & SelectDropdownBehaviorProps & SelectWidthOption} SelectMultipleComponentProps
|
|
278
|
+
* @typedef {SelectTriggerMultipleProps & SelectDropdownConfigProps & SelectDropdownBehaviorProps & SelectWidthOption & SelectMultipleAllOptionProps} SelectMultipleComponentProps
|
|
263
279
|
* @property {ReactNode} [displayLabel] 선택된 라벨
|
|
264
280
|
* @property {ReactNode} [placeholder] placeholder 텍스트
|
|
265
281
|
* @property {SelectMultipleTag[]} [tags] multi select tag 리스트
|
|
@@ -284,8 +300,11 @@ export type SelectDefaultComponentProps = SelectTriggerDefaultProps &
|
|
|
284
300
|
* @property {boolean} [open] dropdown open 상태
|
|
285
301
|
* @property {boolean} [defaultOpen] uncontrolled 초기 open 상태
|
|
286
302
|
* @property {(open: boolean) => void} [onOpenChange] open state change 콜백
|
|
303
|
+
* @property {boolean} [showSelectAllOption] dropdown 첫 행에 "전체" 옵션 노출 여부
|
|
304
|
+
* @property {ReactNode} [selectAllLabel] "전체" 옵션 라벨 커스터마이징
|
|
287
305
|
*/
|
|
288
306
|
export type SelectMultipleComponentProps = SelectTriggerMultipleProps &
|
|
289
307
|
SelectDropdownConfigProps &
|
|
290
308
|
SelectDropdownBehaviorProps &
|
|
291
|
-
SelectWidthOption
|
|
309
|
+
SelectWidthOption &
|
|
310
|
+
SelectMultipleAllOptionProps;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ElementType } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import SlotBase from "./Base";
|
|
4
|
+
import type { SlotTextProps } from "../types/text";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* SlotText; 텍스트 역할 children만 래핑하는 공용 슬롯.
|
|
8
|
+
* @component
|
|
9
|
+
* @param {SlotTextProps} props
|
|
10
|
+
* @param {React.ElementType} [props.as="span"] 렌더링할 요소.
|
|
11
|
+
* @param {React.ReactNode} [props.children] 문자열/숫자는 래핑하고, 그 외 ReactNode는 그대로 반환한다.
|
|
12
|
+
* @param {string} [props.className] 래핑 시 적용할 className.
|
|
13
|
+
*/
|
|
14
|
+
export default function SlotText<C extends ElementType = "span">({
|
|
15
|
+
as = "span" as C,
|
|
16
|
+
children,
|
|
17
|
+
className,
|
|
18
|
+
...restProps
|
|
19
|
+
}: SlotTextProps<C>) {
|
|
20
|
+
if (!["string", "number"].includes(typeof children)) {
|
|
21
|
+
return children;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 문자열/숫자 children만 공통 slot text 마크업으로 감싼다.
|
|
25
|
+
return (
|
|
26
|
+
<SlotBase
|
|
27
|
+
as={as as ElementType}
|
|
28
|
+
className={clsx("slot-text", className)}
|
|
29
|
+
{...restProps}
|
|
30
|
+
>
|
|
31
|
+
{children}
|
|
32
|
+
</SlotBase>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ElementType, ReactNode } from "react";
|
|
2
|
+
import type { SlotComponentRestProps } from "./props";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SlotTextProps; 텍스트 역할 children 래핑용 공통 props.
|
|
6
|
+
* @property {React.ElementType} [as] 렌더링할 요소. 기본값은 span.
|
|
7
|
+
* @property {React.ReactNode} [children] 문자열/숫자는 래핑하고 그 외 ReactNode는 그대로 반환한다.
|
|
8
|
+
* @property {string} [className] 텍스트 래퍼 className.
|
|
9
|
+
*/
|
|
10
|
+
export type SlotTextProps<C extends ElementType = "span"> =
|
|
11
|
+
SlotComponentRestProps<C> & {
|
|
12
|
+
/**
|
|
13
|
+
* 렌더링할 요소.
|
|
14
|
+
*/
|
|
15
|
+
as?: C;
|
|
16
|
+
/**
|
|
17
|
+
* 텍스트/노드 children.
|
|
18
|
+
*/
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
/**
|
|
21
|
+
* 텍스트 래퍼 className.
|
|
22
|
+
*/
|
|
23
|
+
className?: string;
|
|
24
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import clsx from "clsx";
|
|
2
2
|
import { forwardRef } from "react";
|
|
3
3
|
import type { TableCellContentProps } from "../../types";
|
|
4
|
+
import TableText from "./Text";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Table Foundation; Cell 콘텐츠 래퍼 컴포넌트
|
|
@@ -48,18 +49,9 @@ const TableCell = forwardRef<HTMLDivElement, TableCellContentProps>(
|
|
|
48
49
|
)}
|
|
49
50
|
>
|
|
50
51
|
{/* 변경: 텍스트 콘텐츠는 slot 전용 className으로 감싸 스타일 적용 범위를 고정한다. */}
|
|
51
|
-
{
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"table-cell-text",
|
|
55
|
-
section && `table-${section}-cell-text`,
|
|
56
|
-
)}
|
|
57
|
-
>
|
|
58
|
-
{children}
|
|
59
|
-
</span>
|
|
60
|
-
) : (
|
|
61
|
-
children
|
|
62
|
-
)}
|
|
52
|
+
<TableText className={clsx(section && `table-${section}-cell-text`)}>
|
|
53
|
+
{children}
|
|
54
|
+
</TableText>
|
|
63
55
|
</div>
|
|
64
56
|
);
|
|
65
57
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import clsx from "clsx";
|
|
2
2
|
import { forwardRef } from "react";
|
|
3
3
|
import type { TableTdProps } from "../../types";
|
|
4
|
+
import TableText from "./Text";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Table Foundation; Td 마크업 컴포넌트
|
|
@@ -20,13 +21,9 @@ const TableTd = forwardRef<HTMLTableCellElement, TableTdProps>(
|
|
|
20
21
|
style={{ ...style, textAlign: "left" }}
|
|
21
22
|
>
|
|
22
23
|
{/* 변경: 태그 셀렉터 의존을 피하기 위해 className 기반 텍스트 노드를 사용한다. */}
|
|
23
|
-
{
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
</span>
|
|
27
|
-
) : (
|
|
28
|
-
children
|
|
29
|
-
)}
|
|
24
|
+
<TableText className={clsx("table-native-cell-text", "table-td-text")}>
|
|
25
|
+
{children}
|
|
26
|
+
</TableText>
|
|
30
27
|
</td>
|
|
31
28
|
);
|
|
32
29
|
},
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
import { Slot } from "../../../slot";
|
|
3
|
+
import type { TableTextProps } from "../../types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Table Text; th/td/cell 텍스트 공통 렌더 컴포넌트.
|
|
7
|
+
* @component
|
|
8
|
+
* @param {TableTextProps} props
|
|
9
|
+
* @param {React.ReactNode} [props.children] 텍스트 콘텐츠
|
|
10
|
+
* @param {string} [props.className] table text className
|
|
11
|
+
*/
|
|
12
|
+
export default function TableText({ className, ...restProps }: TableTextProps) {
|
|
13
|
+
return (
|
|
14
|
+
<Slot.Text className={clsx("table-cell-text", className)} {...restProps} />
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import clsx from "clsx";
|
|
2
2
|
import { forwardRef } from "react";
|
|
3
3
|
import type { TableThProps } from "../../types";
|
|
4
|
+
import TableText from "./Text";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Table Foundation; Th 마크업 컴포넌트
|
|
@@ -29,13 +30,9 @@ const TableTh = forwardRef<HTMLTableCellElement, TableThProps>(
|
|
|
29
30
|
style={style}
|
|
30
31
|
>
|
|
31
32
|
{/* 변경: 태그 셀렉터 의존을 피하기 위해 className 기반 텍스트 노드를 사용한다. */}
|
|
32
|
-
{
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
</span>
|
|
36
|
-
) : (
|
|
37
|
-
children
|
|
38
|
-
)}
|
|
33
|
+
<TableText className={clsx("table-native-cell-text", "table-th-text")}>
|
|
34
|
+
{children}
|
|
35
|
+
</TableText>
|
|
39
36
|
</th>
|
|
40
37
|
);
|
|
41
38
|
},
|
|
@@ -8,6 +8,7 @@ import TableTh from "./Th";
|
|
|
8
8
|
import TableRoot from "./Root";
|
|
9
9
|
import TableRow from "./Row";
|
|
10
10
|
import TableTd from "./Td";
|
|
11
|
+
import TableText from "./Text";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Table Foundation; 기초 마크업 컴포넌트 세트
|
|
@@ -27,4 +28,5 @@ export const TableFoundation = {
|
|
|
27
28
|
Tr: TableRow,
|
|
28
29
|
Th: TableTh,
|
|
29
30
|
Td: TableTd,
|
|
31
|
+
Text: TableText,
|
|
30
32
|
};
|