@uniai-fe/uds-primitives 0.4.7 → 0.5.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 (36) hide show
  1. package/README.md +3 -0
  2. package/dist/styles.css +325 -138
  3. package/package.json +1 -1
  4. package/src/components/badge/index.tsx +1 -3
  5. package/src/components/badge/markup/Badge.tsx +26 -15
  6. package/src/components/badge/styles/badge.scss +244 -0
  7. package/src/components/badge/styles/index.scss +2 -187
  8. package/src/components/badge/styles/variables.scss +41 -0
  9. package/src/components/badge/types/index.ts +3 -77
  10. package/src/components/badge/types/options.ts +25 -0
  11. package/src/components/badge/types/props.ts +53 -0
  12. package/src/components/switch/index.scss +1 -0
  13. package/src/components/switch/index.tsx +10 -0
  14. package/src/components/switch/markup/Switch.tsx +76 -0
  15. package/src/components/switch/markup/index.ts +1 -0
  16. package/src/components/switch/styles/index.scss +2 -0
  17. package/src/components/switch/styles/switch.scss +63 -0
  18. package/src/components/switch/styles/variables.scss +21 -0
  19. package/src/components/switch/types/index.ts +1 -0
  20. package/src/components/switch/types/switch.ts +46 -0
  21. package/src/components/tab/index.tsx +1 -2
  22. package/src/components/tab/markup/Label.tsx +22 -0
  23. package/src/components/tab/markup/TabList.tsx +27 -29
  24. package/src/components/tab/markup/TabRoot.tsx +16 -71
  25. package/src/components/tab/markup/TabTrigger.tsx +2 -15
  26. package/src/components/tab/styles/index.scss +2 -198
  27. package/src/components/tab/styles/tab.scss +190 -0
  28. package/src/components/tab/styles/variables.scss +39 -0
  29. package/src/components/tab/types/index.ts +29 -6
  30. package/src/components/tab/utils/tab-context.ts +1 -6
  31. package/src/index.scss +1 -0
  32. package/src/index.tsx +1 -0
  33. package/src/components/badge/hooks/index.ts +0 -4
  34. package/src/components/badge/img/.gitkeep +0 -0
  35. package/src/components/badge/utils/index.ts +0 -21
  36. package/src/components/tab/hooks/index.ts +0 -4
