@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,30 @@
1
+ "use client";
2
+
3
+ import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
4
+ import type { ReactNode } from "react";
5
+
6
+ import type { DropdownRootProps } from "../../types/base";
7
+ import { DropdownProvider } from "./Provider";
8
+
9
+ /**
10
+ * Dropdown root; Provider와 Radix Root를 래핑한다.
11
+ * @component
12
+ * @param {DropdownRootProps} props Dropdown Root props
13
+ * @param {ReactNode} props.children Dropdown 하위 node
14
+ * @param {boolean} [props.modal=false] Radix modal 모드
15
+ */
16
+ const DropdownRoot = ({
17
+ children,
18
+ modal = false,
19
+ ...rootProps
20
+ }: DropdownRootProps & { children: ReactNode }) => {
21
+ return (
22
+ <DropdownProvider>
23
+ <DropdownMenu.Root modal={modal} {...rootProps}>
24
+ {children}
25
+ </DropdownMenu.Root>
26
+ </DropdownProvider>
27
+ );
28
+ };
29
+
30
+ export default DropdownRoot;
@@ -0,0 +1,34 @@
1
+ "use client";
2
+
3
+ import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
4
+ import { forwardRef } from "react";
5
+
6
+ import type { DropdownTriggerProps } from "../../types/props";
7
+ import { mergeRefs } from "../../utils";
8
+ import { useDropdownContext } from "./Provider";
9
+
10
+ /**
11
+ * Dropdown trigger; trigger ref를 context에 공유한다.
12
+ * @component
13
+ * @param {DropdownTriggerProps} props Dropdown trigger props
14
+ * @param {boolean} [props.asChild=true] asChild 패턴 유지 여부
15
+ */
16
+ const DropdownTrigger = forwardRef<HTMLElement, DropdownTriggerProps>(
17
+ ({ asChild = true, children, ...rest }, ref) => {
18
+ const { triggerRef } = useDropdownContext("Dropdown.Trigger");
19
+
20
+ return (
21
+ <DropdownMenu.Trigger
22
+ {...rest}
23
+ asChild={asChild}
24
+ ref={mergeRefs(ref, triggerRef)}
25
+ >
26
+ {children}
27
+ </DropdownMenu.Trigger>
28
+ );
29
+ },
30
+ );
31
+
32
+ DropdownTrigger.displayName = "DropdownTrigger";
33
+
34
+ export default DropdownTrigger;
@@ -0,0 +1,25 @@
1
+ import DropdownContainer from "./Container";
2
+ import DropdownMenuItem from "./MenuItem";
3
+ import DropdownMenuList from "./MenuList";
4
+ import DropdownRoot from "./Root";
5
+ import { DropdownProvider } from "./Provider";
6
+ import DropdownTrigger from "./Trigger";
7
+
8
+ /**
9
+ * Dropdown; 기초 컴포넌트
10
+ * - Provider
11
+ * - Root
12
+ * - Container
13
+ * - Menu
14
+ * - Item
15
+ * - List
16
+ * - Panel
17
+ * - Trigger
18
+ */
19
+ export const DropdownFoundation = {
20
+ Provider: DropdownProvider,
21
+ Root: DropdownRoot,
22
+ Container: DropdownContainer,
23
+ Menu: { Item: DropdownMenuItem, List: DropdownMenuList },
24
+ Trigger: DropdownTrigger,
25
+ };
@@ -1,4 +1,10 @@
1
+ import { DropdownFoundation } from "./foundation";
2
+ import DropdownTemplate from "./Template";
3
+
1
4
  /**
2
- * TODO(dropdown): SOT 및 사용자 제약에 따라 컴포넌트를 구현한다.
5
+ * Dropdown namespace export
3
6
  */
