@uniai-fe/uds-primitives 0.2.0 → 0.2.2
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 +105 -13
- package/package.json +5 -3
- package/src/components/button/index.tsx +0 -2
- package/src/components/button/markup/Base.tsx +22 -1
- package/src/components/button/styles/button.scss +24 -2
- package/src/components/button/styles/variables.scss +4 -0
- package/src/components/button/types/index.ts +7 -0
- package/src/components/checkbox/markup/Checkbox.tsx +31 -25
- package/src/components/dropdown/markup/Template.tsx +4 -8
- package/src/components/dropdown/markup/foundation/Container.tsx +35 -7
- package/src/components/dropdown/markup/foundation/MenuItem.tsx +10 -10
- package/src/components/dropdown/markup/index.tsx +10 -1
- package/src/components/dropdown/styles/dropdown.scss +2 -2
- package/src/components/dropdown/styles/variables.scss +4 -4
- package/src/components/dropdown/types/base.ts +13 -0
- package/src/components/dropdown/types/props.ts +23 -27
- package/src/components/input/hooks/index.ts +1 -0
- package/src/components/input/hooks/useAddress.ts +247 -0
- package/src/components/input/index.scss +5 -1
- package/src/components/input/markup/address/Button.tsx +65 -0
- package/src/components/input/markup/address/Template.tsx +135 -0
- package/src/components/input/markup/address/index.ts +9 -0
- package/src/components/input/markup/foundation/Input.tsx +20 -1
- package/src/components/input/markup/index.tsx +2 -0
- package/src/components/input/styles/address.scss +24 -0
- package/src/components/input/styles/foundation.scss +28 -2
- package/src/components/input/styles/variables.scss +4 -0
- package/src/components/input/types/address.ts +249 -0
- package/src/components/input/types/foundation.ts +6 -0
- package/src/components/input/types/index.ts +1 -0
- package/src/components/input/utils/address.ts +165 -0
- package/src/components/input/utils/index.tsx +1 -0
- package/src/components/radio/markup/Radio.tsx +10 -2
- package/src/components/radio/markup/RadioCard.tsx +6 -1
- package/src/components/radio/markup/RadioCardGroup.tsx +6 -1
- package/src/components/select/markup/Default.tsx +6 -4
- package/src/components/select/markup/foundation/Container.tsx +23 -0
- package/src/components/select/markup/multiple/Multiple.tsx +6 -4
- package/src/components/select/styles/select.scss +25 -2
- package/src/components/select/styles/variables.scss +4 -0
- package/src/components/select/types/index.ts +1 -0
- package/src/components/select/types/option.ts +43 -0
- package/src/components/select/types/props.ts +29 -9
- package/src/components/input/styles/index.scss +0 -4
package/dist/styles.css
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
--theme-badge-line-height: var(--font-caption-medium-line-height, 1.5);
|
|
12
12
|
--theme-badge-letter-spacing: var(--font-caption-medium-letter-spacing, 0);
|
|
13
13
|
--theme-badge-dot-size: var(--spacing-gap-3, 8px);
|
|
14
|
+
/* layout presets */
|
|
15
|
+
--button-width: fit-content;
|
|
16
|
+
--button-flex: 0 0 auto;
|
|
14
17
|
/* default button spacing (size 기반) */
|
|
15
18
|
--button-default-gap-small: var(--spacing-gap-1);
|
|
16
19
|
--button-default-gap-medium: var(--spacing-gap-2);
|
|
@@ -214,7 +217,7 @@
|
|
|
214
217
|
--dropdown-text-medium-size: 16px;
|
|
215
218
|
--dropdown-text-medium-line-height: 24px;
|
|
216
219
|
--dropdown-text-medium-letter-spacing: 0;
|
|
217
|
-
--dropdown-text-large-size:
|
|
220
|
+
--dropdown-text-large-size: 19px;
|
|
218
221
|
--dropdown-text-large-line-height: 26px;
|
|
219
222
|
--dropdown-text-large-letter-spacing: 0;
|
|
220
223
|
--dropdown-text-weight: 400;
|
|
@@ -222,9 +225,12 @@
|
|
|
222
225
|
--dropdown-description-color: var(--color-label-neutral);
|
|
223
226
|
--dropdown-description-size: 14px;
|
|
224
227
|
--dropdown-description-line-height: 22px;
|
|
225
|
-
--dropdown-option-height-small:
|
|
226
|
-
--dropdown-option-height-medium:
|
|
227
|
-
--dropdown-option-height-large:
|
|
228
|
+
--dropdown-option-height-small: var(--theme-size-medium-1, 40px);
|
|
229
|
+
--dropdown-option-height-medium: var(--theme-size-medium-2, 48px);
|
|
230
|
+
--dropdown-option-height-large: var(--theme-size-medium-3, 56px);
|
|
231
|
+
/* Layout presets */
|
|
232
|
+
--input-width: 100%;
|
|
233
|
+
--input-flex: 0 1 auto;
|
|
228
234
|
/* Input sizing tokens; Button 변수 규칙과 동일한 prefix 패턴을 맞춘다. */
|
|
229
235
|
--input-default-height-small: var(--theme-size-medium-1);
|
|
230
236
|
--input-default-height-medium: var(--theme-size-medium-2);
|
|
@@ -260,6 +266,9 @@
|
|
|
260
266
|
--input-surface-color: var(--color-common-100);
|
|
261
267
|
--input-surface-muted-color: var(--color-neutral-99);
|
|
262
268
|
--input-surface-disabled-color: var(--color-neutral-95);
|
|
269
|
+
/* layout presets */
|
|
270
|
+
--select-width: 100%;
|
|
271
|
+
--select-flex: 0 1 auto;
|
|
263
272
|
--select-primary-height-small: var(--input-default-height-small);
|
|
264
273
|
--select-primary-height-medium: var(--input-default-height-medium);
|
|
265
274
|
--select-primary-height-large: var(--input-default-height-large);
|
|
@@ -644,7 +653,8 @@
|
|
|
644
653
|
align-items: center;
|
|
645
654
|
justify-content: center;
|
|
646
655
|
gap: var(--button-default-gap-medium, var(--spacing-gap-2, 8px));
|
|
647
|
-
width:
|
|
656
|
+
width: var(--button-width);
|
|
657
|
+
flex: var(--button-flex);
|
|
648
658
|
min-width: var(--button-default-width-min-base, var(--theme-size-small-2, 24px));
|
|
649
659
|
min-height: var(--button-min-height, auto);
|
|
650
660
|
padding-inline: var(--button-padding-inline, var(--button-default-padding-inline-base, var(--spacing-padding-4, 16px)));
|
|
@@ -672,8 +682,24 @@
|
|
|
672
682
|
align-items: center;
|
|
673
683
|
justify-content: center;
|
|
674
684
|
}
|
|
675
|
-
.button
|
|
676
|
-
width:
|
|
685
|
+
.button[data-width=auto] {
|
|
686
|
+
--button-width: auto;
|
|
687
|
+
--button-flex: 0 1 auto;
|
|
688
|
+
}
|
|
689
|
+
.button[data-width=fill] {
|
|
690
|
+
--button-width: auto;
|
|
691
|
+
--button-flex: 1 1 0%;
|
|
692
|
+
}
|
|
693
|
+
.button[data-width=full], .button.button-block {
|
|
694
|
+
--button-width: 100%;
|
|
695
|
+
--button-flex: 0 0 100%;
|
|
696
|
+
}
|
|
697
|
+
.button[data-width=fit] {
|
|
698
|
+
--button-width: fit-content;
|
|
699
|
+
--button-flex: 0 0 auto;
|
|
700
|
+
}
|
|
701
|
+
.button[data-width=custom] {
|
|
702
|
+
--button-flex: 0 0 auto;
|
|
677
703
|
}
|
|
678
704
|
.button:not(.button-fill-solid):not(.button-fill-outlined) {
|
|
679
705
|
background-color: transparent;
|
|
@@ -1545,8 +1571,8 @@ figure.chip {
|
|
|
1545
1571
|
color: var(--dropdown-option-color-disabled);
|
|
1546
1572
|
}
|
|
1547
1573
|
|
|
1548
|
-
.dropdown-menu-item-
|
|
1549
|
-
.dropdown-menu-item-
|
|
1574
|
+
.dropdown-menu-item-left,
|
|
1575
|
+
.dropdown-menu-item-right {
|
|
1550
1576
|
display: inline-flex;
|
|
1551
1577
|
align-items: center;
|
|
1552
1578
|
color: inherit;
|
|
@@ -1603,9 +1629,30 @@ figure.chip {
|
|
|
1603
1629
|
display: flex;
|
|
1604
1630
|
flex-direction: column;
|
|
1605
1631
|
gap: var(--spacing-gap-3);
|
|
1606
|
-
width:
|
|
1632
|
+
width: var(--input-width);
|
|
1633
|
+
flex: var(--input-flex);
|
|
1634
|
+
min-width: 0;
|
|
1635
|
+
}
|
|
1636
|
+
.input[data-width=auto] {
|
|
1637
|
+
--input-width: auto;
|
|
1638
|
+
--input-flex: 0 1 auto;
|
|
1639
|
+
}
|
|
1640
|
+
.input[data-width=fill] {
|
|
1641
|
+
--input-width: auto;
|
|
1642
|
+
--input-flex: 1 1 0%;
|
|
1607
1643
|
}
|
|
1608
|
-
.input[data-
|
|
1644
|
+
.input[data-width=full], .input[data-block=true] {
|
|
1645
|
+
--input-width: 100%;
|
|
1646
|
+
--input-flex: 0 0 100%;
|
|
1647
|
+
}
|
|
1648
|
+
.input[data-width=fit] {
|
|
1649
|
+
--input-width: fit-content;
|
|
1650
|
+
--input-flex: 0 0 auto;
|
|
1651
|
+
}
|
|
1652
|
+
.input[data-width=custom] {
|
|
1653
|
+
--input-flex: 0 0 auto;
|
|
1654
|
+
}
|
|
1655
|
+
.input--block {
|
|
1609
1656
|
width: 100%;
|
|
1610
1657
|
}
|
|
1611
1658
|
|
|
@@ -2079,18 +2126,63 @@ figure.chip {
|
|
|
2079
2126
|
width: 100%;
|
|
2080
2127
|
}
|
|
2081
2128
|
|
|
2129
|
+
.input-address-container {
|
|
2130
|
+
width: 100%;
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
.input-address-row {
|
|
2134
|
+
width: 100%;
|
|
2135
|
+
display: flex;
|
|
2136
|
+
gap: var(--spacing-gap-5);
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
.input-address-lower {
|
|
2140
|
+
margin-top: var(--spacing-gap-5);
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
.input-address-upper {
|
|
2144
|
+
align-items: center;
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
.input-address-field {
|
|
2148
|
+
width: 100%;
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2082
2151
|
/* Select tokens mapped to Input primary tokens for visual parity */
|
|
2083
2152
|
|
|
2084
2153
|
|
|
2085
2154
|
.select {
|
|
2086
2155
|
display: flex;
|
|
2087
|
-
width:
|
|
2156
|
+
width: var(--select-width);
|
|
2157
|
+
flex: var(--select-flex);
|
|
2088
2158
|
flex-direction: column;
|
|
2089
2159
|
gap: var(--spacing-gap-2);
|
|
2160
|
+
min-width: 0;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
.select[data-width=auto] {
|
|
2164
|
+
--select-width: auto;
|
|
2165
|
+
--select-flex: 0 1 auto;
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
.select[data-width=fill] {
|
|
2169
|
+
--select-width: auto;
|
|
2170
|
+
--select-flex: 1 1 0%;
|
|
2090
2171
|
}
|
|
2091
2172
|
|
|
2173
|
+
.select[data-width=full],
|
|
2092
2174
|
.select-block {
|
|
2093
|
-
width: 100%;
|
|
2175
|
+
--select-width: 100%;
|
|
2176
|
+
--select-flex: 0 0 100%;
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
.select[data-width=fit] {
|
|
2180
|
+
--select-width: fit-content;
|
|
2181
|
+
--select-flex: 0 0 auto;
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
.select[data-width=custom] {
|
|
2185
|
+
--select-flex: 0 0 auto;
|
|
2094
2186
|
}
|
|
2095
2187
|
|
|
2096
2188
|
.select-button {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniai-fe/uds-primitives",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "UNIAI Design System; Primitives Components Package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -58,7 +58,8 @@
|
|
|
58
58
|
"@uniai-fe/util-functions": "^0.2.3",
|
|
59
59
|
"react": "^19",
|
|
60
60
|
"react-dom": "^19",
|
|
61
|
-
"react-hook-form": "^7"
|
|
61
|
+
"react-hook-form": "^7",
|
|
62
|
+
"react-daum-postcode": "^3"
|
|
62
63
|
},
|
|
63
64
|
"dependencies": {
|
|
64
65
|
"@mantine/dates": "^8.3.14",
|
|
@@ -68,7 +69,8 @@
|
|
|
68
69
|
"@radix-ui/react-radio-group": "^1.3.8",
|
|
69
70
|
"@radix-ui/react-tabs": "^1.1.13",
|
|
70
71
|
"clsx": "^2.1.1",
|
|
71
|
-
"dayjs": "^1.11.19"
|
|
72
|
+
"dayjs": "^1.11.19",
|
|
73
|
+
"react-daum-postcode": "^3.2.0"
|
|
72
74
|
},
|
|
73
75
|
"devDependencies": {
|
|
74
76
|
"@radix-ui/react-visually-hidden": "^1.2.4",
|
|
@@ -4,6 +4,10 @@ import clsx from "clsx";
|
|
|
4
4
|
import { forwardRef } from "react";
|
|
5
5
|
import type { ButtonProps } from "../types";
|
|
6
6
|
import { SlotComponent } from "../../slot";
|
|
7
|
+
import {
|
|
8
|
+
getFormFieldWidthAttr,
|
|
9
|
+
getFormFieldWidthValue,
|
|
10
|
+
} from "../../form/utils/form-field";
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
13
|
* uds-foundation 토큰 위에서 block/layout/priority/slot API를 제공하는 기본 Button 컴포넌트.
|
|
@@ -41,6 +45,7 @@ const ButtonDefault = forwardRef<HTMLElement, ButtonProps>(
|
|
|
41
45
|
priority,
|
|
42
46
|
state: stateProp = "default",
|
|
43
47
|
block = false,
|
|
48
|
+
width,
|
|
44
49
|
loading = false,
|
|
45
50
|
className,
|
|
46
51
|
type: typeProp,
|
|
@@ -65,6 +70,20 @@ const ButtonDefault = forwardRef<HTMLElement, ButtonProps>(
|
|
|
65
70
|
"aria-disabled": isDisabled || undefined,
|
|
66
71
|
};
|
|
67
72
|
|
|
73
|
+
const widthAttr =
|
|
74
|
+
width !== undefined
|
|
75
|
+
? getFormFieldWidthAttr(width)
|
|
76
|
+
: block
|
|
77
|
+
? "full"
|
|
78
|
+
: undefined;
|
|
79
|
+
const widthValue =
|
|
80
|
+
width !== undefined ? getFormFieldWidthValue(width) : undefined;
|
|
81
|
+
const { style, ...elementRestProps } = restProps;
|
|
82
|
+
const mergedStyle =
|
|
83
|
+
widthValue !== undefined
|
|
84
|
+
? { ...(style ?? {}), ["--button-width" as const]: widthValue }
|
|
85
|
+
: style;
|
|
86
|
+
|
|
68
87
|
return (
|
|
69
88
|
<SlotComponent
|
|
70
89
|
className={clsx(
|
|
@@ -83,8 +102,10 @@ const ButtonDefault = forwardRef<HTMLElement, ButtonProps>(
|
|
|
83
102
|
ref={forwardedRef}
|
|
84
103
|
aria-busy={loading || undefined}
|
|
85
104
|
data-user-action={userAction}
|
|
105
|
+
data-width={widthAttr}
|
|
106
|
+
style={mergedStyle}
|
|
86
107
|
{...elementSpecificProps}
|
|
87
|
-
{...
|
|
108
|
+
{...elementRestProps}
|
|
88
109
|
>
|
|
89
110
|
{left && (
|
|
90
111
|
<span className="button-left" data-slot="left">
|
|
@@ -150,7 +150,8 @@ $button-priorities: (
|
|
|
150
150
|
align-items: center;
|
|
151
151
|
justify-content: center;
|
|
152
152
|
gap: var(--button-default-gap-medium, var(--spacing-gap-2, 8px));
|
|
153
|
-
width:
|
|
153
|
+
width: var(--button-width);
|
|
154
|
+
flex: var(--button-flex);
|
|
154
155
|
min-width: var(
|
|
155
156
|
--button-default-width-min-base,
|
|
156
157
|
var(--theme-size-small-2, 24px)
|
|
@@ -216,8 +217,29 @@ $button-priorities: (
|
|
|
216
217
|
justify-content: center;
|
|
217
218
|
}
|
|
218
219
|
|
|
220
|
+
&[data-width="auto"] {
|
|
221
|
+
--button-width: auto;
|
|
222
|
+
--button-flex: 0 1 auto;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
&[data-width="fill"] {
|
|
226
|
+
--button-width: auto;
|
|
227
|
+
--button-flex: 1 1 0%;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
&[data-width="full"],
|
|
219
231
|
&.button-block {
|
|
220
|
-
width: 100%;
|
|
232
|
+
--button-width: 100%;
|
|
233
|
+
--button-flex: 0 0 100%;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
&[data-width="fit"] {
|
|
237
|
+
--button-width: fit-content;
|
|
238
|
+
--button-flex: 0 0 auto;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
&[data-width="custom"] {
|
|
242
|
+
--button-flex: 0 0 auto;
|
|
221
243
|
}
|
|
222
244
|
|
|
223
245
|
&:not(.button-fill-solid):not(.button-fill-outlined) {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/* 버튼 전용 토큰은 theme root에서 한 번만 정의하며, size 기반 규칙(--button-{type}-{property}-{size})을 따른다. */
|
|
2
2
|
:root {
|
|
3
|
+
/* layout presets */
|
|
4
|
+
--button-width: fit-content;
|
|
5
|
+
--button-flex: 0 0 auto;
|
|
6
|
+
|
|
3
7
|
/* default button spacing (size 기반) */
|
|
4
8
|
--button-default-gap-small: var(--spacing-gap-1);
|
|
5
9
|
--button-default-gap-medium: var(--spacing-gap-2);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ComponentPropsWithoutRef, ElementType, ReactNode } from "react";
|
|
2
|
+
import type { FormFieldWidth } from "../../form/types";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Button; priority 옵션
|
|
@@ -91,6 +92,7 @@ type SharedElementProps = Omit<NativeButtonProps, "children"> &
|
|
|
91
92
|
* @property {ButtonPriority} priority semantic color priority.
|
|
92
93
|
* @property {ButtonState} [state] UI 상태. disabled prop과 조합된다.
|
|
93
94
|
* @property {boolean} [block] width:100% 확장 여부.
|
|
95
|
+
* @property {FormFieldWidth} [width] width preset. block보다 우선 적용된다.
|
|
94
96
|
* @property {boolean} [loading] true면 readonly 처리 + aria-busy.
|
|
95
97
|
* @property {ReactNode} [left] 라벨 왼쪽 커스텀 슬롯.
|
|
96
98
|
* @property {ReactNode} [right] 라벨 오른쪽 커스텀 슬롯.
|
|
@@ -145,6 +147,11 @@ export interface ButtonProps extends SharedElementProps {
|
|
|
145
147
|
* true면 버튼 폭을 100%로 확장한다.
|
|
146
148
|
*/
|
|
147
149
|
block?: boolean;
|
|
150
|
+
/**
|
|
151
|
+
* width preset.
|
|
152
|
+
* block보다 우선 적용된다.
|
|
153
|
+
*/
|
|
154
|
+
width?: FormFieldWidth;
|
|
148
155
|
/**
|
|
149
156
|
* true면 readonly 상태로 전환하고 aria-busy를 설정한다.
|
|
150
157
|
*/
|
|
@@ -31,31 +31,32 @@ const getIndicatorIcon = (size: CheckboxSize) =>
|
|
|
31
31
|
* @example
|
|
32
32
|
* <Checkbox size="medium" checked />
|
|
33
33
|
*/
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
)
|
|
39
|
-
const IndicatorIcon = getIndicatorIcon(size);
|
|
34
|
+
const Checkbox = forwardRef<HTMLButtonElement, CheckboxProps>(function Checkbox(
|
|
35
|
+
{ size = "medium", className, disabled, ...restProps },
|
|
36
|
+
ref,
|
|
37
|
+
) {
|
|
38
|
+
const IndicatorIcon = getIndicatorIcon(size);
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
40
|
+
return (
|
|
41
|
+
<CheckboxPrimitive.Root
|
|
42
|
+
ref={ref}
|
|
43
|
+
disabled={disabled}
|
|
44
|
+
data-size={size}
|
|
45
|
+
data-disabled={disabled ? "true" : undefined}
|
|
46
|
+
className={clsx(CHECKBOX_CLASSNAME, className)}
|
|
47
|
+
{...restProps}
|
|
48
|
+
>
|
|
49
|
+
<span className={CHECKBOX_SURFACE_CLASSNAME} aria-hidden="true">
|
|
50
|
+
<CheckboxPrimitive.Indicator className={CHECKBOX_INDICATOR_CLASSNAME}>
|
|
51
|
+
<IndicatorIcon aria-hidden />
|
|
52
|
+
</CheckboxPrimitive.Indicator>
|
|
53
|
+
</span>
|
|
54
|
+
</CheckboxPrimitive.Root>
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// forwardRef Tooltip 유지를 위해 displayName 지정.
|
|
59
|
+
Checkbox.displayName = "Checkbox";
|
|
59
60
|
|
|
60
61
|
/**
|
|
61
62
|
* 체크박스 필드 컴포넌트; label/helper 텍스트 래퍼
|
|
@@ -76,7 +77,7 @@ export const Checkbox = forwardRef<HTMLButtonElement, CheckboxProps>(
|
|
|
76
77
|
* @example
|
|
77
78
|
* <CheckboxField label="약관 동의" helperText="필수" checked />
|
|
78
79
|
*/
|
|
79
|
-
|
|
80
|
+
const CheckboxField = forwardRef<HTMLButtonElement, CheckboxFieldProps>(
|
|
80
81
|
function CheckboxField(
|
|
81
82
|
{
|
|
82
83
|
label,
|
|
@@ -146,3 +147,8 @@ export const CheckboxField = forwardRef<HTMLButtonElement, CheckboxFieldProps>(
|
|
|
146
147
|
);
|
|
147
148
|
},
|
|
148
149
|
);
|
|
150
|
+
|
|
151
|
+
// forwardRef Tooltip 유지를 위해 displayName 지정.
|
|
152
|
+
CheckboxField.displayName = "CheckboxField";
|
|
153
|
+
|
|
154
|
+
export { Checkbox, CheckboxField };
|
|
@@ -19,7 +19,7 @@ const DropdownTemplate = ({
|
|
|
19
19
|
selectedIds = [],
|
|
20
20
|
onSelect,
|
|
21
21
|
size = "medium",
|
|
22
|
-
|
|
22
|
+
width = "match",
|
|
23
23
|
rootProps,
|
|
24
24
|
containerProps,
|
|
25
25
|
menuListProps,
|
|
@@ -27,11 +27,7 @@ const DropdownTemplate = ({
|
|
|
27
27
|
return (
|
|
28
28
|
<DropdownRoot {...rootProps}>
|
|
29
29
|
<DropdownTrigger asChild>{trigger}</DropdownTrigger>
|
|
30
|
-
<DropdownContainer
|
|
31
|
-
{...containerProps}
|
|
32
|
-
size={size}
|
|
33
|
-
matchTriggerWidth={matchTriggerWidth}
|
|
34
|
-
>
|
|
30
|
+
<DropdownContainer {...containerProps} size={size} width={width}>
|
|
35
31
|
<DropdownMenuList {...menuListProps}>
|
|
36
32
|
{items.map(item => (
|
|
37
33
|
<DropdownMenuItem
|
|
@@ -39,8 +35,8 @@ const DropdownTemplate = ({
|
|
|
39
35
|
label={item.label}
|
|
40
36
|
description={item.description}
|
|
41
37
|
disabled={item.disabled}
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
left={item.left}
|
|
39
|
+
right={item.right}
|
|
44
40
|
multiple={item.multiple}
|
|
45
41
|
isSelected={selectedIds?.includes(item.id)}
|
|
46
42
|
onSelect={event => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
|
4
4
|
import clsx from "clsx";
|
|
5
|
-
import { forwardRef, useEffect, useState } from "react";
|
|
5
|
+
import { forwardRef, useEffect, useMemo, useState } from "react";
|
|
6
6
|
|
|
7
7
|
import type { DropdownContainerProps } from "../../types/props";
|
|
8
8
|
import { useDropdownContext } from "./Provider";
|
|
@@ -11,8 +11,8 @@ import { useDropdownContext } from "./Provider";
|
|
|
11
11
|
* Dropdown container; trigger width 동기화 및 portal 관리
|
|
12
12
|
* @component
|
|
13
13
|
* @param {DropdownContainerProps} props Dropdown container props
|
|
14
|
-
* @param {
|
|
15
|
-
* @param {
|
|
14
|
+
* @param {DropdownPanelWidth} [props.width="match"] panel width 옵션
|
|
15
|
+
* @param {DropdownSize} [props.size="medium"] option height scale
|
|
16
16
|
*/
|
|
17
17
|
const DropdownContainer = forwardRef<HTMLDivElement, DropdownContainerProps>(
|
|
18
18
|
(
|
|
@@ -20,7 +20,7 @@ const DropdownContainer = forwardRef<HTMLDivElement, DropdownContainerProps>(
|
|
|
20
20
|
children,
|
|
21
21
|
className,
|
|
22
22
|
size = "medium",
|
|
23
|
-
|
|
23
|
+
width = "match",
|
|
24
24
|
portalContainer,
|
|
25
25
|
align = "start",
|
|
26
26
|
side = "bottom",
|
|
@@ -33,9 +33,34 @@ const DropdownContainer = forwardRef<HTMLDivElement, DropdownContainerProps>(
|
|
|
33
33
|
) => {
|
|
34
34
|
const { triggerRef } = useDropdownContext("Dropdown.Container");
|
|
35
35
|
const [panelWidth, setPanelWidth] = useState<number>();
|
|
36
|
+
const shouldMatchTriggerWidth = width === "match";
|
|
37
|
+
|
|
38
|
+
const resolvedMinWidth = useMemo(() => {
|
|
39
|
+
if (shouldMatchTriggerWidth) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (typeof width === "number") {
|
|
44
|
+
return `${width}px`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (typeof width === "string") {
|
|
48
|
+
if (width === "fit-content") {
|
|
49
|
+
return "fit-content";
|
|
50
|
+
}
|
|
51
|
+
if (width === "max-content") {
|
|
52
|
+
return "max-content";
|
|
53
|
+
}
|
|
54
|
+
if (width.trim().length > 0 && width !== "match") {
|
|
55
|
+
return width;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return undefined;
|
|
60
|
+
}, [shouldMatchTriggerWidth, width]);
|
|
36
61
|
|
|
37
62
|
useEffect(() => {
|
|
38
|
-
if (!
|
|
63
|
+
if (!shouldMatchTriggerWidth) {
|
|
39
64
|
return;
|
|
40
65
|
}
|
|
41
66
|
|
|
@@ -60,7 +85,7 @@ const DropdownContainer = forwardRef<HTMLDivElement, DropdownContainerProps>(
|
|
|
60
85
|
return () => {
|
|
61
86
|
observer.disconnect();
|
|
62
87
|
};
|
|
63
|
-
}, [
|
|
88
|
+
}, [shouldMatchTriggerWidth, triggerRef]);
|
|
64
89
|
|
|
65
90
|
const content = (
|
|
66
91
|
<DropdownMenu.Content
|
|
@@ -73,7 +98,10 @@ const DropdownContainer = forwardRef<HTMLDivElement, DropdownContainerProps>(
|
|
|
73
98
|
className={clsx("dropdown-panel", `dropdown-panel-${size}`, className)}
|
|
74
99
|
style={{
|
|
75
100
|
...style,
|
|
76
|
-
width:
|
|
101
|
+
width:
|
|
102
|
+
shouldMatchTriggerWidth && panelWidth ? panelWidth : style?.width,
|
|
103
|
+
minWidth:
|
|
104
|
+
resolvedMinWidth !== undefined ? resolvedMinWidth : style?.minWidth,
|
|
77
105
|
}}
|
|
78
106
|
>
|
|
79
107
|
{children}
|
|
@@ -20,8 +20,8 @@ const DropdownMenuItem = forwardRef<HTMLDivElement, DropdownMenuItemProps>(
|
|
|
20
20
|
{
|
|
21
21
|
label,
|
|
22
22
|
description,
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
left,
|
|
24
|
+
right,
|
|
25
25
|
isSelected = false,
|
|
26
26
|
multiple = false,
|
|
27
27
|
className,
|
|
@@ -34,9 +34,9 @@ const DropdownMenuItem = forwardRef<HTMLDivElement, DropdownMenuItemProps>(
|
|
|
34
34
|
) => {
|
|
35
35
|
const labelContent = label ?? children;
|
|
36
36
|
const hasDescription = Boolean(description);
|
|
37
|
-
const shouldRenderCheckbox = multiple && !
|
|
37
|
+
const shouldRenderCheckbox = multiple && !left;
|
|
38
38
|
|
|
39
|
-
const
|
|
39
|
+
const renderLeft = () => {
|
|
40
40
|
if (shouldRenderCheckbox) {
|
|
41
41
|
const checkboxClassName = clsx(
|
|
42
42
|
"dropdown-menu-item-checkbox",
|
|
@@ -53,14 +53,14 @@ const DropdownMenuItem = forwardRef<HTMLDivElement, DropdownMenuItemProps>(
|
|
|
53
53
|
} as CheckboxProps;
|
|
54
54
|
|
|
55
55
|
return (
|
|
56
|
-
<span className="dropdown-menu-item-
|
|
56
|
+
<span className="dropdown-menu-item-left" aria-hidden="true">
|
|
57
57
|
<Checkbox size="medium" {...checkboxAdditionalProps} />
|
|
58
58
|
</span>
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
if (
|
|
63
|
-
return <span className="dropdown-menu-item-
|
|
62
|
+
if (left) {
|
|
63
|
+
return <span className="dropdown-menu-item-left">{left}</span>;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
return null;
|
|
@@ -82,7 +82,7 @@ const DropdownMenuItem = forwardRef<HTMLDivElement, DropdownMenuItemProps>(
|
|
|
82
82
|
onSelectHandler?.(event);
|
|
83
83
|
}}
|
|
84
84
|
>
|
|
85
|
-
{
|
|
85
|
+
{renderLeft()}
|
|
86
86
|
<span className="dropdown-menu-item-body">
|
|
87
87
|
{labelContent ? (
|
|
88
88
|
<span className="dropdown-menu-item-label">{labelContent}</span>
|
|
@@ -93,8 +93,8 @@ const DropdownMenuItem = forwardRef<HTMLDivElement, DropdownMenuItemProps>(
|
|
|
93
93
|
</span>
|
|
94
94
|
) : null}
|
|
95
95
|
</span>
|
|
96
|
-
{
|
|
97
|
-
<span className="dropdown-menu-item-
|
|
96
|
+
{right ? (
|
|
97
|
+
<span className="dropdown-menu-item-right">{right}</span>
|
|
98
98
|
) : null}
|
|
99
99
|
</DropdownMenu.Item>
|
|
100
100
|
</li>
|
|
@@ -2,7 +2,16 @@ import { DropdownFoundation } from "./foundation";
|
|
|
2
2
|
import DropdownTemplate from "./Template";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Dropdown
|
|
5
|
+
* Dropdown
|
|
6
|
+
* - Provider
|
|
7
|
+
* - Root
|
|
8
|
+
* - Container
|
|
9
|
+
* - Menu
|
|
10
|
+
* - Item
|
|
11
|
+
* - List
|
|
12
|
+
* - Panel
|
|
13
|
+
* - Trigger
|
|
14
|
+
* - Template
|
|
6
15
|
*/
|
|
7
16
|
export const Dropdown = {
|
|
8
17
|
...DropdownFoundation,
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
--dropdown-text-medium-size: 16px;
|
|
26
26
|
--dropdown-text-medium-line-height: 24px;
|
|
27
27
|
--dropdown-text-medium-letter-spacing: 0;
|
|
28
|
-
--dropdown-text-large-size:
|
|
28
|
+
--dropdown-text-large-size: 19px;
|
|
29
29
|
--dropdown-text-large-line-height: 26px;
|
|
30
30
|
--dropdown-text-large-letter-spacing: 0;
|
|
31
31
|
--dropdown-text-weight: 400;
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
--dropdown-description-size: 14px;
|
|
35
35
|
--dropdown-description-line-height: 22px;
|
|
36
36
|
|
|
37
|
-
--dropdown-option-height-small:
|
|
38
|
-
--dropdown-option-height-medium:
|
|
39
|
-
--dropdown-option-height-large:
|
|
37
|
+
--dropdown-option-height-small: var(--theme-size-medium-1, 40px);
|
|
38
|
+
--dropdown-option-height-medium: var(--theme-size-medium-2, 48px);
|
|
39
|
+
--dropdown-option-height-large: var(--theme-size-medium-3, 56px);
|
|
40
40
|
}
|
|
@@ -6,6 +6,19 @@ import type { DropdownMenuProps } from "@radix-ui/react-dropdown-menu";
|
|
|
6
6
|
*/
|
|
7
7
|
export type DropdownSize = "small" | "medium" | "large";
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Dropdown panel width 옵션
|
|
11
|
+
* - "match": trigger width 추적
|
|
12
|
+
* - "fit-content" | "max-content": 고정 넓이 프리셋
|
|
13
|
+
* - string/number: min-width 커스텀 값
|
|
14
|
+
*/
|
|
15
|
+
export type DropdownPanelWidth =
|
|
16
|
+
| "match"
|
|
17
|
+
| "fit-content"
|
|
18
|
+
| "max-content"
|
|
19
|
+
| string
|
|
20
|
+
| number;
|
|
21
|
+
|
|
9
22
|
/**
|
|
10
23
|
* Dropdown root props
|
|
11
24
|
* @property {boolean} [modal] Radix modal 모드 여부
|