@@ -1,198 +1,2 @@
1
- .tab-root {
2
- /* Figma node 694:4619 측정값을 CSS 변수로 고정해 Storybook과 실 서비스 간 시각 편차를 줄인다. */
3
- --tab-label-font-size: var(--font-heading-xsmall-size, 17px);
4
- --tab-label-font-weight: var(--font-heading-xsmall-weight, 600);
5
- --tab-label-line-height: 1.4;
6
- --tab-label-letter-spacing: 0px;
7
- --tab-gap: var(--spacing-gap-2, 8px);
8
- --tab-padding-y: 10px;
9
- --tab-padding-x: var(--spacing-padding-8, 24px);
10
- --tab-icon-gap: 6px;
11
- --tab-line-track-color: var(--color-border-divider, #f2f2f3);
12
- --tab-line-track-height: 1px;
13
- --tab-line-indicator-height: 2px;
14
- --tab-color-active-default: #1a6aff;
15
- --tab-color-active: var(--tab-color-active-default);
16
- --tab-color-hover: color-mix(in srgb, var(--tab-color-active), #000 15%);
17
- --tab-line-hover-color: var(--tab-color-hover, var(--tab-color-active));
18
- --tab-fill-hover-bg: var(--color-bg-alternative-cool-gray, #f2f2f3);
19
- --tab-fill-active-color: var(--color-common-100, #ffffff);
20
- --tab-inactive-color: var(--color-label-alternative, #afb1b6);
21
- --tab-disabled-opacity: 0.4;
22
- --tab-height: 48px;
23
- width: 100%;
24
- display: flex;
25
- flex-direction: column;
26
- gap: var(--spacing-gap-3);
27
- }
28
-
29
- .tab-root:where([data-scale="small"]) {
30
- --tab-label-font-size: var(--font-heading-xxsmall-size, 15px);
31
- --tab-label-font-weight: var(--font-heading-xxsmall-weight, 600);
32
- --tab-height: 40px;
33
- --tab-padding-x: var(--spacing-padding-4, 8px);
34
- }
35
-
36
- .tab-root:where([data-scale="medium"]) {
37
- --tab-label-font-size: var(--font-heading-xsmall-size, 17px);
38
- --tab-label-font-weight: var(--font-heading-xsmall-weight, 600);
39
- --tab-height: 48px;
40
- }
41
-
42
- .tab-root:where([data-scale="large"]) {
43
- --tab-label-font-size: var(--font-heading-small-size, 19px);
44
- --tab-label-font-weight: var(--font-heading-small-weight, 600);
45
- --tab-height: 56px;
46
- --tab-padding-x: var(--spacing-padding-8, 24px);
47
- }
48
-
49
- .tab-list {
50
- display: flex;
51
- align-items: stretch;
52
- gap: var(--tab-gap);
53
- width: fit-content;
54
- overflow-x: auto;
55
- scrollbar-width: none;
56
- }
57
-
58
- .tab-list::-webkit-scrollbar {
59
- display: none;
60
- }
61
-
62
- .tab-list:where([data-full-width="true"]) {
63
- width: 100%;
64
- }
65
-
66
- .tab-trigger {
67
- position: relative;
68
- display: flex;
69
- align-items: center;
70
- justify-content: center;
71
- gap: var(--tab-icon-gap);
72
- min-height: var(--tab-height);
73
- padding: var(--tab-padding-y) var(--tab-padding-x);
74
- background: transparent;
75
- border: none;
76
- cursor: pointer;
77
- transition: background-color 0.2s ease;
78
- }
79
-
80
- .tab-trigger:where(:focus-visible) {
81
- outline: 2px solid var(--color-focus-ring, var(--color-primary-default));
82
- outline-offset: 2px;
83
- }
84
-
85
- .tab-trigger:where([data-disabled="true"]) {
86
- cursor: not-allowed;
87
- opacity: var(--tab-disabled-opacity);
88
- }
89
-
90
- .tab-trigger-icon {
91
- display: flex;
92
- align-items: center;
93
- justify-content: center;
94
- }
95
-
96
- .tab-trigger-label {
97
- display: flex;
98
- align-items: center;
99
- justify-content: center;
100
- font-size: var(--tab-label-font-size);
101
- font-weight: var(--tab-label-font-weight);
102
- line-height: var(--tab-label-line-height);
103
- letter-spacing: var(--tab-label-letter-spacing);
104
- color: var(--tab-inactive-color);
105
- transition: color 0.2s ease;
106
- }
107
-
108
- .tab-list:where([data-variant="line"]) {
109
- position: relative;
110
- width: 100%;
111
- }
112
-
113
- .tab-list:where([data-variant="line"])::before {
114
- content: "";
115
- position: absolute;
116
- inset: auto 0 0;
117
- height: var(--tab-line-track-height, 1px);
118
- background: var(--tab-line-track-color);
119
- z-index: 0;
120
- }
121
-
122
- .tab-list:where([data-variant="line"][data-full-width="true"]) > .tab-trigger {
123
- flex: 1;
124
- }
125
-
126
- .tab-trigger:where([data-variant="line"])::after {
127
- content: "";
128
- position: absolute;
129
- left: 0;
130
- right: 0;
131
- bottom: -1px;
132
- height: var(--tab-line-indicator-height, 2px);
133
- background: transparent;
134
- transform: scaleX(0);
135
- transform-origin: center;
136
- z-index: 1;
137
- transition:
138
- transform 0.2s ease,
139
- background-color 0.2s ease;
140
- }
141
-
142
- .tab-trigger:where([data-variant="line"][data-state="active"])
143
- .tab-trigger-label {
144
- color: var(--tab-color-active);
145
- }
146
-
147
- .tab-trigger:where([data-variant="line"][data-state="active"])::after {
148
- background: var(--tab-color-active);
149
- transform: scaleX(1);
150
- }
151
-
152
- .tab-trigger:where([data-variant="line"]):where(
153
- :not([data-state="active"])
154
- ):hover
155
- .tab-trigger-label {
156
- color: var(--tab-line-hover-color);
157
- }
158
-
159
- .tab-list:where([data-variant="fill"]) {
160
- width: fit-content;
161
- border: none;
162
- background: transparent;
163
- border-radius: 0;
164
- gap: 0;
165
- }
166
-
167
- .tab-list:where([data-variant="fill"][data-full-width="true"]) {
168
- width: 100%;
169
- }
170
-
171
- .tab-list:where([data-variant="fill"][data-full-width="true"]) > .tab-trigger {
172
- flex: 1;
173
- }
174
-
175
- .tab-trigger:where([data-variant="fill"]) {
176
- border-radius: 12px;
177
- min-width: 0;
178
- }
179
-
180
- .tab-trigger:where([data-variant="fill"][data-state="active"]) {
181
- background: var(--tab-color-active);
182
- }
183
-
184
- .tab-trigger:where([data-variant="fill"][data-state="active"])
185
- .tab-trigger-label {
186
- color: var(--tab-fill-active-color);
187
- }
188
-
189
- .tab-trigger:where([data-variant="fill"]):where(
190
- :not([data-state="active"])
191
- ):hover {
192
- background: var(--tab-fill-hover-bg);
193
- }
194
-
195
- .tab-content {
196
- display: block;
197
- padding: var(--spacing-padding-6) 0;
198
- }
1
+ @use "./variables";
2
+ @use "./tab";
@@ -0,0 +1,190 @@
1
+ .tab-root {
2
+ --tab-height: var(--tab-height-medium);
3
+ --tab-label-font-size: var(--tab-label-font-size-medium);
4
+ --tab-label-font-weight: var(--tab-label-font-weight-medium);
5
+ --tab-label-line-height: var(--tab-label-line-height-medium);
6
+ --tab-label-letter-spacing: var(--tab-label-letter-spacing-medium);
7
+ width: 100%;
8
+ display: flex;
9
+ flex-direction: column;
10
+ gap: var(--tab-root-gap);
11
+ }
12
+
13
+ .tab-root:where([data-scale="small"]) {
14
+ --tab-height: var(--tab-height-small);
15
+ --tab-label-font-size: var(--tab-label-font-size-small);
16
+ --tab-label-font-weight: var(--tab-label-font-weight-small);
17
+ --tab-label-line-height: var(--tab-label-line-height-small);
18
+ --tab-label-letter-spacing: var(--tab-label-letter-spacing-small);
19
+ --tab-padding-x: var(--tab-padding-x-small);
20
+ }
21
+
22
+ .tab-root:where([data-scale="medium"]) {
23
+ --tab-height: var(--tab-height-medium);
24
+ --tab-label-font-size: var(--tab-label-font-size-medium);
25
+ --tab-label-font-weight: var(--tab-label-font-weight-medium);
26
+ --tab-label-line-height: var(--tab-label-line-height-medium);
27
+ --tab-label-letter-spacing: var(--tab-label-letter-spacing-medium);
28
+ --tab-padding-x: var(--spacing-padding-8);
29
+ }
30
+
31
+ .tab-root:where([data-scale="large"]) {
32
+ --tab-height: var(--tab-height-large);
33
+ --tab-label-font-size: var(--tab-label-font-size-large);
34
+ --tab-label-font-weight: var(--tab-label-font-weight-large);
35
+ --tab-label-line-height: var(--tab-label-line-height-large);
36
+ --tab-label-letter-spacing: var(--tab-label-letter-spacing-large);
37
+ --tab-padding-x: var(--spacing-padding-8);
38
+ }
39
+
40
+ .tab-list {
41
+ display: flex;
42
+ align-items: stretch;
43
+ gap: var(--tab-gap);
44
+ width: fit-content;
45
+ overflow-x: auto;
46
+ scrollbar-width: none;
47
+ }
48
+
49
+ .tab-list::-webkit-scrollbar {
50
+ display: none;
51
+ }
52
+
53
+ .tab-list:where([data-full-width="true"]) {
54
+ width: 100%;
55
+ }
56
+
57
+ .tab-trigger {
58
+ position: relative;
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ gap: var(--tab-icon-gap);
63
+ min-height: var(--tab-height);
64
+ padding: var(--tab-padding-y) var(--tab-padding-x);
65
+ background: transparent;
66
+ border: none;
67
+ cursor: pointer;
68
+ transition: background-color 0.2s ease;
69
+ }
70
+
71
+ .tab-trigger:where(:focus-visible) {
72
+ outline: 2px solid var(--color-primary-default);
73
+ outline-offset: 2px;
74
+ }
75
+
76
+ .tab-trigger:where([data-disabled="true"]) {
77
+ cursor: not-allowed;
78
+ opacity: var(--tab-disabled-opacity);
79
+ }
80
+
81
+ .tab-trigger-icon {
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ }
86
+
87
+ .tab-trigger-label {
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ font-size: var(--tab-label-font-size);
92
+ font-weight: var(--tab-label-font-weight);
93
+ line-height: var(--tab-label-line-height);
94
+ letter-spacing: var(--tab-label-letter-spacing);
95
+ color: var(--tab-inactive-color);
96
+ transition: color 0.2s ease;
97
+ }
98
+
99
+ .tab-list:where([data-variant="line"]) {
100
+ position: relative;
101
+ width: 100%;
102
+ }
103
+
104
+ .tab-list:where([data-variant="line"])::before {
105
+ content: "";
106
+ position: absolute;
107
+ inset: auto 0 0;
108
+ height: var(--tab-line-track-height);
109
+ background: var(--tab-line-track-color);
110
+ z-index: 0;
111
+ }
112
+
113
+ .tab-list:where([data-variant="line"][data-full-width="true"]) > .tab-trigger {
114
+ flex: 1;
115
+ }
116
+
117
+ .tab-trigger:where([data-variant="line"])::after {
118
+ content: "";
119
+ position: absolute;
120
+ left: 0;
121
+ right: 0;
122
+ /* 변경: scroll container에서 vertical overflow clip 리스크를 피하기 위해 indicator를 내부 바닥에 붙인다. */
123
+ bottom: 0;
124
+ height: var(--tab-line-indicator-height);
125
+ background: transparent;
126
+ transform: scaleX(0);
127
+ transform-origin: center;
128
+ z-index: 1;
129
+ transition:
130
+ transform 0.2s ease,
131
+ background-color 0.2s ease;
132
+ }
133
+
134
+ .tab-trigger:where([data-variant="line"][data-state="active"])
135
+ .tab-trigger-label {
136
+ color: var(--tab-color-active);
137
+ }
138
+
139
+ .tab-trigger:where([data-variant="line"][data-state="active"])::after {
140
+ background: var(--tab-color-active);
141
+ transform: scaleX(1);
142
+ }
143
+
144
+ .tab-trigger:where([data-variant="line"]):where(
145
+ :not([data-state="active"])
146
+ ):hover
147
+ .tab-trigger-label {
148
+ color: var(--tab-color-hover);
149
+ }
150
+
151
+ .tab-list:where([data-variant="fill"]) {
152
+ width: fit-content;
153
+ border: none;
154
+ background: transparent;
155
+ border-radius: 0;
156
+ gap: 0;
157
+ }
158
+
159
+ .tab-list:where([data-variant="fill"][data-full-width="true"]) {
160
+ width: 100%;
161
+ }
162
+
163
+ .tab-list:where([data-variant="fill"][data-full-width="true"]) > .tab-trigger {
164
+ flex: 1;
165
+ }
166
+
167
+ .tab-trigger:where([data-variant="fill"]) {
168
+ min-width: 0;
169
+ border-radius: 12px;
170
+ }
171
+
172
+ .tab-trigger:where([data-variant="fill"][data-state="active"]) {
173
+ background: var(--tab-color-active);
174
+ }
175
+
176
+ .tab-trigger:where([data-variant="fill"][data-state="active"])
177
+ .tab-trigger-label {
178
+ color: var(--tab-fill-active-color);
179
+ }
180
+
181
+ .tab-trigger:where([data-variant="fill"]):where(
182
+ :not([data-state="active"])
183
+ ):hover {
184
+ background: var(--tab-fill-hover-bg);
185
+ }
186
+
187
+ .tab-content {
188
+ display: block;
189
+ padding: var(--spacing-padding-6) 0;
190
+ }
@@ -0,0 +1,39 @@
1
+ :root {
2
+ --tab-height-small: 40px;
3
+ --tab-height-medium: 48px;
4
+ --tab-height-large: 56px;
5
+
6
+ /* 변경: Figma node 1176:834/781/739 기준으로 tab label typography를 exact spec으로 다시 고정한다. */
7
+ --tab-label-font-size-small: var(--font-label-medium-size);
8
+ --tab-label-font-weight-small: 700;
9
+ --tab-label-line-height-small: 1.4;
10
+ --tab-label-letter-spacing-small: 0px;
11
+
12
+ --tab-label-font-size-medium: var(--font-label-large-size);
13
+ --tab-label-font-weight-medium: 700;
14
+ --tab-label-line-height-medium: 1.4;
15
+ --tab-label-letter-spacing-medium: 0px;
16
+
17
+ --tab-label-font-size-large: var(--font-heading-small-size);
18
+ --tab-label-font-weight-large: 700;
19
+ --tab-label-line-height-large: 1.4;
20
+ --tab-label-letter-spacing-large: 0px;
21
+
22
+ --tab-gap: var(--spacing-gap-2);
23
+ --tab-root-gap: var(--spacing-gap-3);
24
+ --tab-padding-y: 10px;
25
+ --tab-padding-x: var(--spacing-padding-8);
26
+ --tab-padding-x-small: var(--spacing-padding-4);
27
+ --tab-icon-gap: 6px;
28
+ /* 변경: line track은 foundation에 존재하는 assistive border token(#f2f2f3)을 사용한다. */
29
+ --tab-line-track-color: var(--color-border-assistive);
30
+ --tab-line-track-height: 1px;
31
+ --tab-line-indicator-height: 2px;
32
+ --tab-color-active-default: var(--color-primary-default);
33
+ --tab-color-active: var(--tab-color-active-default);
34
+ --tab-color-hover: color-mix(in srgb, var(--tab-color-active), #000 15%);
35
+ --tab-fill-hover-bg: var(--color-bg-alternative-cool-gray);
36
+ --tab-fill-active-color: var(--color-common-100);
37
+ --tab-inactive-color: var(--color-label-alternative);
38
+ --tab-disabled-opacity: 0.4;
39
+ }
@@ -5,13 +5,17 @@ import type {
5
5
  TabsTriggerProps,
6
6
  } from "@radix-ui/react-tabs";
7
7
  import type { ReactNode } from "react";
8
+ import type { SlotTextProps } from "../../slot";
8
9
 
9
- export const TAB_VARIANTS = ["line", "fill"] as const;
10
- export const TAB_SCALES = ["small", "medium", "large"] as const;
11
-
12
- export type TabVariant = (typeof TAB_VARIANTS)[number];
13
- export type TabScale = (typeof TAB_SCALES)[number];
10
+ export type TabVariant = "line" | "fill";
11
+ export type TabScale = "small" | "medium" | "large";
14
12
 
13
+ /**
14
+ * Tab Types; Root props
15
+ * @property {TabVariant} [variant] line / fill 스타일 변형
16
+ * @property {TabScale} [scale] small / medium / large 스케일
17
+ * @property {string} [color] 활성 색상(CSS 변수 포함)
18
+ */
15
19
  export interface TabRootProps extends TabsProps {
16
20
  /**
17
21
  * line / fill 스타일 토글. 기본 line.
@@ -27,6 +31,13 @@ export interface TabRootProps extends TabsProps {
27
31
  color?: string;
28
32
  }
29
33
 
34
+ /**
35
+ * Tab Types; List props
36
+ * @property {TabVariant} [variant] 루트 variant override
37
+ * @property {TabScale} [scale] 루트 scale override
38
+ * @property {string} [color] 리스트 단위 활성 색상 override
39
+ * @property {boolean} [fullWidth] true면 trigger를 균등 분배
40
+ */
30
41
  export interface TabListProps extends TabsListProps {
31
42
  /**
32
43
  * 루트와 동일한 variant를 재지정할 수 있다.
@@ -46,6 +57,10 @@ export interface TabListProps extends TabsListProps {
46
57
  fullWidth?: boolean;
47
58
  }
48
59
 
60
+ /**
61
+ * Tab Types; Trigger props
62
+ * @property {React.ReactNode} [icon] 라벨 앞에 표시할 아이콘
63
+ */
49
64
  export interface TabTriggerProps extends TabsTriggerProps {
50
65
  /**
51
66
  * label 앞에 표시할 아이콘.
@@ -53,4 +68,12 @@ export interface TabTriggerProps extends TabsTriggerProps {
53
68
  icon?: ReactNode;
54
69
  }
55
70
 
56
- export type TabContentProps = TabsContentProps;
71
+ /**
72
+ * Tab Types; Content props
73
+ */
74
+ export interface TabContentProps extends TabsContentProps {}
75
+
76
+ /**
77
+ * Tab Types; Label props
78
+ */
79
+ export type TabLabelProps = SlotTextProps;
@@ -5,16 +5,11 @@ interface TabContextValue {
5
5
  variant: TabVariant;
6
6
  color: string;
7
7
  scale: TabScale;
8
- setSharedConfig?: (
9
- nextVariant: TabVariant,
10
- nextColor: string,
11
- nextScale: TabScale,
12
- ) => void;
13
8
  }
14
9
 
15
10
  const DEFAULT_TAB_CONTEXT_VALUE: TabContextValue = {
16
11
  variant: "line",
17
- color: "var(--tab-color-active-default, #1a6aff)",
12
+ color: "var(--tab-color-active-default)",
18
13
  scale: "medium",
19
14
  };
20
15
 
package/src/index.scss CHANGED
@@ -19,6 +19,7 @@
19
19
  @use "./components/segmented-control";
20
20
  @use "./components/select";
21
21
  @use "./components/spinner";
22
+ @use "./components/switch";
22
23
  @use "./components/tab";
23
24
  @use "./components/table";
24
25
  @use "./components/tooltip";
package/src/index.tsx CHANGED
@@ -25,6 +25,7 @@ export * from "./components/segmented-control";
25
25
  export * from "./components/select";
26
26
  export * from "./components/slot";
27
27
  export * from "./components/spinner";
28
+ export * from "./components/switch";
28
29
  export * from "./components/tab";
29
30
  export * from "./components/table";
30
31
  export * from "./components/tooltip";
@@ -1,4 +0,0 @@
1
- /**
2
- * TODO(badge): 접근성/상태 계산 hook을 정의한다.
3
- */
4
- export {};
File without changes
@@ -1,21 +0,0 @@
1
- import clsx from "clsx";
2
- import type { BadgeClassNameOptions } from "../types";
3
-
4
- const BADGE_CLASSNAME = "badge";
5
-
6
- /**
7
- * Badge 데이터 축을 className 구조로 묶어서 Story/override 시 일관되게 쓴다.
8
- */
9
- const composeBadgeClassName = ({
10
- size,
11
- style,
12
- className,
13
- }: BadgeClassNameOptions) =>
14
- clsx(
15
- BADGE_CLASSNAME,
16
- `${BADGE_CLASSNAME}--size-${size}`,
17
- `${BADGE_CLASSNAME}--style-${style}`,
18
- className,
19
- );
20
-
21
- export { BADGE_CLASSNAME, composeBadgeClassName };
@@ -1,4 +0,0 @@
1
- /**
2
- * TODO(tab-menu): 접근성/상태 계산 hook을 정의한다.
3
- */
4
- export {};