4
- export {};
7
+ export const Dropdown = {
8
+ ...DropdownFoundation,
9
+ Template: DropdownTemplate,
10
+ };
@@ -0,0 +1,166 @@
1
+ .dropdown-panel {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--dropdown-panel-gap);
5
+ padding: var(--dropdown-panel-padding);
6
+ border-radius: var(--dropdown-panel-radius);
7
+ border: 1px solid var(--dropdown-panel-border-color);
8
+ background-color: var(--dropdown-panel-background);
9
+ box-shadow: var(--dropdown-panel-shadow);
10
+ max-height: var(--dropdown-panel-max-height);
11
+ overflow-y: auto;
12
+ }
13
+
14
+ .dropdown-panel-small {
15
+ --dropdown-option-height-current: var(--dropdown-option-height-small);
16
+ --dropdown-text-size-current: var(--dropdown-text-small-size);
17
+ --dropdown-text-line-height-current: var(--dropdown-text-small-line-height);
18
+ --dropdown-text-letter-spacing-current: var(
19
+ --dropdown-text-small-letter-spacing
20
+ );
21
+ }
22
+
23
+ .dropdown-panel-medium {
24
+ --dropdown-option-height-current: var(--dropdown-option-height-medium);
25
+ --dropdown-text-size-current: var(--dropdown-text-medium-size);
26
+ --dropdown-text-line-height-current: var(--dropdown-text-medium-line-height);
27
+ --dropdown-text-letter-spacing-current: var(
28
+ --dropdown-text-medium-letter-spacing
29
+ );
30
+ }
31
+
32
+ .dropdown-panel-large {
33
+ --dropdown-option-height-current: var(--dropdown-option-height-large);
34
+ --dropdown-text-size-current: var(--dropdown-text-large-size);
35
+ --dropdown-text-line-height-current: var(--dropdown-text-large-line-height);
36
+ --dropdown-text-letter-spacing-current: var(
37
+ --dropdown-text-large-letter-spacing
38
+ );
39
+ }
40
+
41
+ .dropdown-menu-list {
42
+ display: flex;
43
+ flex-direction: column;
44
+ gap: var(--dropdown-panel-gap);
45
+ list-style: none;
46
+ padding: 0;
47
+ margin: 0;
48
+ }
49
+
50
+ .dropdown-menu-item {
51
+ width: 100%;
52
+ }
53
+
54
+ .dropdown-menu-item-trigger {
55
+ display: flex;
56
+ align-items: center;
57
+ gap: var(--dropdown-option-gap-inline);
58
+ width: 100%;
59
+ min-height: var(
60
+ --dropdown-option-height-current,
61
+ var(--dropdown-option-height-medium)
62
+ );
63
+ padding: var(--dropdown-option-padding-block)
64
+ var(--dropdown-option-padding-inline);
65
+ border-radius: var(--dropdown-option-radius);
66
+ background-color: transparent;
67
+ color: var(--dropdown-option-color);
68
+ cursor: pointer;
69
+ font-size: var(--dropdown-option-font-size, var(--dropdown-text-medium-size));
70
+ font-weight: var(--dropdown-option-font-weight, var(--dropdown-text-weight));
71
+ line-height: var(
72
+ --dropdown-option-line-height,
73
+ var(--dropdown-text-medium-line-height)
74
+ );
75
+
76
+ &[data-state="selected"] {
77
+ background-color: var(--dropdown-option-bg-selected);
78
+ color: var(--dropdown-option-color-selected);
79
+ }
80
+
81
+ &[data-highlighted],
82
+ &:hover {
83
+ background-color: var(--dropdown-option-bg-hover);
84
+ color: var(--dropdown-option-color-hover);
85
+ }
86
+
87
+ &[data-state="selected"]:hover {
88
+ background-color: var(--dropdown-option-bg-selected);
89
+ color: var(--dropdown-option-color-selected);
90
+ }
91
+
92
+ &[data-disabled],
93
+ &:disabled {
94
+ color: var(--dropdown-option-color-disabled);
95
+ cursor: not-allowed;
96
+ background-color: var(--dropdown-option-bg-disabled);
97
+ }
98
+
99
+ &[data-disabled] .dropdown-menu-item-label,
100
+ &:disabled .dropdown-menu-item-label {
101
+ color: var(--dropdown-option-color-disabled);
102
+ }
103
+
104
+ &[data-disabled] .dropdown-menu-item-description,
105
+ &:disabled .dropdown-menu-item-description {
106
+ color: var(--dropdown-option-color-disabled);
107
+ }
108
+ }
109
+
110
+ .dropdown-menu-item-prefix,
111
+ .dropdown-menu-item-suffix {
112
+ display: inline-flex;
113
+ align-items: center;
114
+ color: inherit;
115
+ }
116
+
117
+ .dropdown-menu-item-body {
118
+ display: flex;
119
+ flex-direction: column;
120
+ gap: 0.2rem;
121
+ flex: 1 1 auto;
122
+ min-width: 0;
123
+ }
124
+
125
+ .dropdown-menu-item-label {
126
+ display: inline-flex;
127
+ align-items: center;
128
+ min-width: 0;
129
+ color: inherit;
130
+ font-size: var(
131
+ --dropdown-text-size-current,
132
+ var(--dropdown-text-medium-size)
133
+ );
134
+ line-height: var(
135
+ --dropdown-text-line-height-current,
136
+ var(--dropdown-text-medium-line-height)
137
+ );
138
+ letter-spacing: var(
139
+ --dropdown-text-letter-spacing-current,
140
+ var(--dropdown-text-medium-letter-spacing)
141
+ );
142
+ font-weight: var(--dropdown-text-weight);
143
+ overflow: hidden;
144
+ text-overflow: ellipsis;
145
+ white-space: nowrap;
146
+ }
147
+
148
+ .dropdown-menu-item-description {
149
+ font-size: var(--dropdown-description-size);
150
+ line-height: var(--dropdown-description-line-height);
151
+ color: var(--dropdown-description-color);
152
+ overflow: hidden;
153
+ text-overflow: ellipsis;
154
+ white-space: nowrap;
155
+ }
156
+ .dropdown-menu-item-trigger[data-state="selected"] .dropdown-menu-item-label {
157
+ font-weight: var(--dropdown-text-weight-selected);
158
+ }
159
+ .dropdown-menu-item-trigger[data-multiple="true"] {
160
+ align-items: center;
161
+ }
162
+ .dropdown-menu-item-checkbox {
163
+ width: var(--theme-checkbox-frame-size-medium);
164
+ height: var(--theme-checkbox-frame-size-medium);
165
+ padding: 0;
166
+ }
@@ -0,0 +1,2 @@
1
+ @use "./variables.scss";
2
+ @use "./dropdown.scss";
@@ -0,0 +1,40 @@
1
+ :root {
2
+ --dropdown-panel-background: var(--color-common-100);
3
+ --dropdown-panel-border-color: var(--color-border-standard-cool-gray);
4
+ --dropdown-panel-radius: var(--theme-radius-large-1);
5
+ --dropdown-panel-shadow: 0px 4px 22px rgba(19, 22, 32, 0.12);
6
+ --dropdown-panel-padding: var(--spacing-padding-3);
7
+ --dropdown-panel-gap: var(--spacing-gap-2);
8
+ --dropdown-panel-max-height: 32rem;
9
+
10
+ --dropdown-option-radius: var(--theme-radius-medium-1);
11
+ --dropdown-option-gap-inline: var(--spacing-gap-3);
12
+ --dropdown-option-padding-inline: var(--spacing-padding-6);
13
+ --dropdown-option-padding-block: var(--spacing-padding-2);
14
+ --dropdown-option-color: var(--color-label-standard);
15
+ --dropdown-option-color-hover: var(--color-label-strong);
16
+ --dropdown-option-color-selected: var(--color-primary-default);
17
+ --dropdown-option-color-disabled: var(--color-label-disabled);
18
+ --dropdown-option-bg-hover: var(--color-surface-standard);
19
+ --dropdown-option-bg-selected: var(--color-surface-static-blue);
20
+ --dropdown-option-bg-disabled: transparent;
21
+
22
+ --dropdown-text-small-size: 15px;
23
+ --dropdown-text-small-line-height: 24px;
24
+ --dropdown-text-small-letter-spacing: 0;
25
+ --dropdown-text-medium-size: 16px;
26
+ --dropdown-text-medium-line-height: 24px;
27
+ --dropdown-text-medium-letter-spacing: 0;
28
+ --dropdown-text-large-size: 17px;
29
+ --dropdown-text-large-line-height: 26px;
30
+ --dropdown-text-large-letter-spacing: 0;
31
+ --dropdown-text-weight: 400;
32
+ --dropdown-text-weight-selected: 600;
33
+ --dropdown-description-color: var(--color-label-neutral);
34
+ --dropdown-description-size: 14px;
35
+ --dropdown-description-line-height: 22px;
36
+
37
+ --dropdown-option-height-small: 32px;
38
+ --dropdown-option-height-medium: 40px;
39
+ --dropdown-option-height-large: 48px;
40
+ }
@@ -0,0 +1,18 @@
1
+ import type { DropdownMenuProps } from "@radix-ui/react-dropdown-menu";
2
+
3
+ /**
4
+ * Dropdown size scale
5
+ * @typedef {"small" | "medium" | "large"} DropdownSize
6
+ */
7
+ export type DropdownSize = "small" | "medium" | "large";
8
+
9
+ /**
10
+ * Dropdown root props
11
+ * @property {boolean} [modal] Radix modal 모드 여부
12
+ */
13
+ export interface DropdownRootProps extends DropdownMenuProps {
14
+ /**
15
+ * Radix modal 모드 여부
16
+ */
17
+ modal?: DropdownMenuProps["modal"];
18
+ }
@@ -1,4 +1,2 @@
1
- /**
2
- * TODO(dropdown): variant/slot 타입 정의를 작성한다.
3
- */
4
- export {};
1
+ export type * from "./base";
2
+ export type * from "./props";
@@ -0,0 +1,174 @@
1
+ import type {
2
+ DropdownMenuContentProps,
3
+ DropdownMenuItemProps as RadixDropdownMenuItemProps,
4
+ DropdownMenuTriggerProps,
5
+ } from "@radix-ui/react-dropdown-menu";
6
+ import type { HTMLAttributes, MutableRefObject, ReactNode } from "react";
7
+
8
+ import type { CheckboxProps } from "../../checkbox/types";
9
+ import type { DropdownRootProps, DropdownSize } from "./base";
10
+
11
+ /**
12
+ * Dropdown trigger props
13
+ * @property {boolean} [asChild=true] trigger를 asChild 패턴으로 감쌀지 여부
14
+ */
15
+ export interface DropdownTriggerProps extends DropdownMenuTriggerProps {
16
+ /**
17
+ * trigger를 asChild 패턴으로 감쌀지 여부
18
+ */
19
+ asChild?: DropdownMenuTriggerProps["asChild"];
20
+ }
21
+
22
+ /**
23
+ * Dropdown Container props
24
+ * @property {DropdownSize} [size="medium"] option 높이 스케일
25
+ * @property {boolean} [matchTriggerWidth=true] trigger 너비에 맞춰 dropdown 너비를 맞출지 여부
26
+ * @property {HTMLElement | null} [portalContainer] portal을 렌더링할 DOM 컨테이너
27
+ */
28
+ export interface DropdownContainerProps extends DropdownMenuContentProps {
29
+ /**
30
+ * option 높이 스케일
31
+ */
32
+ size?: DropdownSize;
33
+ /**
34
+ * trigger 너비에 맞춰 dropdown 너비를 맞출지 여부
35
+ */
36
+ matchTriggerWidth?: boolean;
37
+ /**
38
+ * portal을 렌더링할 DOM 컨테이너
39
+ */
40
+ portalContainer?: HTMLElement | null;
41
+ }
42
+
43
+ /**
44
+ * Dropdown menu item props
45
+ * @property {ReactNode} [label] 옵션 라벨
46
+ * @property {ReactNode} [description] 보조 텍스트
47
+ * @property {ReactNode} [leftSlot] 좌측 슬롯
48
+ * @property {ReactNode} [rightSlot] 우측 슬롯
49
+ * @property {boolean} [isSelected] 선택 상태 여부
50
+ * @property {boolean} [multiple] multi select 스타일 여부
51
+ * @property {CheckboxProps} [checkboxProps] multiple 시 Checkbox 커스터마이징 옵션
52
+ */
53
+ export interface DropdownMenuItemProps extends RadixDropdownMenuItemProps {
54
+ /**
55
+ * 옵션 라벨
56
+ */
57
+ label?: ReactNode;
58
+ /**
59
+ * 보조 텍스트
60
+ */
61
+ description?: ReactNode;
62
+ /**
63
+ * 좌측 슬롯
64
+ */
65
+ leftSlot?: ReactNode;
66
+ /**
67
+ * 우측 슬롯
68
+ */
69
+ rightSlot?: ReactNode;
70
+ /**
71
+ * 선택 상태 여부
72
+ */
73
+ isSelected?: boolean;
74
+ /**
75
+ * multi select 스타일 여부
76
+ */
77
+ multiple?: boolean;
78
+ /**
79
+ * Checkbox 커스터마이징 props
80
+ */
81
+ checkboxProps?: CheckboxProps;
82
+ }
83
+
84
+ /**
85
+ * Dropdown menu list props
86
+ */
87
+ export interface DropdownMenuListProps extends HTMLAttributes<HTMLUListElement> {}
88
+
89
+ /**
90
+ * Dropdown context value; trigger ref 공유용
91
+ * @property {React.MutableRefObject<HTMLElement | null>} triggerRef trigger DOM ref
92
+ */
93
+ export interface DropdownContextValue {
94
+ /**
95
+ * trigger DOM ref
96
+ */
97
+ triggerRef: MutableRefObject<HTMLElement | null>;
98
+ }
99
+
100
+ /**
101
+ * Dropdown template item
102
+ * @property {string} id 고유 식별자
103
+ * @property {ReactNode} label 옵션 라벨
104
+ * @property {ReactNode} [description] 보조 텍스트
105
+ * @property {boolean} [disabled] 비활성 여부
106
+ * @property {ReactNode} [leftSlot] 좌측 슬롯
107
+ * @property {ReactNode} [rightSlot] 우측 슬롯
108
+ * @property {boolean} [multiple] multi select 스타일 여부
109
+ */
110
+ export interface DropdownTemplateItem {
111
+ /**
112
+ * 고유 식별자
113
+ */
114
+ id: string;
115
+ /**
116
+ * 옵션 라벨
117
+ */
118
+ label: ReactNode;
119
+ /**
120
+ * 보조 텍스트
121
+ */
122
+ description?: ReactNode;
123
+ /**
124
+ * 비활성 여부
125
+ */
126
+ disabled?: boolean;
127
+ /**
128
+ * 좌측 슬롯
129
+ */
130
+ leftSlot?: ReactNode;
131
+ /**
132
+ * 우측 슬롯
133
+ */
134
+ rightSlot?: ReactNode;
135
+ /**
136
+ * multi select 스타일 여부
137
+ */
138
+ multiple?: boolean;
139
+ }
140
+
141
+ /**
142
+ * Dropdown template props
143
+ * @property {ReactNode} trigger trigger 요소
144
+ * @property {DropdownTemplateItem[]} items 렌더링할 menu item 리스트
145
+ * @property {string[]} [selectedIds] 선택된 item id 배열
146
+ * @property {DropdownSize} [size="medium"] surface height scale
147
+ * @property {boolean} [matchTriggerWidth=true] trigger width 동기화 여부
148
+ * @property {DropdownRootProps} [rootProps] Root 에 전달할 props
149
+ * @property {DropdownContainerProps} [containerProps] Container 에 전달할 props
150
+ * @property {DropdownMenuListProps} [menuListProps] MenuList 에 전달할 props
151
+ */
152
+ export interface DropdownTemplateProps {
153
+ trigger: ReactNode;
154
+ items: DropdownTemplateItem[];
155
+ selectedIds?: string[];
156
+ onSelect?: (item: DropdownTemplateItem) => void;
157
+ size?: DropdownSize;
158
+ matchTriggerWidth?: boolean;
159
+ /**
160
+ * Root 에 전달할 props
161
+ */
162
+ rootProps?: DropdownRootProps;
163
+ /**
164
+ * Container 에 전달할 props
165
+ */
166
+ containerProps?: Omit<
167
+ DropdownContainerProps,
168
+ "children" | "size" | "matchTriggerWidth"
169
+ >;
170
+ /**
171
+ * MenuList 에 전달할 props
172
+ */
173
+ menuListProps?: DropdownMenuListProps;
174
+ }
@@ -1,4 +1 @@
1
- /**
2
- * TODO(dropdown): 토큰 매핑과 클래스명 유틸을 구현한다.
3
- */
4
- export {};
1
+ export { mergeRefs } from "./refs";
@@ -0,0 +1,20 @@
1
+ import type { ForwardedRef } from "react";
2
+
3
+ /**
4
+ * 다중 ref를 병합하는 헬퍼
5
+ * @param {ForwardedRef<T>[]} refs 연결할 refs
6
+ * @return {(node: T | null) => void} merged ref
7
+ */
8
+ const mergeRefs = <T>(...refs: (ForwardedRef<T> | null | undefined)[]) => {
9
+ return (node: T | null) => {
10
+ refs.forEach(ref => {
11
+ if (typeof ref === "function") {
12
+ ref(node);
13
+ } else if (ref && typeof ref === "object") {
14
+ (ref as { current: T | null }).current = node;
15
+ }
16
+ });
17
+ };
18
+ };
19
+
20
+ export { mergeRefs };
@@ -0,0 +1 @@
1
+ @use "./styles/index.scss";
@@ -1,3 +1,19 @@
1
- import FormProvider from "./Provider";
1
+ import FormProvider from "./markup/Provider";
2
+ import { FormField } from "./markup/form-field";
2
3
 
