@uniai-fe/uds-primitives 0.13.2 → 0.14.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/dist/styles.css
CHANGED
|
@@ -5755,9 +5755,9 @@ figure.chip {
|
|
|
5755
5755
|
}
|
|
5756
5756
|
|
|
5757
5757
|
.segmented-control {
|
|
5758
|
-
--segmented-height:
|
|
5759
|
-
--segmented-padding:
|
|
5760
|
-
--segmented-radius:
|
|
5758
|
+
--segmented-height: 32px;
|
|
5759
|
+
--segmented-padding: var(--spacing-padding-1);
|
|
5760
|
+
--segmented-radius: calc(var(--segmented-height) / 2);
|
|
5761
5761
|
--segmented-bg: var(--color-bg-alternative-cool-gray, #f2f2f3);
|
|
5762
5762
|
--segmented-indicator-bg: var(--color-common-100, #ffffff);
|
|
5763
5763
|
--segmented-indicator-shadow: 2px 2px 4px rgba(0, 0, 0, 0.02);
|
|
@@ -5765,11 +5765,15 @@ figure.chip {
|
|
|
5765
5765
|
--segmented-label-active-color: var(--color-label-strong, #181a1b);
|
|
5766
5766
|
--segmented-disabled-opacity: 0.4;
|
|
5767
5767
|
--segmented-gap: 2px;
|
|
5768
|
-
--segmented-item-
|
|
5769
|
-
|
|
5770
|
-
|
|
5768
|
+
--segmented-item-height: calc(
|
|
5769
|
+
var(--segmented-height) - (var(--segmented-padding) * 2)
|
|
5770
|
+
);
|
|
5771
|
+
--segmented-item-padding-x: var(--spacing-padding-7);
|
|
5772
|
+
--segmented-item-padding-y: 0px;
|
|
5773
|
+
--segmented-item-font-size: 13px;
|
|
5771
5774
|
--segmented-item-font-weight: 500;
|
|
5772
|
-
--segmented-item-line-height:
|
|
5775
|
+
--segmented-item-line-height: 1em;
|
|
5776
|
+
--segmented-item-letter-spacing: 0px;
|
|
5773
5777
|
position: relative;
|
|
5774
5778
|
display: block;
|
|
5775
5779
|
box-sizing: border-box;
|
|
@@ -5782,6 +5786,40 @@ figure.chip {
|
|
|
5782
5786
|
overflow: hidden;
|
|
5783
5787
|
}
|
|
5784
5788
|
|
|
5789
|
+
.segmented-control:where([data-size="medium"]) {
|
|
5790
|
+
--segmented-height: 40px;
|
|
5791
|
+
--segmented-item-padding-x: var(--spacing-padding-8);
|
|
5792
|
+
--segmented-item-font-size: 15px;
|
|
5793
|
+
}
|
|
5794
|
+
|
|
5795
|
+
.segmented-control:where([data-size="large"]) {
|
|
5796
|
+
--segmented-height: 48px;
|
|
5797
|
+
--segmented-item-padding-x: var(--spacing-padding-9);
|
|
5798
|
+
--segmented-item-font-size: var(--font-label-large-size);
|
|
5799
|
+
}
|
|
5800
|
+
|
|
5801
|
+
.segmented-control:where([data-variant="squared"]) {
|
|
5802
|
+
--segmented-radius: var(--theme-radius-medium-4);
|
|
5803
|
+
--segmented-bg: rgba(227, 228, 232, 0.52);
|
|
5804
|
+
--segmented-indicator-bg: var(--color-common-100);
|
|
5805
|
+
--segmented-indicator-shadow:
|
|
5806
|
+
0 2px 4px rgba(0, 0, 0, 0.06), 0 0 2px rgba(0, 0, 0, 0.12);
|
|
5807
|
+
--segmented-label-color: var(--color-cool-gray-30);
|
|
5808
|
+
--segmented-label-active-color: var(--color-cool-gray-30);
|
|
5809
|
+
--segmented-gap: 0px;
|
|
5810
|
+
overflow: visible;
|
|
5811
|
+
}
|
|
5812
|
+
.segmented-control:where([data-variant="squared"]):where([data-size="small"]) {
|
|
5813
|
+
--segmented-radius: var(--theme-radius-medium-3);
|
|
5814
|
+
}
|
|
5815
|
+
.segmented-control:where([data-variant="squared"]):where([data-size="small"])
|
|
5816
|
+
.segmented-control-button:where([data-state="off"]) {
|
|
5817
|
+
border-radius: var(--theme-radius-medium-3);
|
|
5818
|
+
}
|
|
5819
|
+
.segmented-control:where([data-variant="squared"]):where([data-size="large"]) {
|
|
5820
|
+
--segmented-radius: var(--theme-radius-large-1);
|
|
5821
|
+
}
|
|
5822
|
+
|
|
5785
5823
|
.segmented-control:where([data-keep-selected="true"]) {
|
|
5786
5824
|
--segmented-disabled-opacity: 0.3;
|
|
5787
5825
|
}
|
|
@@ -5837,6 +5875,7 @@ figure.chip {
|
|
|
5837
5875
|
background: transparent;
|
|
5838
5876
|
cursor: pointer;
|
|
5839
5877
|
min-width: 0;
|
|
5878
|
+
height: var(--segmented-item-height);
|
|
5840
5879
|
border-radius: calc(var(--segmented-radius) - var(--segmented-padding));
|
|
5841
5880
|
padding: var(--segmented-item-padding-y) var(--segmented-item-padding-x);
|
|
5842
5881
|
transition: color 0.2s ease;
|
|
@@ -5854,8 +5893,9 @@ figure.chip {
|
|
|
5854
5893
|
font-size: var(--segmented-item-font-size);
|
|
5855
5894
|
font-weight: var(--segmented-item-font-weight);
|
|
5856
5895
|
line-height: var(--segmented-item-line-height);
|
|
5857
|
-
letter-spacing:
|
|
5896
|
+
letter-spacing: var(--segmented-item-letter-spacing);
|
|
5858
5897
|
color: var(--segmented-label-color);
|
|
5898
|
+
white-space: nowrap;
|
|
5859
5899
|
transition: color 0.2s ease;
|
|
5860
5900
|
}
|
|
5861
5901
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniai-fe/uds-primitives",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "UNIAI Design System; Primitives Components Package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -90,8 +90,8 @@
|
|
|
90
90
|
"@uniai-fe/eslint-config": "0.4.5",
|
|
91
91
|
"@uniai-fe/next-devkit": "0.4.1",
|
|
92
92
|
"@uniai-fe/tsconfig": "0.2.0",
|
|
93
|
-
"@uniai-fe/
|
|
94
|
-
"@uniai-fe/
|
|
93
|
+
"@uniai-fe/util-functions": "0.4.3",
|
|
94
|
+
"@uniai-fe/uds-foundation": "0.6.0"
|
|
95
95
|
},
|
|
96
96
|
"scripts": {
|
|
97
97
|
"check:pre-commit": "pnpm --dir ../../.. run check:pre-commit",
|
|
@@ -22,16 +22,18 @@ const toNullableValue = (value?: SegmentedControlValue) =>
|
|
|
22
22
|
value === undefined || value === "" ? undefined : value;
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
* @
|
|
27
|
-
* @
|
|
28
|
-
* @
|
|
29
|
-
* @
|
|
30
|
-
* @
|
|
31
|
-
* @
|
|
32
|
-
* @
|
|
33
|
-
* @
|
|
34
|
-
* @
|
|
25
|
+
* SegmentedControl; 텍스트 옵션의 단일 선택
|
|
26
|
+
* @component
|
|
27
|
+
* @property {SegmentedControlOption[]} options 고유한 value와 텍스트 label을 가진 선택지
|
|
28
|
+
* @property {string} ariaLabel 라디오 그룹의 접근 가능한 이름
|
|
29
|
+
* @property {boolean} [keepSelected=true] 이미 선택한 항목의 선택 유지 여부
|
|
30
|
+
* @property {"rounded" | "squared"} [variant=rounded] 기존 둥근 형태 또는 각진 형태
|
|
31
|
+
* @property {"small" | "medium" | "large"} [size=small] 두 외형이 공유하는 크기
|
|
32
|
+
* @property {SegmentedControlValue} [value] 제어형 선택값. 빈 문자열은 선택 없음
|
|
33
|
+
* @property {SegmentedControlValue} [defaultValue] 비제어형 초기 선택값
|
|
34
|
+
* @property {(value: SegmentedControlValue) => void} [onValueChange] 선택값 변경. 해제하면 빈 문자열
|
|
35
|
+
* @property {string} [className] 최상위 컨테이너의 추가 className
|
|
36
|
+
* @desc native 라디오 그룹의 선택·키보드·disabled 계약을 유지하고 indicator와 목록을 구성한다.
|
|
35
37
|
*/
|
|
36
38
|
const SegmentedControl = forwardRef<HTMLDivElement, SegmentedControlProps>(
|
|
37
39
|
(
|
|
@@ -39,6 +41,8 @@ const SegmentedControl = forwardRef<HTMLDivElement, SegmentedControlProps>(
|
|
|
39
41
|
options,
|
|
40
42
|
ariaLabel,
|
|
41
43
|
keepSelected = true,
|
|
44
|
+
variant = "rounded",
|
|
45
|
+
size = "small",
|
|
42
46
|
className,
|
|
43
47
|
onValueChange,
|
|
44
48
|
value: valueProp,
|
|
@@ -185,10 +189,10 @@ const SegmentedControl = forwardRef<HTMLDivElement, SegmentedControlProps>(
|
|
|
185
189
|
});
|
|
186
190
|
}, [options, resolvedValue]);
|
|
187
191
|
|
|
188
|
-
//
|
|
192
|
+
// 같은 컨테이너에서 외형·크기만 바뀌어도 선택면을 새 버튼 geometry에 맞춘다.
|
|
189
193
|
useLayoutEffect(() => {
|
|
190
194
|
measureIndicator();
|
|
191
|
-
}, [measureIndicator]);
|
|
195
|
+
}, [measureIndicator, variant, size]);
|
|
192
196
|
|
|
193
197
|
useEffect(() => {
|
|
194
198
|
if (!rootRef.current || typeof ResizeObserver === "undefined") {
|
|
@@ -227,6 +231,8 @@ const SegmentedControl = forwardRef<HTMLDivElement, SegmentedControlProps>(
|
|
|
227
231
|
"segmented-control segmented-control-container",
|
|
228
232
|
className,
|
|
229
233
|
)}
|
|
234
|
+
data-variant={variant}
|
|
235
|
+
data-size={size}
|
|
230
236
|
data-keep-selected={keepSelected ? "true" : undefined}
|
|
231
237
|
>
|
|
232
238
|
{/* indicator와 list를 분리해 DOM 구조가 단순한 flex 계층을 유지한다. */}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
.segmented-control {
|
|
2
|
-
--segmented-height:
|
|
3
|
-
--segmented-padding:
|
|
4
|
-
--segmented-radius:
|
|
2
|
+
--segmented-height: 32px;
|
|
3
|
+
--segmented-padding: var(--spacing-padding-1);
|
|
4
|
+
--segmented-radius: calc(var(--segmented-height) / 2);
|
|
5
5
|
--segmented-bg: var(--color-bg-alternative-cool-gray, #f2f2f3);
|
|
6
6
|
--segmented-indicator-bg: var(--color-common-100, #ffffff);
|
|
7
7
|
--segmented-indicator-shadow: 2px 2px 4px rgba(0, 0, 0, 0.02);
|
|
@@ -9,12 +9,15 @@
|
|
|
9
9
|
--segmented-label-active-color: var(--color-label-strong, #181a1b);
|
|
10
10
|
--segmented-disabled-opacity: 0.4;
|
|
11
11
|
--segmented-gap: 2px;
|
|
12
|
-
--segmented-item-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
--segmented-item-height: calc(
|
|
13
|
+
var(--segmented-height) - (var(--segmented-padding) * 2)
|
|
14
|
+
);
|
|
15
|
+
--segmented-item-padding-x: var(--spacing-padding-7);
|
|
16
|
+
--segmented-item-padding-y: 0px;
|
|
17
|
+
--segmented-item-font-size: 13px;
|
|
16
18
|
--segmented-item-font-weight: 500;
|
|
17
|
-
--segmented-item-line-height:
|
|
19
|
+
--segmented-item-line-height: 1em;
|
|
20
|
+
--segmented-item-letter-spacing: 0px;
|
|
18
21
|
position: relative;
|
|
19
22
|
display: block;
|
|
20
23
|
box-sizing: border-box;
|
|
@@ -27,6 +30,42 @@
|
|
|
27
30
|
overflow: hidden;
|
|
28
31
|
}
|
|
29
32
|
|
|
33
|
+
.segmented-control:where([data-size="medium"]) {
|
|
34
|
+
--segmented-height: 40px;
|
|
35
|
+
--segmented-item-padding-x: var(--spacing-padding-8);
|
|
36
|
+
--segmented-item-font-size: 15px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.segmented-control:where([data-size="large"]) {
|
|
40
|
+
--segmented-height: 48px;
|
|
41
|
+
--segmented-item-padding-x: var(--spacing-padding-9);
|
|
42
|
+
--segmented-item-font-size: var(--font-label-large-size);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.segmented-control:where([data-variant="squared"]) {
|
|
46
|
+
--segmented-radius: var(--theme-radius-medium-4);
|
|
47
|
+
--segmented-bg: rgba(227, 228, 232, 0.52);
|
|
48
|
+
--segmented-indicator-bg: var(--color-common-100);
|
|
49
|
+
--segmented-indicator-shadow:
|
|
50
|
+
0 2px 4px rgba(0, 0, 0, 0.06), 0 0 2px rgba(0, 0, 0, 0.12);
|
|
51
|
+
--segmented-label-color: var(--color-cool-gray-30);
|
|
52
|
+
--segmented-label-active-color: var(--color-cool-gray-30);
|
|
53
|
+
--segmented-gap: 0px;
|
|
54
|
+
overflow: visible;
|
|
55
|
+
|
|
56
|
+
&:where([data-size="small"]) {
|
|
57
|
+
--segmented-radius: var(--theme-radius-medium-3);
|
|
58
|
+
|
|
59
|
+
.segmented-control-button:where([data-state="off"]) {
|
|
60
|
+
border-radius: var(--theme-radius-medium-3);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
&:where([data-size="large"]) {
|
|
65
|
+
--segmented-radius: var(--theme-radius-large-1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
30
69
|
.segmented-control:where([data-keep-selected="true"]) {
|
|
31
70
|
--segmented-disabled-opacity: 0.3;
|
|
32
71
|
}
|
|
@@ -82,6 +121,7 @@
|
|
|
82
121
|
background: transparent;
|
|
83
122
|
cursor: pointer;
|
|
84
123
|
min-width: 0;
|
|
124
|
+
height: var(--segmented-item-height);
|
|
85
125
|
border-radius: calc(var(--segmented-radius) - var(--segmented-padding));
|
|
86
126
|
padding: var(--segmented-item-padding-y) var(--segmented-item-padding-x);
|
|
87
127
|
transition: color 0.2s ease;
|
|
@@ -99,8 +139,9 @@
|
|
|
99
139
|
font-size: var(--segmented-item-font-size);
|
|
100
140
|
font-weight: var(--segmented-item-font-weight);
|
|
101
141
|
line-height: var(--segmented-item-line-height);
|
|
102
|
-
letter-spacing:
|
|
142
|
+
letter-spacing: var(--segmented-item-letter-spacing);
|
|
103
143
|
color: var(--segmented-label-color);
|
|
144
|
+
white-space: nowrap;
|
|
104
145
|
transition: color 0.2s ease;
|
|
105
146
|
}
|
|
106
147
|
|
|
@@ -26,22 +26,68 @@ export interface SegmentedControlOption {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* SegmentedControl
|
|
30
|
-
* @property {SegmentedControlOption[]} options 렌더할 옵션 배열.
|
|
31
|
-
* @property {string} ariaLabel 스크린리더용 라벨. 필수.
|
|
32
|
-
* @property {boolean} [keepSelected=true] 선택된 항목을 다시 클릭했을 때 해제하지 않고 유지할지 여부.
|
|
29
|
+
* SegmentedControl의 외형. rounded는 둥근 형태, squared는 각진 형태다.
|
|
33
30
|
*/
|
|
34
|
-
type
|
|
31
|
+
export type SegmentedControlVariant = "rounded" | "squared";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 두 외형이 공유하는 크기. small, medium, large를 지원한다.
|
|
35
|
+
*/
|
|
36
|
+
export type SegmentedControlSize = "small" | "medium" | "large";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* SegmentedControl 루트의 native div 속성. children은 options로 구성하므로 받지 않는다.
|
|
40
|
+
*/
|
|
41
|
+
export type SegmentedControlRootProps = Omit<
|
|
35
42
|
ComponentPropsWithoutRef<"div">,
|
|
36
43
|
"children"
|
|
37
44
|
>;
|
|
38
45
|
|
|
46
|
+
/**
|
|
47
|
+
* SegmentedControl 공개 입력
|
|
48
|
+
* @property {SegmentedControlOption[]} options 고유한 문자열 value와 텍스트 label을 가진 선택지
|
|
49
|
+
* @property {string} ariaLabel 라디오 그룹의 접근 가능한 이름
|
|
50
|
+
* @property {boolean} [keepSelected=true] 같은 항목을 다시 눌러도 선택을 유지할지 여부
|
|
51
|
+
* @property {"rounded" | "squared"} [variant=rounded] 기존 둥근 형태 또는 각진 형태
|
|
52
|
+
* @property {"small" | "medium" | "large"} [size=small] rounded와 squared가 공유하는 크기
|
|
53
|
+
* @property {SegmentedControlValue} [value] 제어형 선택값. 빈 문자열은 선택 없음
|
|
54
|
+
* @property {SegmentedControlValue} [defaultValue] 비제어형 초기 선택값
|
|
55
|
+
* @property {(value: SegmentedControlValue) => void} [onValueChange] 다음 선택값. 해제하면 빈 문자열
|
|
56
|
+
* @desc SegmentedControlRootProps의 native div 속성을 상속하며 선택 상태와 외형을 구성한다.
|
|
57
|
+
* @see SegmentedControlRootProps
|
|
58
|
+
*/
|
|
39
59
|
export interface SegmentedControlProps extends SegmentedControlRootProps {
|
|
60
|
+
/**
|
|
61
|
+
* 고유한 문자열 value와 텍스트 label을 가진 선택지
|
|
62
|
+
*/
|
|
40
63
|
options: SegmentedControlOption[];
|
|
64
|
+
/**
|
|
65
|
+
* 라디오 그룹의 접근 가능한 이름
|
|
66
|
+
*/
|
|
41
67
|
ariaLabel: string;
|
|
68
|
+
/**
|
|
69
|
+
* 기본 true. false이면 같은 항목을 다시 눌러 선택을 해제할 수 있다.
|
|
70
|
+
*/
|
|
42
71
|
keepSelected?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* 허용 값은 "rounded" | "squared". 기본 rounded는 둥근 모서리를 사용한다.
|
|
74
|
+
*/
|
|
75
|
+
variant?: SegmentedControlVariant;
|
|
76
|
+
/**
|
|
77
|
+
* 허용 값은 "small" | "medium" | "large". 기본 small이며 두 외형에 같은 높이와 여백을 적용한다.
|
|
78
|
+
*/
|
|
79
|
+
size?: SegmentedControlSize;
|
|
80
|
+
/**
|
|
81
|
+
* 제어형 선택값. 빈 문자열은 선택 없음이며 undefined는 비제어형으로 처리한다.
|
|
82
|
+
*/
|
|
43
83
|
value?: SegmentedControlValue;
|
|
84
|
+
/**
|
|
85
|
+
* 비제어형 초기 선택값
|
|
86
|
+
*/
|
|
44
87
|
defaultValue?: SegmentedControlValue;
|
|
88
|
+
/**
|
|
89
|
+
* 다음 선택값을 전달하며 선택 해제 시 빈 문자열을 전달한다.
|
|
90
|
+
*/
|
|
45
91
|
onValueChange?: (value: SegmentedControlValue) => void;
|
|
46
92
|
}
|
|
47
93
|
|