@uniai-fe/uds-primitives 0.1.13 → 0.2.0
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/README.md +2 -2
- package/dist/styles.css +1112 -385
- package/package.json +12 -15
- package/src/components/button/index.scss +1 -0
- package/src/components/button/markup/{ButtonRounded.tsx → Rounded.tsx} +1 -1
- package/src/components/button/markup/{ButtonText.tsx → Text.tsx} +1 -1
- package/src/components/button/markup/index.ts +3 -3
- package/src/components/button/styles/button.scss +113 -229
- package/src/components/button/styles/round-button.scss +11 -14
- package/src/components/button/styles/text-button.scss +23 -23
- package/src/components/button/styles/variables.scss +145 -0
- package/src/components/dropdown/index.tsx +3 -3
- package/src/components/dropdown/markup/Template.tsx +61 -0
- package/src/components/dropdown/markup/foundation/Container.tsx +97 -0
- package/src/components/dropdown/markup/foundation/MenuItem.tsx +107 -0
- package/src/components/dropdown/markup/foundation/MenuList.tsx +27 -0
- package/src/components/dropdown/markup/foundation/Provider.tsx +46 -0
- package/src/components/dropdown/markup/foundation/Root.tsx +30 -0
- package/src/components/dropdown/markup/foundation/Trigger.tsx +34 -0
- package/src/components/dropdown/markup/foundation/index.tsx +25 -0
- package/src/components/dropdown/markup/index.tsx +8 -2
- package/src/components/dropdown/styles/dropdown.scss +166 -0
- package/src/components/dropdown/styles/index.scss +2 -0
- package/src/components/dropdown/styles/variables.scss +40 -0
- package/src/components/dropdown/types/base.ts +18 -0
- package/src/components/dropdown/types/index.ts +2 -4
- package/src/components/dropdown/types/props.ts +174 -0
- package/src/components/dropdown/utils/index.ts +1 -4
- package/src/components/dropdown/utils/refs.ts +20 -0
- package/src/components/form/index.scss +1 -0
- package/src/components/form/index.tsx +18 -2
- package/src/components/form/markup/form-field/Body.tsx +18 -0
- package/src/components/form/markup/form-field/Container.tsx +58 -0
- package/src/components/form/markup/form-field/Footer.tsx +21 -0
- package/src/components/form/markup/form-field/Header.tsx +39 -0
- package/src/components/form/markup/form-field/Template.tsx +56 -0
- package/src/components/form/markup/form-field/index.tsx +22 -0
- package/src/components/form/styles/form-field/layout.scss +67 -0
- package/src/components/form/styles/form-field/variables.scss +17 -0
- package/src/components/form/styles/index.scss +2 -0
- package/src/components/form/types/index.ts +1 -0
- package/src/components/form/types/props.ts +125 -0
- package/src/components/form/utils/form-field.ts +42 -0
- package/src/components/input/hooks/index.ts +1 -4
- package/src/components/input/hooks/useDigitField.ts +63 -0
- package/src/components/input/img/calendar/calendar.svg +7 -0
- package/src/components/input/img/calendar/chevron-down.svg +3 -0
- package/src/components/input/img/calendar/chevron-left.svg +3 -0
- package/src/components/input/img/calendar/chevron-right.svg +3 -0
- package/src/components/input/img/calendar/chevron-up.svg +3 -0
- package/src/components/input/index.tsx +2 -1
- package/src/components/input/markup/calendar/Base.tsx +329 -0
- package/src/components/input/markup/calendar/index.tsx +8 -0
- package/src/components/input/markup/{text/InputUtilityButton.tsx → foundation/Button.tsx} +5 -15
- package/src/components/input/markup/foundation/Input.tsx +245 -0
- package/src/components/input/markup/foundation/SideSlot.tsx +30 -0
- package/src/components/input/markup/foundation/StatusIcon.tsx +21 -0
- package/src/components/input/markup/foundation/Utility.tsx +103 -0
- package/src/components/input/markup/foundation/index.tsx +15 -0
- package/src/components/input/markup/index.tsx +11 -1
- package/src/components/input/markup/text/AuthCode.tsx +41 -59
- package/src/components/input/markup/text/Email.tsx +25 -115
- package/src/components/input/markup/text/Password.tsx +30 -39
- package/src/components/input/markup/text/Phone.tsx +35 -122
- package/src/components/input/markup/text/Search.tsx +17 -18
- package/src/components/input/markup/text/index.ts +15 -12
- package/src/components/input/styles/calendar.scss +110 -0
- package/src/components/input/styles/foundation.scss +345 -0
- package/src/components/input/styles/index.scss +4 -476
- package/src/components/input/styles/text.scss +89 -0
- package/src/components/input/styles/variables.scss +41 -0
- package/src/components/input/types/calendar.ts +208 -0
- package/src/components/input/types/foundation.ts +194 -0
- package/src/components/input/types/hooks.ts +43 -0
- package/src/components/input/types/index.ts +5 -87
- package/src/components/input/types/text.ts +203 -0
- package/src/components/input/types/verification.ts +23 -0
- package/src/components/input/utils/index.tsx +1 -0
- package/src/components/input/utils/verification.tsx +35 -0
- package/src/components/select/hooks/index.ts +43 -2
- package/src/components/select/img/chevron/primary/large.svg +3 -0
- package/src/components/select/img/chevron/primary/medium.svg +3 -0
- package/src/components/select/img/chevron/primary/small.svg +3 -0
- package/src/components/select/img/chevron/secondary/large.svg +3 -0
- package/src/components/select/img/chevron/secondary/medium.svg +3 -0
- package/src/components/select/img/chevron/secondary/small.svg +3 -0
- package/src/components/select/img/remove.svg +3 -0
- package/src/components/select/index.scss +2 -1
- package/src/components/select/index.tsx +5 -0
- package/src/components/select/markup/Default.tsx +154 -0
- package/src/components/select/markup/foundation/Base.tsx +90 -0
- package/src/components/select/markup/foundation/Container.tsx +30 -0
- package/src/components/select/markup/foundation/Icon.tsx +78 -0
- package/src/components/select/markup/foundation/Selected.tsx +34 -0
- package/src/components/select/markup/foundation/index.ts +2 -0
- package/src/components/select/markup/index.tsx +36 -2
- package/src/components/select/markup/multiple/Multiple.tsx +205 -0
- package/src/components/select/markup/multiple/SelectedChip.tsx +58 -0
- package/src/components/select/markup/multiple/index.ts +2 -0
- package/src/components/select/styles/select.scss +316 -0
- package/src/components/select/styles/variables.scss +91 -0
- package/src/components/select/types/base.ts +34 -0
- package/src/components/select/types/icon.ts +45 -0
- package/src/components/select/types/index.ts +5 -4
- package/src/components/select/types/multiple.ts +57 -0
- package/src/components/select/types/props.ts +208 -0
- package/src/components/select/types/trigger.ts +196 -0
- package/src/index.scss +3 -2
- package/src/components/input/markup/text/Base.tsx +0 -454
- package/src/components/input/utils/index.ts +0 -60
- package/src/components/select/styles/index.scss +0 -0
- /package/src/components/button/markup/{ButtonDefault.tsx → Base.tsx} +0 -0
- /package/src/components/form/{Provider.tsx → markup/Provider.tsx} +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import ChevronPrimarySmall from "../../img/chevron/primary/small.svg";
|
|
2
|
+
import ChevronPrimaryMedium from "../../img/chevron/primary/medium.svg";
|
|
3
|
+
import ChevronPrimaryLarge from "../../img/chevron/primary/large.svg";
|
|
4
|
+
import ChevronSecondarySmall from "../../img/chevron/secondary/small.svg";
|
|
5
|
+
import ChevronSecondaryMedium from "../../img/chevron/secondary/medium.svg";
|
|
6
|
+
import ChevronSecondaryLarge from "../../img/chevron/secondary/large.svg";
|
|
7
|
+
|
|
8
|
+
import Remove from "../../img/remove.svg";
|
|
9
|
+
import type {
|
|
10
|
+
SelectIconCollection,
|
|
11
|
+
SelectIconPriorityMap,
|
|
12
|
+
SelectIconRemovePriorityMap,
|
|
13
|
+
SelectIconSizeMap,
|
|
14
|
+
} from "../../types/icon";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Select; Chevron 아이콘 - primary
|
|
18
|
+
* - small
|
|
19
|
+
* - medium
|
|
20
|
+
* - large
|
|
21
|
+
*/
|
|
22
|
+
const SelectChevronPrimaryIcon: SelectIconSizeMap = {
|
|
23
|
+
small: ChevronPrimarySmall,
|
|
24
|
+
medium: ChevronPrimaryMedium,
|
|
25
|
+
large: ChevronPrimaryLarge,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Select; Chevron 아이콘 - secondary
|
|
30
|
+
* - small
|
|
31
|
+
* - medium
|
|
32
|
+
* - large
|
|
33
|
+
*/
|
|
34
|
+
const SelectChevronSecondaryIcon: SelectIconSizeMap = {
|
|
35
|
+
small: ChevronSecondarySmall,
|
|
36
|
+
medium: ChevronSecondaryMedium,
|
|
37
|
+
large: ChevronSecondaryLarge,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Select; Chevron 아이콘 컬렉션
|
|
42
|
+
* - primary (small, medium, large)
|
|
43
|
+
* - secondary (small, medium, large)
|
|
44
|
+
*/
|
|
45
|
+
const SelectChevronIcon: SelectIconPriorityMap = {
|
|
46
|
+
primary: SelectChevronPrimaryIcon,
|
|
47
|
+
secondary: SelectChevronSecondaryIcon,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Select; Remove 아이콘 - primary
|
|
52
|
+
* - small
|
|
53
|
+
* - medium
|
|
54
|
+
* - large
|
|
55
|
+
*/
|
|
56
|
+
const SelectMultipleRemoveIcon: SelectIconSizeMap = {
|
|
57
|
+
small: Remove,
|
|
58
|
+
medium: Remove,
|
|
59
|
+
large: Remove,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Select; Remove 아이콘 컬렉션
|
|
64
|
+
* - primary (small, medium, large)
|
|
65
|
+
*/
|
|
66
|
+
const SelectRemoveIcon: SelectIconRemovePriorityMap = {
|
|
67
|
+
primary: SelectMultipleRemoveIcon,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Select svgr 아이콘 레지스트리
|
|
72
|
+
* @property {SelectIconPriorityMap} Chevron priority/size별 Chevron 세트
|
|
73
|
+
* @property {SelectIconRemovePriorityMap} Remove multi select remove 세트
|
|
74
|
+
*/
|
|
75
|
+
export const SelectIcon: SelectIconCollection = {
|
|
76
|
+
Chevron: SelectChevronIcon,
|
|
77
|
+
Remove: SelectRemoveIcon,
|
|
78
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import type { SelectSelectedProps } from "../../types/trigger";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Select trigger label renderer
|
|
8
|
+
* @component
|
|
9
|
+
* @param {SelectSelectedProps} props selected view props
|
|
10
|
+
* @param {React.ReactNode} [props.label] 선택된 라벨
|
|
11
|
+
* @param {React.ReactNode} [props.placeholder] placeholder 텍스트
|
|
12
|
+
* @param {boolean} [props.isPlaceholder] placeholder 스타일 활성 여부
|
|
13
|
+
*/
|
|
14
|
+
const SelectTriggerSelected = ({
|
|
15
|
+
label,
|
|
16
|
+
placeholder,
|
|
17
|
+
isPlaceholder,
|
|
18
|
+
}: SelectSelectedProps) => {
|
|
19
|
+
return (
|
|
20
|
+
<div className="select-value">
|
|
21
|
+
<span
|
|
22
|
+
className={clsx("select-label", {
|
|
23
|
+
"select-label-placeholder": isPlaceholder,
|
|
24
|
+
})}
|
|
25
|
+
>
|
|
26
|
+
{label ?? placeholder}
|
|
27
|
+
</span>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
SelectTriggerSelected.displayName = "SelectSelected";
|
|
33
|
+
|
|
34
|
+
export { SelectTriggerSelected };
|
|
@@ -1,4 +1,38 @@
|
|
|
1
|
+
import { SelectDefault } from "./Default";
|
|
2
|
+
import { SelectMultipleSelectedChip, SelectMultipleTrigger } from "./multiple";
|
|
3
|
+
import { SelectTriggerBase, SelectTriggerSelected } from "./foundation";
|
|
4
|
+
import SelectContainer from "./foundation/Container";
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
|
-
*
|
|
7
|
+
* Selec; 컴포넌트 모듈
|
|
8
|
+
* @namespace Select
|
|
9
|
+
* - <Select.Default />: Select 기본
|
|
10
|
+
* - <Select.Multiple />: Select multi select
|
|
11
|
+
* - <Select.Container />: Select wrapper
|
|
12
|
+
* - <Select.Trigger.Base />: Select trigger 컴포넌트
|
|
13
|
+
* - <Select.Selected.Base />: Selected 기본 컴포넌트
|
|
14
|
+
* - <Select.Selected.Multiple />: Selected multi select 컴포넌트
|
|
3
15
|
*/
|
|
4
|
-
export {
|
|
16
|
+
export const Select = {
|
|
17
|
+
Default: SelectDefault,
|
|
18
|
+
Multiple: SelectMultipleTrigger,
|
|
19
|
+
Container: SelectContainer,
|
|
20
|
+
/**
|
|
21
|
+
* Select Trigger 컴포넌트 모듈
|
|
22
|
+
* @namespace Select.Trigger
|
|
23
|
+
* - <Select.Trigger.Base />: Select trigger 컴포넌트
|
|
24
|
+
*/
|
|
25
|
+
Trigger: {
|
|
26
|
+
Base: SelectTriggerBase,
|
|
27
|
+
},
|
|
28
|
+
/**
|
|
29
|
+
* Select Selected 컴포넌트 모듈
|
|
30
|
+
* @namespace Select.Selected
|
|
31
|
+
* - <Select.Selected.Base />: Selected 기본 컴포넌트
|
|
32
|
+
* - <Select.Selected.Multiple />: Selected multi select 컴포넌트
|
|
33
|
+
*/
|
|
34
|
+
Selected: {
|
|
35
|
+
Base: SelectTriggerSelected,
|
|
36
|
+
Multiple: SelectMultipleSelectedChip,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { forwardRef, useMemo } from "react";
|
|
5
|
+
|
|
6
|
+
import Container from "../foundation/Container";
|
|
7
|
+
import { Dropdown } from "../../../dropdown/markup";
|
|
8
|
+
import type { DropdownSize } from "../../../dropdown/types";
|
|
9
|
+
import type { SelectMultipleComponentProps } from "../../types/props";
|
|
10
|
+
import type { SelectMultipleTag } from "../../types/multiple";
|
|
11
|
+
import { SelectMultipleSelectedChip } from "./SelectedChip";
|
|
12
|
+
import { SelectTriggerBase, SelectTriggerSelected } from "../foundation";
|
|
13
|
+
import { useSelectDropdownOpenState } from "../../hooks";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Select trigger for multi select; 선택된 tag들을 chip 형태로 렌더링한다.
|
|
17
|
+
* @component
|
|
18
|
+
* @param {SelectMultipleComponentProps} props multi trigger props
|
|
19
|
+
* @param {SelectMultipleTag[]} [props.tags] 선택된 tag 리스트
|
|
20
|
+
* @param {React.ReactNode} [props.displayLabel] fallback 라벨
|
|
21
|
+
* @param {React.ReactNode} [props.placeholder] placeholder 텍스트
|
|
22
|
+
* @param {"primary" | "secondary"} [props.priority="primary"] priority scale
|
|
23
|
+
* @param {"small" | "medium" | "large"} [props.size="medium"] size scale
|
|
24
|
+
* @param {boolean} [props.block] block 여부
|
|
25
|
+
* @param {boolean} [props.isOpen] dropdown open 여부
|
|
26
|
+
* @param {boolean} [props.disabled] disabled 여부
|
|
27
|
+
*/
|
|
28
|
+
const SelectMultipleTrigger = forwardRef<
|
|
29
|
+
HTMLElement,
|
|
30
|
+
SelectMultipleComponentProps
|
|
31
|
+
>(
|
|
32
|
+
(
|
|
33
|
+
{
|
|
34
|
+
className,
|
|
35
|
+
displayLabel,
|
|
36
|
+
placeholder,
|
|
37
|
+
priority = "primary",
|
|
38
|
+
size = "medium",
|
|
39
|
+
state = "default",
|
|
40
|
+
block,
|
|
41
|
+
isOpen,
|
|
42
|
+
disabled,
|
|
43
|
+
tags,
|
|
44
|
+
options = [],
|
|
45
|
+
selectedOptionIds,
|
|
46
|
+
onOptionSelect,
|
|
47
|
+
dropdownSize,
|
|
48
|
+
dropdownMatchTriggerWidth = true,
|
|
49
|
+
dropdownRootProps,
|
|
50
|
+
dropdownContainerProps,
|
|
51
|
+
dropdownMenuListProps,
|
|
52
|
+
open,
|
|
53
|
+
defaultOpen,
|
|
54
|
+
onOpenChange,
|
|
55
|
+
...rest
|
|
56
|
+
},
|
|
57
|
+
ref,
|
|
58
|
+
) => {
|
|
59
|
+
// hook dependency 안정화를 위해 memoized selected id 배열을 유지한다.
|
|
60
|
+
const resolvedSelectedIds = useMemo(
|
|
61
|
+
() => selectedOptionIds ?? [],
|
|
62
|
+
[selectedOptionIds],
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const resolvedDisplayLabel =
|
|
66
|
+
displayLabel ??
|
|
67
|
+
(resolvedSelectedIds.length > 0
|
|
68
|
+
? options.find(option => option.id === resolvedSelectedIds[0])?.label
|
|
69
|
+
: undefined);
|
|
70
|
+
|
|
71
|
+
const derivedTags = useMemo<SelectMultipleTag[]>(() => {
|
|
72
|
+
if (tags && tags.length > 0) {
|
|
73
|
+
return tags;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (options.length === 0 || resolvedSelectedIds.length === 0) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return resolvedSelectedIds
|
|
81
|
+
.map(id => options.find(option => option.id === id))
|
|
82
|
+
.filter((option): option is NonNullable<typeof option> =>
|
|
83
|
+
Boolean(option),
|
|
84
|
+
)
|
|
85
|
+
.map(option => ({
|
|
86
|
+
label: option.label,
|
|
87
|
+
removable: false,
|
|
88
|
+
}));
|
|
89
|
+
}, [tags, options, resolvedSelectedIds]);
|
|
90
|
+
|
|
91
|
+
const hasTags = derivedTags.length > 0;
|
|
92
|
+
const hasLabel =
|
|
93
|
+
resolvedDisplayLabel !== undefined &&
|
|
94
|
+
resolvedDisplayLabel !== null &&
|
|
95
|
+
resolvedDisplayLabel !== "";
|
|
96
|
+
|
|
97
|
+
const { open: dropdownOpen, setOpen } = useSelectDropdownOpenState({
|
|
98
|
+
open: open ?? isOpen,
|
|
99
|
+
defaultOpen,
|
|
100
|
+
onOpenChange,
|
|
101
|
+
});
|
|
102
|
+
// multi select에서도 동일한 open 상태를 유지하기 위해 공통 hook을 사용한다.
|
|
103
|
+
|
|
104
|
+
const panelSize = (dropdownSize ?? size) as DropdownSize;
|
|
105
|
+
const shouldRenderDropdown = options.length > 0;
|
|
106
|
+
const MAX_VISIBLE_TAGS = 3;
|
|
107
|
+
const visibleTags = hasTags ? derivedTags.slice(0, MAX_VISIBLE_TAGS) : [];
|
|
108
|
+
const overflowCount = hasTags
|
|
109
|
+
? Math.max(derivedTags.length - visibleTags.length, 0)
|
|
110
|
+
: 0;
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<Container
|
|
114
|
+
className={clsx("select-trigger-multiple", className)}
|
|
115
|
+
block={block}
|
|
116
|
+
>
|
|
117
|
+
<Dropdown.Root
|
|
118
|
+
open={dropdownOpen}
|
|
119
|
+
onOpenChange={setOpen}
|
|
120
|
+
modal={false}
|
|
121
|
+
{...dropdownRootProps}
|
|
122
|
+
>
|
|
123
|
+
{/* Select trigger와 Dropdown trigger를 결합해 동일한 DOM을 공유한다. */}
|
|
124
|
+
<Dropdown.Trigger asChild>
|
|
125
|
+
<SelectTriggerBase
|
|
126
|
+
ref={ref}
|
|
127
|
+
priority={priority}
|
|
128
|
+
size={size}
|
|
129
|
+
state={disabled ? "disabled" : state}
|
|
130
|
+
block={block}
|
|
131
|
+
open={dropdownOpen}
|
|
132
|
+
multiple
|
|
133
|
+
disabled={disabled}
|
|
134
|
+
as="div"
|
|
135
|
+
{...rest}
|
|
136
|
+
>
|
|
137
|
+
{hasTags ? (
|
|
138
|
+
<div className="select-tags">
|
|
139
|
+
{visibleTags.map(
|
|
140
|
+
({ label, suffix, removable, onRemove }, index) => (
|
|
141
|
+
<SelectMultipleSelectedChip
|
|
142
|
+
key={`select-tag-${index}`}
|
|
143
|
+
label={label}
|
|
144
|
+
suffix={suffix}
|
|
145
|
+
removable={removable}
|
|
146
|
+
onRemove={onRemove}
|
|
147
|
+
/>
|
|
148
|
+
),
|
|
149
|
+
)}
|
|
150
|
+
{overflowCount > 0 ? (
|
|
151
|
+
<SelectMultipleSelectedChip
|
|
152
|
+
label={`+${overflowCount}`}
|
|
153
|
+
removable={false}
|
|
154
|
+
kind="summary"
|
|
155
|
+
/>
|
|
156
|
+
) : null}
|
|
157
|
+
</div>
|
|
158
|
+
) : (
|
|
159
|
+
<SelectTriggerSelected
|
|
160
|
+
label={resolvedDisplayLabel}
|
|
161
|
+
placeholder={placeholder}
|
|
162
|
+
isPlaceholder={!hasLabel}
|
|
163
|
+
/>
|
|
164
|
+
)}
|
|
165
|
+
</SelectTriggerBase>
|
|
166
|
+
</Dropdown.Trigger>
|
|
167
|
+
{shouldRenderDropdown ? (
|
|
168
|
+
<Dropdown.Container
|
|
169
|
+
{...dropdownContainerProps}
|
|
170
|
+
size={panelSize}
|
|
171
|
+
matchTriggerWidth={dropdownMatchTriggerWidth}
|
|
172
|
+
>
|
|
173
|
+
<Dropdown.Menu.List {...dropdownMenuListProps}>
|
|
174
|
+
{/* multi select 전용 옵션을 Dropdown.Menu.Item으로 노출한다. */}
|
|
175
|
+
{options.map(option => (
|
|
176
|
+
<Dropdown.Menu.Item
|
|
177
|
+
key={option.id}
|
|
178
|
+
label={option.label}
|
|
179
|
+
description={option.description}
|
|
180
|
+
disabled={option.disabled}
|
|
181
|
+
leftSlot={option.leftSlot}
|
|
182
|
+
rightSlot={option.rightSlot}
|
|
183
|
+
multiple
|
|
184
|
+
isSelected={resolvedSelectedIds.includes(option.id)}
|
|
185
|
+
onSelect={event => {
|
|
186
|
+
if (option.disabled) {
|
|
187
|
+
event.preventDefault();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
onOptionSelect?.(option);
|
|
191
|
+
}}
|
|
192
|
+
/>
|
|
193
|
+
))}
|
|
194
|
+
</Dropdown.Menu.List>
|
|
195
|
+
</Dropdown.Container>
|
|
196
|
+
) : null}
|
|
197
|
+
</Dropdown.Root>
|
|
198
|
+
</Container>
|
|
199
|
+
);
|
|
200
|
+
},
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
SelectMultipleTrigger.displayName = "SelectMultipleTrigger";
|
|
204
|
+
|
|
205
|
+
export { SelectMultipleTrigger };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
|
|
5
|
+
import RemoveIcon from "../../img/remove.svg";
|
|
6
|
+
import type { SelectMultipleChipProps } from "../../types/multiple";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Select multi chip; 선택된 값을 chip 형태로 표시하고 필요 시 제거 버튼을 노출한다.
|
|
10
|
+
* @component
|
|
11
|
+
* @param {SelectMultipleChipProps} props chip props
|
|
12
|
+
* @param {React.ReactNode} props.label chip 라벨
|
|
13
|
+
* @param {React.ReactNode} [props.suffix] 라벨 뒤에 붙는 서브 라벨
|
|
14
|
+
* @param {boolean} [props.removable] remove 버튼 노출 여부
|
|
15
|
+
* @param {() => void} [props.onRemove] remove 클릭 핸들러
|
|
16
|
+
*/
|
|
17
|
+
export function SelectMultipleSelectedChip({
|
|
18
|
+
label,
|
|
19
|
+
suffix,
|
|
20
|
+
removable = true,
|
|
21
|
+
onRemove,
|
|
22
|
+
kind = "value",
|
|
23
|
+
}: SelectMultipleChipProps) {
|
|
24
|
+
const isRemovable = removable && typeof onRemove === "function";
|
|
25
|
+
const handleRemove = () => {
|
|
26
|
+
if (isRemovable) {
|
|
27
|
+
onRemove?.();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
className={clsx("select-tag", {
|
|
34
|
+
"select-tag-removable": isRemovable,
|
|
35
|
+
"select-tag-summary": kind === "summary",
|
|
36
|
+
})}
|
|
37
|
+
data-removable={isRemovable ? "true" : undefined}
|
|
38
|
+
data-kind={kind}
|
|
39
|
+
>
|
|
40
|
+
<span className="select-tag-label">
|
|
41
|
+
{label}
|
|
42
|
+
{suffix && <span className="select-tag-suffix">{suffix}</span>}
|
|
43
|
+
</span>
|
|
44
|
+
{isRemovable ? (
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
className="select-tag-remove"
|
|
48
|
+
aria-label="remove tag"
|
|
49
|
+
onClick={handleRemove}
|
|
50
|
+
>
|
|
51
|
+
<RemoveIcon width={16} height={16} />
|
|
52
|
+
</button>
|
|
53
|
+
) : null}
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
SelectMultipleSelectedChip.displayName = "SelectMultipleSelectedChip";
|