3
- export const Form = { Provider: FormProvider };
4
+ import "./index.scss";
5
+
6
+ export type * from "./types";
7
+
8
+ /**
9
+ * Form
10
+ * @component
11
+ * @desc
12
+ * - Form.Provider: Form 컨텍스트 제공 컴포넌트
13
+ * - Form.Field: Form 필드 컴포넌트
14
+ * - Form.Field.Container: Form 필드의 컨테이너 컴포넌트
15
+ * - Form.Field.Header: Form 필드의 헤더 컴포넌트
16
+ * - Form.Field.Body: Form 필드의 본문 컴포넌트
17
+ * - Form.Field.Footer: Form 필드의 푸터 컴포넌트
18
+ */
19
+ export const Form = { Provider: FormProvider, Field: FormField };
@@ -0,0 +1,18 @@
1
+ import clsx from "clsx";
2
+
3
+ /**
4
+ * Form; field body
5
+ * @component
6
+ * @param {object} props
7
+ * @property {string} [className]
8
+ * @property {React.ReactNode} [children]
9
+ */
10
+ export default function FormFieldBody({
11
+ className,
12
+ children,
13
+ }: {
14
+ className?: string;
15
+ children?: React.ReactNode;
16
+ }) {
17
+ return <div className={clsx("form-field-body", className)}>{children}</div>;
18
+ }
@@ -0,0 +1,58 @@
1
+ "use client";
2
+
3
+ import clsx from "clsx";
4
+ import { forwardRef } from "react";
5
+ import type { FormFieldContainerProps } from "../../types/props";
6
+ import {
7
+ getFormFieldWidthAttr,
8
+ getFormFieldWidthValue,
9
+ } from "../../utils/form-field";
10
+
11
+ /**
12
+ * Form; field container
13
+ * @component
14
+ * @param {FormFieldContainerProps} props
15
+ * @property {React.ElementType} [as] container 태그 지정 (default: <section />)
16
+ * @property {FormFieldWidth} [width] form field 너비 옵션
17
+ * @property {string} [className]
18
+ * @property {React.ReactNode} [children]
19
+ * @property {React.HTMLAttributes<HTMLElement>} [containerProps] container 속성
20
+ */
21
+ const FormFieldContainer = forwardRef<HTMLElement, FormFieldContainerProps>(
22
+ (
23
+ {
24
+ as: ElementTag = "section",
25
+ className,
26
+ children,
27
+ width,
28
+ style,
29
+ ...containerProps
30
+ },
31
+ ref,
32
+ ) => {
33
+ const ComponentTag = ElementTag as React.ElementType;
34
+ const widthValue = getFormFieldWidthValue(width);
35
+ const widthStyle =
36
+ widthValue !== undefined
37
+ ? { ["--form-field-width" as const]: widthValue }
38
+ : undefined;
39
+ const mergedStyle =
40
+ widthStyle !== undefined ? { ...(style ?? {}), ...widthStyle } : style;
41
+
42
+ return (
43
+ <ComponentTag
44
+ ref={ref}
45
+ data-width={getFormFieldWidthAttr(width)}
46
+ className={clsx("form-field form-field-container", className)}
47
+ style={mergedStyle}
48
+ {...containerProps}
49
+ >
50
+ {children}
51
+ </ComponentTag>
52
+ );
53
+ },
54
+ );
55
+
56
+ FormFieldContainer.displayName = "FormFieldContainer";
57
+
58
+ export default FormFieldContainer;
@@ -0,0 +1,21 @@
1
+ import clsx from "clsx";
2
+ import type { FormFieldFooterProps } from "../../types";
3
+
4
+ /**
5
+ * Form; field footer
6
+ * @component
7
+ * @param {FormFieldFooterProps} props
8
+ * @property {string} [className]
9
+ * @property {React.ReactNode} [children]
10
+ */
11
+ export default function FormFieldFooter({
12
+ className,
13
+ children,
14
+ ...footerAttrs
15
+ }: FormFieldFooterProps) {
16
+ return (
17
+ <footer className={clsx("form-field-footer", className)} {...footerAttrs}>
18
+ {children}
19
+ </footer>
20
+ );
21
+ }