@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.
Files changed (113) hide show
  1. package/README.md +2 -2
  2. package/dist/styles.css +1112 -385
  3. package/package.json +12 -15
  4. package/src/components/button/index.scss +1 -0
  5. package/src/components/button/markup/{ButtonRounded.tsx → Rounded.tsx} +1 -1
  6. package/src/components/button/markup/{ButtonText.tsx → Text.tsx} +1 -1
  7. package/src/components/button/markup/index.ts +3 -3
  8. package/src/components/button/styles/button.scss +113 -229
  9. package/src/components/button/styles/round-button.scss +11 -14
  10. package/src/components/button/styles/text-button.scss +23 -23
  11. package/src/components/button/styles/variables.scss +145 -0
  12. package/src/components/dropdown/index.tsx +3 -3
  13. package/src/components/dropdown/markup/Template.tsx +61 -0
  14. package/src/components/dropdown/markup/foundation/Container.tsx +97 -0
  15. package/src/components/dropdown/markup/foundation/MenuItem.tsx +107 -0
  16. package/src/components/dropdown/markup/foundation/MenuList.tsx +27 -0
  17. package/src/components/dropdown/markup/foundation/Provider.tsx +46 -0
  18. package/src/components/dropdown/markup/foundation/Root.tsx +30 -0
  19. package/src/components/dropdown/markup/foundation/Trigger.tsx +34 -0
  20. package/src/components/dropdown/markup/foundation/index.tsx +25 -0
  21. package/src/components/dropdown/markup/index.tsx +8 -2
  22. package/src/components/dropdown/styles/dropdown.scss +166 -0
  23. package/src/components/dropdown/styles/index.scss +2 -0
  24. package/src/components/dropdown/styles/variables.scss +40 -0
  25. package/src/components/dropdown/types/base.ts +18 -0
  26. package/src/components/dropdown/types/index.ts +2 -4
  27. package/src/components/dropdown/types/props.ts +174 -0
  28. package/src/components/dropdown/utils/index.ts +1 -4
  29. package/src/components/dropdown/utils/refs.ts +20 -0
  30. package/src/components/form/index.scss +1 -0
  31. package/src/components/form/index.tsx +18 -2
  32. package/src/components/form/markup/form-field/Body.tsx +18 -0
  33. package/src/components/form/markup/form-field/Container.tsx +58 -0
  34. package/src/components/form/markup/form-field/Footer.tsx +21 -0
  35. package/src/components/form/markup/form-field/Header.tsx +39 -0
  36. package/src/components/form/markup/form-field/Template.tsx +56 -0
  37. package/src/components/form/markup/form-field/index.tsx +22 -0
  38. package/src/components/form/styles/form-field/layout.scss +67 -0
  39. package/src/components/form/styles/form-field/variables.scss +17 -0
  40. package/src/components/form/styles/index.scss +2 -0
  41. package/src/components/form/types/index.ts +1 -0
  42. package/src/components/form/types/props.ts +125 -0
  43. package/src/components/form/utils/form-field.ts +42 -0
  44. package/src/components/input/hooks/index.ts +1 -4
  45. package/src/components/input/hooks/useDigitField.ts +63 -0
  46. package/src/components/input/img/calendar/calendar.svg +7 -0
  47. package/src/components/input/img/calendar/chevron-down.svg +3 -0
  48. package/src/components/input/img/calendar/chevron-left.svg +3 -0
  49. package/src/components/input/img/calendar/chevron-right.svg +3 -0
  50. package/src/components/input/img/calendar/chevron-up.svg +3 -0
  51. package/src/components/input/index.tsx +2 -1
  52. package/src/components/input/markup/calendar/Base.tsx +329 -0
  53. package/src/components/input/markup/calendar/index.tsx +8 -0
  54. package/src/components/input/markup/{text/InputUtilityButton.tsx → foundation/Button.tsx} +5 -15
  55. package/src/components/input/markup/foundation/Input.tsx +245 -0
  56. package/src/components/input/markup/foundation/SideSlot.tsx +30 -0
  57. package/src/components/input/markup/foundation/StatusIcon.tsx +21 -0
  58. package/src/components/input/markup/foundation/Utility.tsx +103 -0
  59. package/src/components/input/markup/foundation/index.tsx +15 -0
  60. package/src/components/input/markup/index.tsx +11 -1
  61. package/src/components/input/markup/text/AuthCode.tsx +41 -59
  62. package/src/components/input/markup/text/Email.tsx +25 -115
  63. package/src/components/input/markup/text/Password.tsx +30 -39
  64. package/src/components/input/markup/text/Phone.tsx +35 -122
  65. package/src/components/input/markup/text/Search.tsx +17 -18
  66. package/src/components/input/markup/text/index.ts +15 -12
  67. package/src/components/input/styles/calendar.scss +110 -0
  68. package/src/components/input/styles/foundation.scss +345 -0
  69. package/src/components/input/styles/index.scss +4 -476
  70. package/src/components/input/styles/text.scss +89 -0
  71. package/src/components/input/styles/variables.scss +41 -0
  72. package/src/components/input/types/calendar.ts +208 -0
  73. package/src/components/input/types/foundation.ts +194 -0
  74. package/src/components/input/types/hooks.ts +43 -0
  75. package/src/components/input/types/index.ts +5 -87
  76. package/src/components/input/types/text.ts +203 -0
  77. package/src/components/input/types/verification.ts +23 -0
  78. package/src/components/input/utils/index.tsx +1 -0
  79. package/src/components/input/utils/verification.tsx +35 -0
  80. package/src/components/select/hooks/index.ts +43 -2
  81. package/src/components/select/img/chevron/primary/large.svg +3 -0
  82. package/src/components/select/img/chevron/primary/medium.svg +3 -0
  83. package/src/components/select/img/chevron/primary/small.svg +3 -0
  84. package/src/components/select/img/chevron/secondary/large.svg +3 -0
  85. package/src/components/select/img/chevron/secondary/medium.svg +3 -0
  86. package/src/components/select/img/chevron/secondary/small.svg +3 -0
  87. package/src/components/select/img/remove.svg +3 -0
  88. package/src/components/select/index.scss +2 -1
  89. package/src/components/select/index.tsx +5 -0
  90. package/src/components/select/markup/Default.tsx +154 -0
  91. package/src/components/select/markup/foundation/Base.tsx +90 -0
  92. package/src/components/select/markup/foundation/Container.tsx +30 -0
  93. package/src/components/select/markup/foundation/Icon.tsx +78 -0
  94. package/src/components/select/markup/foundation/Selected.tsx +34 -0
  95. package/src/components/select/markup/foundation/index.ts +2 -0
  96. package/src/components/select/markup/index.tsx +36 -2
  97. package/src/components/select/markup/multiple/Multiple.tsx +205 -0
  98. package/src/components/select/markup/multiple/SelectedChip.tsx +58 -0
  99. package/src/components/select/markup/multiple/index.ts +2 -0
  100. package/src/components/select/styles/select.scss +316 -0
  101. package/src/components/select/styles/variables.scss +91 -0
  102. package/src/components/select/types/base.ts +34 -0
  103. package/src/components/select/types/icon.ts +45 -0
  104. package/src/components/select/types/index.ts +5 -4
  105. package/src/components/select/types/multiple.ts +57 -0
  106. package/src/components/select/types/props.ts +208 -0
  107. package/src/components/select/types/trigger.ts +196 -0
  108. package/src/index.scss +3 -2
  109. package/src/components/input/markup/text/Base.tsx +0 -454
  110. package/src/components/input/utils/index.ts +0 -60
  111. package/src/components/select/styles/index.scss +0 -0
  112. /package/src/components/button/markup/{ButtonDefault.tsx → Base.tsx} +0 -0
  113. /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 };
@@ -0,0 +1,2 @@
1
+ export { SelectTriggerBase } from "./Base";
2
+ export { SelectTriggerSelected } from "./Selected";
@@ -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
- * TODO(select): SOT 및 사용자 제약에 따라 컴포넌트를 구현한다.
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";
@@ -0,0 +1,2 @@
1
+ export { SelectMultipleTrigger } from "./Multiple";
2
+ export { SelectMultipleSelectedChip } from "./SelectedChip";