@uniai-fe/uds-primitives 0.0.7 → 0.0.8
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/README.md +64 -5
- package/dist/styles.css +236 -279
- package/package.json +4 -4
- package/src/components/badge/markup/Badge.tsx +10 -0
- package/src/components/badge/styles/index.scss +2 -2
- package/src/components/badge/types/index.ts +1 -1
- package/src/components/button/index.scss +3 -1
- package/src/components/button/index.tsx +9 -1
- package/src/components/button/markup/ButtonDefault.tsx +162 -0
- package/src/components/button/markup/ButtonRounded.tsx +48 -0
- package/src/components/button/markup/ButtonText.tsx +49 -0
- package/src/components/button/markup/index.ts +3 -0
- package/src/components/button/styles/{index.scss → button.scss} +148 -362
- package/src/components/button/styles/round-button.scss +56 -0
- package/src/components/button/styles/text-button.scss +96 -0
- package/src/components/button/types/index.ts +110 -35
- package/src/components/button/types/templates.ts +33 -0
- package/src/components/button/utils/index.ts +19 -19
- package/src/components/checkbox/markup/Checkbox.tsx +20 -2
- package/src/components/checkbox/types/checkbox.ts +16 -0
- package/src/components/chip/markup/Chip.tsx +8 -0
- package/src/components/dialog/markup/{confirm-dialog.tsx → ConfirmDialog.tsx} +23 -0
- package/src/components/dialog/markup/{notice-dialog.tsx → NoticeDialog.tsx} +18 -0
- package/src/components/dialog/markup/index.tsx +2 -2
- package/src/components/dialog/types/index.ts +43 -0
- package/src/components/drawer/markup/{drawer.tsx → Drawer.tsx} +58 -0
- package/src/components/drawer/markup/index.tsx +1 -1
- package/src/components/drawer/types/index.ts +24 -0
- package/src/components/input/markup/text/Base.tsx +32 -3
- package/src/components/input/markup/text/Identification.tsx +15 -2
- package/src/components/input/markup/text/Password.tsx +35 -2
- package/src/components/input/markup/text/Phone.tsx +38 -2
- package/src/components/input/markup/text/Search.tsx +30 -1
- package/src/components/input/styles/index.scss +6 -6
- package/src/components/input/types/index.ts +22 -1
- package/src/components/input/utils/index.ts +6 -0
- package/src/components/navigation/markup/mobile/BottomNavigation.tsx +11 -0
- package/src/components/navigation/types/index.ts +22 -0
- package/src/components/pagination/markup/Carousel.tsx +1 -0
- package/src/components/pagination/markup/Count.tsx +1 -0
- package/src/components/pagination/markup/Pagination.tsx +2 -0
- package/src/components/radio/markup/Radio.tsx +16 -2
- package/src/components/radio/markup/RadioCard.tsx +8 -0
- package/src/components/radio/markup/RadioCardGroup.tsx +8 -0
- package/src/components/radio/types/radio.ts +39 -0
- package/src/components/segmented-control/markup/SegmentedControl.tsx +12 -0
- package/src/components/segmented-control/types/index.ts +16 -0
- package/src/components/tab/markup/TabContent.tsx +5 -0
- package/src/components/tab/markup/TabList.tsx +19 -2
- package/src/components/tab/markup/TabRoot.tsx +50 -4
- package/src/components/tab/markup/TabTrigger.tsx +9 -1
- package/src/components/tab/styles/index.scss +28 -10
- package/src/components/tab/types/index.ts +10 -0
- package/src/components/tab/utils/tab-context.ts +8 -2
- package/src/components/button/markup/Button.tsx +0 -175
- package/src/components/button/markup/index.tsx +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniai-fe/uds-primitives",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "UNIAI Design System; Primitives Components Package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"publishConfig": {
|
|
15
15
|
"access": "public"
|
|
16
16
|
},
|
|
17
|
-
"packageManager": "pnpm@10.
|
|
17
|
+
"packageManager": "pnpm@10.26.0",
|
|
18
18
|
"engines": {
|
|
19
19
|
"node": ">=24",
|
|
20
20
|
"pnpm": ">=10"
|
|
@@ -89,10 +89,10 @@
|
|
|
89
89
|
"@uniai-fe/uds-foundation": "workspace:*",
|
|
90
90
|
"@uniai-fe/tsconfig": "workspace:*",
|
|
91
91
|
"@uniai-fe/util-functions": "workspace:*",
|
|
92
|
-
"eslint": "^9.39.
|
|
92
|
+
"eslint": "^9.39.2",
|
|
93
93
|
"prettier": "^3.7.4",
|
|
94
94
|
"react-hook-form": "^7.68.0",
|
|
95
|
-
"sass": "^1.
|
|
95
|
+
"sass": "^1.97.0",
|
|
96
96
|
"typescript": "~5.9.3"
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -4,6 +4,16 @@ import { composeBadgeClassName } from "../utils";
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Badge 컴포넌트; size/style/intent 축을 data attribute로 노출한다.
|
|
7
|
+
* @component
|
|
8
|
+
* @param {BadgeProps} props
|
|
9
|
+
* @param {"xsmall" | "small"} [props.size="xsmall"] 토큰 size.
|
|
10
|
+
* @param {"fill" | "outlined" | "dot"} [props.style="fill"] 시각 스타일.
|
|
11
|
+
* @param {"primary" | "secondary" | "tertiary" | "gray" | "green" | "yellow" | "orange" | "red"} [props.intent="primary"]
|
|
12
|
+
* fill/outlined 스타일에서 사용되는 intent.
|
|
13
|
+
* @param {"primary" | "feedback"} [props.tone="primary"] dot 스타일에 사용되는 tone.
|
|
14
|
+
* @param {React.ReactNode} [props.children] 라벨 텍스트. dot 스타일에서 생략하면 점만 렌더.
|
|
15
|
+
* @param {string} [props.className] figure className.
|
|
16
|
+
* @param {React.CSSProperties} [props.inlineStyle] figure inline style.
|
|
7
17
|
*/
|
|
8
18
|
const Badge = forwardRef<HTMLElementTagNameMap["figure"], BadgeProps>(
|
|
9
19
|
(
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
--badge-fill-label-color: var(--color-primary-default, #1a6aff);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
.badge:where([data-style="fill"][data-intent="
|
|
111
|
+
.badge:where([data-style="fill"][data-intent="tertiary"]) {
|
|
112
112
|
--badge-fill-bg-color: var(--color-cool-gray-10, #18191b);
|
|
113
113
|
--badge-fill-label-color: var(--color-common-100, #ffffff);
|
|
114
114
|
}
|
|
@@ -149,7 +149,7 @@
|
|
|
149
149
|
--badge-outline-label-color: var(--color-primary-strong, #0050e5);
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
.badge:where([data-style="outlined"][data-intent="
|
|
152
|
+
.badge:where([data-style="outlined"][data-intent="tertiary"]) {
|
|
153
153
|
--badge-outline-border-color: var(--color-border-standard-heavy, #313235);
|
|
154
154
|
--badge-outline-label-color: var(--color-label-strong, #18191b);
|
|
155
155
|
}
|
|
@@ -3,4 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import "./index.scss";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import { ButtonDefault, ButtonText, ButtonRounded } from "./markup";
|
|
7
|
+
|
|
8
|
+
export { ButtonDefault, ButtonText, ButtonRounded };
|
|
9
|
+
|
|
10
|
+
export const Button = {
|
|
11
|
+
Default: ButtonDefault,
|
|
12
|
+
Text: ButtonText,
|
|
13
|
+
Rounded: ButtonRounded,
|
|
14
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
type ComponentPropsWithoutRef,
|
|
4
|
+
type ElementType,
|
|
5
|
+
type ForwardedRef,
|
|
6
|
+
} from "react";
|
|
7
|
+
import type { ButtonProps } from "../types";
|
|
8
|
+
import {
|
|
9
|
+
BUTTON_SCALE_CONFIG,
|
|
10
|
+
DEFAULT_BUTTON_PRIORITY,
|
|
11
|
+
DEFAULT_BUTTON_SCALE,
|
|
12
|
+
} from "../types";
|
|
13
|
+
import { composeButtonClassName } from "../utils";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* uds-foundation 토큰 위에서 block/layout/priority/slot API를 제공하는 기본 Button 컴포넌트.
|
|
17
|
+
* @component
|
|
18
|
+
* @param {ButtonProps} props
|
|
19
|
+
* @param {ElementType} [props.as="button"] 커스텀 요소(Renderer).
|
|
20
|
+
* @param {React.ReactNode} [props.children] 문자열이면 `.button-label` span으로 감싼다.
|
|
21
|
+
* @param {React.ReactNode} [props.icon] iconSlot 위치에 배치할 노드.
|
|
22
|
+
* @param {"none" | "left" | "right"} [props.iconSlot="none"] 아이콘 배치 위치.
|
|
23
|
+
* @param {React.ReactNode} [props.left] 라벨 왼쪽 커스텀 슬롯.
|
|
24
|
+
* @param {React.ReactNode} [props.right] 라벨 오른쪽 커스텀 슬롯.
|
|
25
|
+
* @param {
|
|
26
|
+
* "solid-xlarge" | "solid-large" | "solid-medium" | "solid-small" |
|
|
27
|
+
* "outlined-xlarge" | "outlined-large" | "outlined-medium" | "outlined-small"
|
|
28
|
+
* } [props.scale="solid-medium"] spacing/height/타이포 세트.
|
|
29
|
+
* @param {"primary" | "secondary" | "tertiary"} [props.priority="primary"] semantic color 세트.
|
|
30
|
+
* @param {"default" | "readonly" | "disabled"} [props.state="default"] 내부 state. disabled prop과 조합된다.
|
|
31
|
+
* @param {boolean} [props.block=false] true면 width 100%.
|
|
32
|
+
* @param {boolean} [props.loading=false] true면 readonly 처리 + aria-busy.
|
|
33
|
+
* @param {string} [props.className] 추가 className.
|
|
34
|
+
* @param {"button" | "submit" | "reset"} [props.type="button"] button 요소일 때 type.
|
|
35
|
+
* @param {boolean} [props.disabled] native disabled. state보다 우선한다.
|
|
36
|
+
* @param {"hover" | "pressed"} [props.data-user-action] Story 시각 상태 고정용 data attr.
|
|
37
|
+
*/
|
|
38
|
+
const ButtonDefault = forwardRef<HTMLElement, ButtonProps>(
|
|
39
|
+
(
|
|
40
|
+
{
|
|
41
|
+
as,
|
|
42
|
+
children,
|
|
43
|
+
icon,
|
|
44
|
+
left,
|
|
45
|
+
right,
|
|
46
|
+
iconSlot = "none",
|
|
47
|
+
scale = DEFAULT_BUTTON_SCALE,
|
|
48
|
+
priority = DEFAULT_BUTTON_PRIORITY,
|
|
49
|
+
state: stateProp = "default",
|
|
50
|
+
block = false,
|
|
51
|
+
loading = false,
|
|
52
|
+
className,
|
|
53
|
+
type: typeProp,
|
|
54
|
+
disabled: disabledProp,
|
|
55
|
+
"data-user-action": userAction,
|
|
56
|
+
...restProps
|
|
57
|
+
},
|
|
58
|
+
forwardedRef,
|
|
59
|
+
) => {
|
|
60
|
+
const Component = (as ?? "button") as ElementType;
|
|
61
|
+
const normalizedChildren =
|
|
62
|
+
children === null || children === undefined ? null : typeof children ===
|
|
63
|
+
"string" || typeof children === "number" ? (
|
|
64
|
+
<span className="button-label" data-slot="label">
|
|
65
|
+
{children}
|
|
66
|
+
</span>
|
|
67
|
+
) : (
|
|
68
|
+
children
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const scaleConfig =
|
|
72
|
+
BUTTON_SCALE_CONFIG[scale] ?? BUTTON_SCALE_CONFIG[DEFAULT_BUTTON_SCALE];
|
|
73
|
+
const derivedFill = scaleConfig.fill;
|
|
74
|
+
const derivedSize = scaleConfig.size;
|
|
75
|
+
const resolvedPriority = priority ?? DEFAULT_BUTTON_PRIORITY;
|
|
76
|
+
const resolvedState = disabledProp ? "disabled" : stateProp;
|
|
77
|
+
const effectiveState = loading ? "readonly" : resolvedState;
|
|
78
|
+
const isReadonly = effectiveState === "readonly";
|
|
79
|
+
const isDisabled = effectiveState === "disabled" || isReadonly;
|
|
80
|
+
const isButtonElement =
|
|
81
|
+
typeof Component === "string" ? Component === "button" : false;
|
|
82
|
+
const isIconOnly =
|
|
83
|
+
Boolean(icon) &&
|
|
84
|
+
iconSlot !== "none" &&
|
|
85
|
+
!normalizedChildren &&
|
|
86
|
+
!left &&
|
|
87
|
+
!right;
|
|
88
|
+
|
|
89
|
+
const combinedClassName = composeButtonClassName({
|
|
90
|
+
scale,
|
|
91
|
+
fill: derivedFill,
|
|
92
|
+
priority: resolvedPriority,
|
|
93
|
+
size: derivedSize,
|
|
94
|
+
block,
|
|
95
|
+
loading,
|
|
96
|
+
iconSlot,
|
|
97
|
+
iconOnly: isIconOnly,
|
|
98
|
+
state: effectiveState,
|
|
99
|
+
className,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const content = (
|
|
103
|
+
<>
|
|
104
|
+
{left ? (
|
|
105
|
+
<span className="button-left" data-slot="left">
|
|
106
|
+
{left}
|
|
107
|
+
</span>
|
|
108
|
+
) : null}
|
|
109
|
+
{iconSlot === "left" && icon ? (
|
|
110
|
+
<span className="button-icon" data-slot="left">
|
|
111
|
+
{icon}
|
|
112
|
+
</span>
|
|
113
|
+
) : null}
|
|
114
|
+
{normalizedChildren}
|
|
115
|
+
{iconSlot === "right" && icon ? (
|
|
116
|
+
<span className="button-icon" data-slot="right">
|
|
117
|
+
{icon}
|
|
118
|
+
</span>
|
|
119
|
+
) : null}
|
|
120
|
+
{right ? (
|
|
121
|
+
<span className="button-right" data-slot="right">
|
|
122
|
+
{right}
|
|
123
|
+
</span>
|
|
124
|
+
) : null}
|
|
125
|
+
</>
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
if (isButtonElement) {
|
|
129
|
+
const buttonProps = restProps as ComponentPropsWithoutRef<"button">;
|
|
130
|
+
return (
|
|
131
|
+
<Component
|
|
132
|
+
{...buttonProps}
|
|
133
|
+
ref={forwardedRef as ForwardedRef<HTMLButtonElement>}
|
|
134
|
+
className={combinedClassName}
|
|
135
|
+
type={typeProp ?? "button"}
|
|
136
|
+
disabled={isDisabled}
|
|
137
|
+
aria-busy={loading || undefined}
|
|
138
|
+
data-user-action={userAction}
|
|
139
|
+
>
|
|
140
|
+
{content}
|
|
141
|
+
</Component>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<Component
|
|
147
|
+
{...restProps}
|
|
148
|
+
ref={forwardedRef as ForwardedRef<HTMLElement>}
|
|
149
|
+
className={combinedClassName}
|
|
150
|
+
aria-busy={loading || undefined}
|
|
151
|
+
aria-disabled={isDisabled || undefined}
|
|
152
|
+
data-user-action={userAction}
|
|
153
|
+
>
|
|
154
|
+
{content}
|
|
155
|
+
</Component>
|
|
156
|
+
);
|
|
157
|
+
},
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
ButtonDefault.displayName = "ButtonDefault";
|
|
161
|
+
|
|
162
|
+
export { ButtonDefault };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import type { ButtonScale } from "../types";
|
|
4
|
+
import type { RoundButtonProps, RoundButtonSize } from "../types";
|
|
5
|
+
import { ButtonDefault } from "./ButtonDefault";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* RoundButton의 size를 Button scale로 맵핑한다.
|
|
9
|
+
*/
|
|
10
|
+
const ROUND_BUTTON_SCALE_MAP: Record<RoundButtonSize, ButtonScale> = {
|
|
11
|
+
small: "outlined-small",
|
|
12
|
+
medium: "outlined-medium",
|
|
13
|
+
large: "outlined-large",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 원형 버튼 템플릿. size만 받으면 적절한 scale을 선택하고 라운드 전용 className을 적용한다.
|
|
18
|
+
* @component
|
|
19
|
+
* @param {RoundButtonProps} props
|
|
20
|
+
* @param {"small" | "medium" | "large"} [props.size="medium"] size에 따라 scale을 매핑한다.
|
|
21
|
+
* @param {"primary" | "secondary" | "tertiary"} [props.priority="primary"] semantic color.
|
|
22
|
+
* @param {"none" | "left" | "right"} [props.iconSlot="none"] 아이콘 위치.
|
|
23
|
+
* @param {React.ReactNode} [props.icon] iconSlot 콘텐츠.
|
|
24
|
+
* @param {boolean} [props.block] width 확장 여부.
|
|
25
|
+
*/
|
|
26
|
+
const ButtonRounded = forwardRef<HTMLElement, RoundButtonProps>(
|
|
27
|
+
({ size = "medium", className, ...rest }, ref) => {
|
|
28
|
+
const resolvedScale =
|
|
29
|
+
ROUND_BUTTON_SCALE_MAP[size] ?? ROUND_BUTTON_SCALE_MAP.medium;
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<ButtonDefault
|
|
33
|
+
{...rest}
|
|
34
|
+
ref={ref}
|
|
35
|
+
scale={resolvedScale}
|
|
36
|
+
className={clsx(
|
|
37
|
+
"button-template-round",
|
|
38
|
+
`button-template-round-size-${size}`,
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
ButtonRounded.displayName = "ButtonRounded";
|
|
47
|
+
|
|
48
|
+
export { ButtonRounded };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { forwardRef } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import type { ButtonScale } from "../types";
|
|
4
|
+
import type { TextButtonProps, TextButtonSize } from "../types";
|
|
5
|
+
import { ButtonDefault } from "./ButtonDefault";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* TextButton size를 scale에 매핑해 spacing/타이포를 일관되게 맞춘다.
|
|
9
|
+
*/
|
|
10
|
+
const TEXT_BUTTON_SCALE_MAP: Record<TextButtonSize, ButtonScale> = {
|
|
11
|
+
small: "outlined-small",
|
|
12
|
+
medium: "outlined-medium",
|
|
13
|
+
large: "outlined-large",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 텍스트 링크형 버튼 템플릿. size/priority 조합만 노출하고 나머지는 ButtonDefault에서 처리한다.
|
|
18
|
+
* @component
|
|
19
|
+
* @param {TextButtonProps} props
|
|
20
|
+
* @param {"small" | "medium" | "large"} [props.size="medium"] size에 따른 scale 파생.
|
|
21
|
+
* @param {"secondary" | "tertiary"} [props.priority="secondary"] semantic color.
|
|
22
|
+
* @param {"none" | "left" | "right"} [props.iconSlot="none"] 아이콘 위치.
|
|
23
|
+
* @param {React.ReactNode} [props.icon] iconSlot 위치 콘텐츠.
|
|
24
|
+
* @param {boolean} [props.block] 텍스트 버튼도 block 확장 가능.
|
|
25
|
+
*/
|
|
26
|
+
const ButtonText = forwardRef<HTMLElement, TextButtonProps>(
|
|
27
|
+
({ size = "medium", priority = "secondary", className, ...rest }, ref) => {
|
|
28
|
+
const resolvedScale =
|
|
29
|
+
TEXT_BUTTON_SCALE_MAP[size] ?? TEXT_BUTTON_SCALE_MAP.medium;
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<ButtonDefault
|
|
33
|
+
{...rest}
|
|
34
|
+
ref={ref}
|
|
35
|
+
scale={resolvedScale}
|
|
36
|
+
priority={priority}
|
|
37
|
+
className={clsx(
|
|
38
|
+
"button-template-text",
|
|
39
|
+
`button-template-text-size-${size}`,
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
ButtonText.displayName = "ButtonText";
|
|
48
|
+
|
|
49
|
+
export { ButtonText };
|