sh-ui-cli 0.52.0 → 0.52.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/data/changelog/versions.json +25 -0
- package/data/registry/react/components/_smoke/vanilla-extract.test.ts +33 -0
- package/data/registry/react/components/input/styles.css.ts +6 -6
- package/data/registry/react/registry.json +35 -852
- package/package.json +2 -2
- package/src/api.d.ts +3 -4
- package/src/constants.js +9 -5
- package/src/create/plugins/pluginSchema.js +5 -3
- package/src/mcp.mjs +4 -3
- package/data/registry/react/components/accordion/index.vanilla-extract.tsx +0 -97
- package/data/registry/react/components/accordion/styles.css.ts +0 -131
- package/data/registry/react/components/avatar/index.vanilla-extract.tsx +0 -73
- package/data/registry/react/components/avatar/styles.css.ts +0 -68
- package/data/registry/react/components/badge/index.vanilla-extract.tsx +0 -40
- package/data/registry/react/components/badge/styles.css.ts +0 -71
- package/data/registry/react/components/breadcrumb/index.vanilla-extract.tsx +0 -152
- package/data/registry/react/components/breadcrumb/styles.css.ts +0 -95
- package/data/registry/react/components/calendar/index.vanilla-extract.tsx +0 -806
- package/data/registry/react/components/calendar/styles.css.ts +0 -250
- package/data/registry/react/components/carousel/index.vanilla-extract.tsx +0 -430
- package/data/registry/react/components/carousel/styles.css.ts +0 -169
- package/data/registry/react/components/checkbox/index.vanilla-extract.tsx +0 -96
- package/data/registry/react/components/checkbox/styles.css.ts +0 -74
- package/data/registry/react/components/code-editor/index.vanilla-extract.tsx +0 -230
- package/data/registry/react/components/code-editor/styles.css.ts +0 -97
- package/data/registry/react/components/code-panel/index.vanilla-extract.tsx +0 -191
- package/data/registry/react/components/code-panel/styles.css.ts +0 -151
- package/data/registry/react/components/color-picker/index.vanilla-extract.tsx +0 -467
- package/data/registry/react/components/color-picker/styles.css.ts +0 -169
- package/data/registry/react/components/combobox/index.vanilla-extract.tsx +0 -165
- package/data/registry/react/components/combobox/styles.css.ts +0 -174
- package/data/registry/react/components/context-menu/index.vanilla-extract.tsx +0 -251
- package/data/registry/react/components/context-menu/styles.css.ts +0 -167
- package/data/registry/react/components/date-picker/index.vanilla-extract.tsx +0 -520
- package/data/registry/react/components/date-picker/styles.css.ts +0 -111
- package/data/registry/react/components/dialog/index.vanilla-extract.tsx +0 -95
- package/data/registry/react/components/dialog/styles.css.ts +0 -140
- package/data/registry/react/components/dropdown-menu/index.vanilla-extract.tsx +0 -255
- package/data/registry/react/components/dropdown-menu/styles.css.ts +0 -175
- package/data/registry/react/components/file-upload/index.vanilla-extract.tsx +0 -487
- package/data/registry/react/components/file-upload/styles.css.ts +0 -193
- package/data/registry/react/components/form/index.vanilla-extract.tsx +0 -61
- package/data/registry/react/components/form/styles.css.ts +0 -56
- package/data/registry/react/components/header/index.vanilla-extract.tsx +0 -805
- package/data/registry/react/components/header/styles.css.ts +0 -413
- package/data/registry/react/components/label/index.vanilla-extract.tsx +0 -52
- package/data/registry/react/components/label/styles.css.ts +0 -141
- package/data/registry/react/components/markdown-editor/index.vanilla-extract.tsx +0 -119
- package/data/registry/react/components/markdown-editor/styles.css.ts +0 -231
- package/data/registry/react/components/menubar/index.vanilla-extract.tsx +0 -32
- package/data/registry/react/components/menubar/styles.css.ts +0 -53
- package/data/registry/react/components/numeric-input/index.vanilla-extract.tsx +0 -148
- package/data/registry/react/components/numeric-input/styles.css.ts +0 -65
- package/data/registry/react/components/page-toc/index.vanilla-extract.tsx +0 -174
- package/data/registry/react/components/page-toc/styles.css.ts +0 -97
- package/data/registry/react/components/pagination/index.vanilla-extract.tsx +0 -269
- package/data/registry/react/components/pagination/styles.css.ts +0 -113
- package/data/registry/react/components/popover/index.vanilla-extract.tsx +0 -113
- package/data/registry/react/components/popover/styles.css.ts +0 -78
- package/data/registry/react/components/progress/index.vanilla-extract.tsx +0 -54
- package/data/registry/react/components/progress/styles.css.ts +0 -53
- package/data/registry/react/components/radio/index.vanilla-extract.tsx +0 -65
- package/data/registry/react/components/radio/styles.css.ts +0 -79
- package/data/registry/react/components/rich-text-editor/index.vanilla-extract.tsx +0 -348
- package/data/registry/react/components/rich-text-editor/styles.css.ts +0 -243
- package/data/registry/react/components/select/index.vanilla-extract.tsx +0 -234
- package/data/registry/react/components/select/styles.css.ts +0 -225
- package/data/registry/react/components/separator/index.vanilla-extract.tsx +0 -46
- package/data/registry/react/components/separator/styles.css.ts +0 -24
- package/data/registry/react/components/sidebar/index.vanilla-extract.tsx +0 -1067
- package/data/registry/react/components/sidebar/styles.css.ts +0 -578
- package/data/registry/react/components/skeleton/index.vanilla-extract.tsx +0 -22
- package/data/registry/react/components/skeleton/styles.css.ts +0 -30
- package/data/registry/react/components/slider/index.vanilla-extract.tsx +0 -298
- package/data/registry/react/components/slider/styles.css.ts +0 -75
- package/data/registry/react/components/spinner/index.vanilla-extract.tsx +0 -38
- package/data/registry/react/components/spinner/styles.css.ts +0 -60
- package/data/registry/react/components/switch/index.vanilla-extract.tsx +0 -39
- package/data/registry/react/components/switch/styles.css.ts +0 -87
- package/data/registry/react/components/tabs/index.vanilla-extract.tsx +0 -91
- package/data/registry/react/components/tabs/styles.css.ts +0 -145
- package/data/registry/react/components/textarea/index.vanilla-extract.tsx +0 -23
- package/data/registry/react/components/textarea/styles.css.ts +0 -55
- package/data/registry/react/components/toast/index.vanilla-extract.tsx +0 -258
- package/data/registry/react/components/toast/styles.css.ts +0 -307
- package/data/registry/react/components/toggle/index.vanilla-extract.tsx +0 -131
- package/data/registry/react/components/toggle/styles.css.ts +0 -109
- package/data/registry/react/components/tooltip/index.vanilla-extract.tsx +0 -83
- package/data/registry/react/components/tooltip/styles.css.ts +0 -59
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sh-ui-cli",
|
|
3
|
-
"version": "0.52.
|
|
3
|
+
"version": "0.52.2",
|
|
4
4
|
"description": "sh-ui CLI — 프로젝트 스캐폴드(create) + 컴포넌트 추가(add/list/remove) + IDE-내 AI용 MCP 서버",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@inquirer/prompts": "^7.0.0",
|
|
29
29
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
30
30
|
"fs-extra": "^11.2.0",
|
|
31
|
-
"zod": "^3.
|
|
31
|
+
"zod": "^4.3.6"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"vitest": "^3.2.4"
|
package/src/api.d.ts
CHANGED
|
@@ -13,11 +13,10 @@ export type ThemeMode = 'light-dark' | 'light' | 'dark';
|
|
|
13
13
|
/** 현재 실제로 동작하는 CSS 프레임워크.
|
|
14
14
|
* - plain: 모든 컴포넌트가 plain 변종 보유.
|
|
15
15
|
* - tailwind: 모든 styled 컴포넌트가 utility-class 변종 보유.
|
|
16
|
-
* - css-modules: 모든 styled 컴포넌트가 .module.css 변종 보유.
|
|
17
|
-
|
|
18
|
-
export type CssFrameworkSupported = 'plain' | 'tailwind' | 'css-modules' | 'vanilla-extract';
|
|
16
|
+
* - css-modules: 모든 styled 컴포넌트가 .module.css 변종 보유. */
|
|
17
|
+
export type CssFrameworkSupported = 'plain' | 'tailwind' | 'css-modules';
|
|
19
18
|
/** 향후 추가 예정 — UI 에서 "곧 지원" 으로 노출되지만 CLI 는 거부. */
|
|
20
|
-
export type CssFrameworkPlanned =
|
|
19
|
+
export type CssFrameworkPlanned = 'vanilla-extract';
|
|
21
20
|
/** 알려진 전체 (validation 메시지용). */
|
|
22
21
|
export type CssFramework = CssFrameworkSupported | CssFrameworkPlanned;
|
|
23
22
|
|
package/src/constants.js
CHANGED
|
@@ -27,11 +27,15 @@ export const THEME_MODES = ['light-dark', 'light', 'dark'];
|
|
|
27
27
|
// - plain: CSS custom properties + 일반 .css 파일 (모든 컴포넌트 변종 보유)
|
|
28
28
|
// - tailwind: utility class TSX 변종 (모든 styled 컴포넌트 변종 보유)
|
|
29
29
|
// - css-modules: 모듈 단위 .module.css + styles.X 참조 (모든 styled 컴포넌트 변종 보유)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
|
|
30
|
+
export const CSS_FRAMEWORKS_SUPPORTED = ['plain', 'tailwind', 'css-modules'];
|
|
31
|
+
|
|
32
|
+
// 향후 추가 예정. 사용자가 이 값을 주면 친절 에러로 안내.
|
|
33
|
+
// vanilla-extract: button/card/input 3 개 파일럿만 변종 보유 (수동 작성, 검증됨).
|
|
34
|
+
// v0.50.0 에서 자동 변환 스크립트로 40 개 추가 시도했으나 vanilla-extract 의 strict
|
|
35
|
+
// selector 규칙 (selectors 안에 다른 클래스 reference 불가, third-party 클래스 descendant 불가
|
|
36
|
+
// 등) 을 어김 — v0.52.2 에서 broken 변종 rollback. 사용자가 직접 .css.ts 를 작성하거나
|
|
37
|
+
// 향후 정식 rollout (selectors → globalStyle 분기까지 처리) 후 SUPPORTED 로 다시 승격 예정.
|
|
38
|
+
export const CSS_FRAMEWORKS_PLANNED = ['vanilla-extract'];
|
|
35
39
|
|
|
36
40
|
// 알려진 전체 — 검증 시 supported 와 planned 둘 다 인지하기 위함.
|
|
37
41
|
export const CSS_FRAMEWORKS_ALL = [
|
|
@@ -23,10 +23,12 @@ const filePath = z
|
|
|
23
23
|
"Use 'middleware.ts' instead of 'src/middleware.ts'.",
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
// zod 4 에서 z.function 의 .args/.returns 체이닝 API 가 제거됨.
|
|
27
|
+
// 함수 시그니처는 런타임에 의미 있게 검증할 수 없으므로 custom 으로 type 만 확인.
|
|
26
28
|
const wrapperFn = z
|
|
27
|
-
.
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
.custom((val) => typeof val === "function", {
|
|
30
|
+
message: "wrapExport must be a function (expr: string) => string",
|
|
31
|
+
})
|
|
30
32
|
.optional();
|
|
31
33
|
|
|
32
34
|
export const PluginSchema = z.object({
|
package/src/mcp.mjs
CHANGED
|
@@ -74,7 +74,8 @@ const INIT_DESCRIPTIONS = {
|
|
|
74
74
|
},
|
|
75
75
|
cssFramework: {
|
|
76
76
|
plain: "플레인 CSS — CSS custom properties + 일반 .css 파일 (모든 컴포넌트 지원)",
|
|
77
|
-
tailwind: "Tailwind v4 utility class — class-variance-authority
|
|
77
|
+
tailwind: "Tailwind v4 utility class — class-variance-authority 기반",
|
|
78
|
+
"css-modules": "CSS Modules — .module.css + styles.X 참조, 빌드 타임 hash 격리",
|
|
78
79
|
},
|
|
79
80
|
};
|
|
80
81
|
|
|
@@ -205,7 +206,7 @@ export async function startMcpServer() {
|
|
|
205
206
|
theme: z.string().optional()
|
|
206
207
|
.describe(`프리셋 이름 (${THEME_PRESETS_LIST}) 또는 playground 에서 생성한 base64 (선택)`),
|
|
207
208
|
cssFramework: z.enum(CSS_FRAMEWORKS).optional()
|
|
208
|
-
.describe(`CSS 프레임워크. 기본 plain. 현재 ${CSS_FRAMEWORKS.join('/')} 지원
|
|
209
|
+
.describe(`CSS 프레임워크. 기본 plain. 현재 ${CSS_FRAMEWORKS.join('/')} 지원 — 변종 미보유 컴포넌트는 add 시 plain 으로 자동 fallback`),
|
|
209
210
|
cwd: z.string().optional()
|
|
210
211
|
.describe("부모 디렉토리. 기본 process.cwd()"),
|
|
211
212
|
force: z.boolean().optional()
|
|
@@ -276,7 +277,7 @@ export async function startMcpServer() {
|
|
|
276
277
|
mode: z.enum(MODES).optional()
|
|
277
278
|
.describe("색 모드. 기본 light-dark"),
|
|
278
279
|
cssFramework: z.enum(CSS_FRAMEWORKS).optional()
|
|
279
|
-
.describe(`CSS 프레임워크. 기본 plain. 현재 ${CSS_FRAMEWORKS.join('/')} 지원
|
|
280
|
+
.describe(`CSS 프레임워크. 기본 plain. 현재 ${CSS_FRAMEWORKS.join('/')} 지원 — 변종 미보유 컴포넌트는 add 시 plain 으로 자동 fallback`),
|
|
280
281
|
cwd: z.string().optional()
|
|
281
282
|
.describe("작업 디렉토리. 기본 process.cwd()"),
|
|
282
283
|
force: z.boolean().optional()
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { Accordion as BaseAccordion } from "@base-ui/react/accordion";
|
|
3
|
-
import { byKey, accordion, accordion__item, accordion__header, accordion__trigger, accordionTriggerLabel, accordion__chevron, accordion__panel, accordion__content } from "./styles.css";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import { cn } from "@SH_UI_UTILS@";
|
|
7
|
-
type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
|
|
8
|
-
|
|
9
|
-
export type AccordionSize = "sm" | "md";
|
|
10
|
-
|
|
11
|
-
type AccordionProps = WithStringClassName<
|
|
12
|
-
React.ComponentPropsWithoutRef<typeof BaseAccordion.Root>
|
|
13
|
-
> & {
|
|
14
|
-
/**
|
|
15
|
-
* 트리거 + chevron + content 의 패딩·폰트 크기 묶음.
|
|
16
|
-
* - `md` (기본) — padding 16/4, font 15px, chevron 16px
|
|
17
|
-
* - `sm` — padding 8/4, font 12px, chevron 12px. 좁은 사이드바·다중 섹션에 적합.
|
|
18
|
-
* @default "md"
|
|
19
|
-
*/
|
|
20
|
-
size?: AccordionSize;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const Accordion = React.forwardRef<HTMLDivElement, AccordionProps>(
|
|
24
|
-
({ className, size = "md", ...props }, ref) => (
|
|
25
|
-
<BaseAccordion.Root
|
|
26
|
-
ref={ref}
|
|
27
|
-
className={cn(accordion, className)}
|
|
28
|
-
data-size={size}
|
|
29
|
-
{...props}
|
|
30
|
-
/>
|
|
31
|
-
),
|
|
32
|
-
);
|
|
33
|
-
Accordion.displayName = "Accordion";
|
|
34
|
-
|
|
35
|
-
export const AccordionItem = React.forwardRef<
|
|
36
|
-
HTMLDivElement,
|
|
37
|
-
WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseAccordion.Item>>
|
|
38
|
-
>(({ className, ...props }, ref) => (
|
|
39
|
-
<BaseAccordion.Item
|
|
40
|
-
ref={ref}
|
|
41
|
-
className={cn(accordion__item, className)}
|
|
42
|
-
{...props}
|
|
43
|
-
/>
|
|
44
|
-
));
|
|
45
|
-
AccordionItem.displayName = "AccordionItem";
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Trigger: 헤더 버튼. 우측에 chevron이 자동으로 붙고 expanded 상태에서 회전한다.
|
|
49
|
-
* Base UI의 AccordionHeader(h3)로 감싸 의미론적 헤더 구조를 유지한다.
|
|
50
|
-
*/
|
|
51
|
-
export const AccordionTrigger = React.forwardRef<
|
|
52
|
-
HTMLButtonElement,
|
|
53
|
-
WithStringClassName<
|
|
54
|
-
React.ComponentPropsWithoutRef<typeof BaseAccordion.Trigger>
|
|
55
|
-
>
|
|
56
|
-
>(({ className, children, ...props }, ref) => (
|
|
57
|
-
<BaseAccordion.Header className={accordion__header}>
|
|
58
|
-
<BaseAccordion.Trigger
|
|
59
|
-
ref={ref}
|
|
60
|
-
className={cn(accordion__trigger, className)}
|
|
61
|
-
{...props}
|
|
62
|
-
>
|
|
63
|
-
<span className={accordionTriggerLabel}>{children}</span>
|
|
64
|
-
<svg
|
|
65
|
-
className={accordion__chevron}
|
|
66
|
-
width="16"
|
|
67
|
-
height="16"
|
|
68
|
-
viewBox="0 0 16 16"
|
|
69
|
-
fill="none"
|
|
70
|
-
aria-hidden="true"
|
|
71
|
-
>
|
|
72
|
-
<path
|
|
73
|
-
d="M4 6l4 4 4-4"
|
|
74
|
-
stroke="currentColor"
|
|
75
|
-
strokeWidth="1.5"
|
|
76
|
-
strokeLinecap="round"
|
|
77
|
-
strokeLinejoin="round"
|
|
78
|
-
/>
|
|
79
|
-
</svg>
|
|
80
|
-
</BaseAccordion.Trigger>
|
|
81
|
-
</BaseAccordion.Header>
|
|
82
|
-
));
|
|
83
|
-
AccordionTrigger.displayName = "AccordionTrigger";
|
|
84
|
-
|
|
85
|
-
export const AccordionContent = React.forwardRef<
|
|
86
|
-
HTMLDivElement,
|
|
87
|
-
WithStringClassName<React.ComponentPropsWithoutRef<typeof BaseAccordion.Panel>>
|
|
88
|
-
>(({ className, children, ...props }, ref) => (
|
|
89
|
-
<BaseAccordion.Panel
|
|
90
|
-
ref={ref}
|
|
91
|
-
className={cn(accordion__panel, className)}
|
|
92
|
-
{...props}
|
|
93
|
-
>
|
|
94
|
-
<div className={accordion__content}>{children}</div>
|
|
95
|
-
</BaseAccordion.Panel>
|
|
96
|
-
));
|
|
97
|
-
AccordionContent.displayName = "AccordionContent";
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { style } from "@vanilla-extract/css";
|
|
2
|
-
|
|
3
|
-
export const accordion = style({
|
|
4
|
-
display: "flex",
|
|
5
|
-
flexDirection: "column",
|
|
6
|
-
width: "100%",
|
|
7
|
-
selectors: {
|
|
8
|
-
[`&[data-size="sm"] ${accordion__trigger}`]: {
|
|
9
|
-
padding: "var(--space-2) var(--space-1)",
|
|
10
|
-
fontSize: "var(--text-xs)",
|
|
11
|
-
lineHeight: 1.2,
|
|
12
|
-
},
|
|
13
|
-
[`&[data-size="sm"] ${accordion__chevron}`]: {
|
|
14
|
-
width: "12px",
|
|
15
|
-
height: "12px",
|
|
16
|
-
},
|
|
17
|
-
[`&[data-size="sm"] ${accordion__content}`]: {
|
|
18
|
-
padding: "0 var(--space-1) var(--space-2)",
|
|
19
|
-
fontSize: "var(--text-xs)",
|
|
20
|
-
lineHeight: 1.5,
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
export const accordion__item = style({
|
|
26
|
-
borderBottom: "1px solid var(--border)",
|
|
27
|
-
selectors: {
|
|
28
|
-
"&:first-child": {
|
|
29
|
-
borderTop: "1px solid var(--border)",
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
export const accordion__header = style({
|
|
35
|
-
margin: 0,
|
|
36
|
-
font: "inherit",
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
export const accordion__trigger = style({
|
|
40
|
-
display: "flex",
|
|
41
|
-
alignItems: "center",
|
|
42
|
-
justifyContent: "space-between",
|
|
43
|
-
gap: "var(--space-4)",
|
|
44
|
-
width: "100%",
|
|
45
|
-
padding: "var(--space-4) var(--space-1)",
|
|
46
|
-
background: "transparent",
|
|
47
|
-
border: "none",
|
|
48
|
-
color: "var(--foreground)",
|
|
49
|
-
fontSize: "0.9375rem",
|
|
50
|
-
fontWeight: "var(--weight-medium)",
|
|
51
|
-
lineHeight: 1.4,
|
|
52
|
-
textAlign: "left",
|
|
53
|
-
cursor: "pointer",
|
|
54
|
-
transition: "background-color var(--duration-fast) var(--ease-standard)",
|
|
55
|
-
WebkitTapHighlightColor: "transparent",
|
|
56
|
-
selectors: {
|
|
57
|
-
"&:not([disabled]):not([data-disabled]):hover": {
|
|
58
|
-
background: "var(--background-muted)",
|
|
59
|
-
},
|
|
60
|
-
"&:focus-visible": {
|
|
61
|
-
outline: "var(--border-width-strong) solid var(--foreground)",
|
|
62
|
-
outlineOffset: "2px",
|
|
63
|
-
borderRadius: "calc(var(--radius) - 2px)",
|
|
64
|
-
},
|
|
65
|
-
"&[disabled]": {
|
|
66
|
-
cursor: "not-allowed",
|
|
67
|
-
color: "var(--foreground-muted)",
|
|
68
|
-
},
|
|
69
|
-
"&[data-disabled]": {
|
|
70
|
-
cursor: "not-allowed",
|
|
71
|
-
color: "var(--foreground-muted)",
|
|
72
|
-
},
|
|
73
|
-
[`&[data-panel-open] ${accordion__chevron}`]: {
|
|
74
|
-
transform: "rotate(180deg)",
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
export const accordionTriggerLabel = style({
|
|
80
|
-
minWidth: 0,
|
|
81
|
-
overflowWrap: "anywhere",
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
export const accordion__chevron = style({
|
|
85
|
-
flexShrink: 0,
|
|
86
|
-
color: "var(--foreground-muted)",
|
|
87
|
-
transition: "transform 180ms var(--ease-standard)",
|
|
88
|
-
"@media": {
|
|
89
|
-
"(prefers-reduced-motion: reduce)": {
|
|
90
|
-
transition: "none",
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
export const accordion__panel = style({
|
|
96
|
-
overflow: "hidden",
|
|
97
|
-
height: "var(--accordion-panel-height)",
|
|
98
|
-
transition: "height var(--duration-slow) var(--ease-standard)",
|
|
99
|
-
selectors: {
|
|
100
|
-
"&[data-starting-style]": {
|
|
101
|
-
height: 0,
|
|
102
|
-
},
|
|
103
|
-
"&[data-ending-style]": {
|
|
104
|
-
height: 0,
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
"@media": {
|
|
108
|
-
"(prefers-reduced-motion: reduce)": {
|
|
109
|
-
transition: "none",
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
export const accordion__content = style({
|
|
115
|
-
padding: "0 var(--space-1) var(--space-4)",
|
|
116
|
-
fontSize: "var(--text-sm)",
|
|
117
|
-
lineHeight: 1.6,
|
|
118
|
-
color: "var(--foreground-muted)",
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
/** 동적 키로 클래스 참조용 — `byKey[\`badge--${variant}\`]` 같은 패턴 지원. */
|
|
122
|
-
export const byKey: Record<string, string> = {
|
|
123
|
-
"accordion": accordion,
|
|
124
|
-
"accordion__item": accordion__item,
|
|
125
|
-
"accordion__header": accordion__header,
|
|
126
|
-
"accordion__trigger": accordion__trigger,
|
|
127
|
-
"accordion__trigger-label": accordionTriggerLabel,
|
|
128
|
-
"accordion__chevron": accordion__chevron,
|
|
129
|
-
"accordion__panel": accordion__panel,
|
|
130
|
-
"accordion__content": accordion__content,
|
|
131
|
-
};
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { Avatar as BaseAvatar } from "@base-ui/react/avatar";
|
|
3
|
-
import { byKey, avatar, avatarSm, avatarMd, avatarLg, avatarXl, avatar__image, avatar__fallback } from "./styles.css";
|
|
4
|
-
|
|
5
|
-
import { cn } from "@SH_UI_UTILS@";
|
|
6
|
-
type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export type AvatarSize = "sm" | "md" | "lg" | "xl";
|
|
10
|
-
|
|
11
|
-
export interface AvatarProps
|
|
12
|
-
extends WithStringClassName<
|
|
13
|
-
React.ComponentPropsWithoutRef<typeof BaseAvatar.Root>
|
|
14
|
-
> {
|
|
15
|
-
/**
|
|
16
|
-
* 크기.
|
|
17
|
-
* - `sm` (24px) — 댓글·리스트 행
|
|
18
|
-
* - `md` (32px) — 일반 (기본)
|
|
19
|
-
* - `lg` (40px) — 헤더·프로필 카드
|
|
20
|
-
* - `xl` (56px) — 프로필 페이지
|
|
21
|
-
*
|
|
22
|
-
* @default "md"
|
|
23
|
-
*/
|
|
24
|
-
size?: AvatarSize;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* 사용자/엔티티를 대표하는 원형 이미지. `Avatar` 안에 `AvatarImage`와
|
|
29
|
-
* `AvatarFallback`을 함께 둬, 이미지 로드 실패 시 자동으로 fallback이 표시되도록 한다.
|
|
30
|
-
*/
|
|
31
|
-
export const Avatar = React.forwardRef<HTMLSpanElement, AvatarProps>(
|
|
32
|
-
function Avatar({ className, size = "md", ...props }, ref) {
|
|
33
|
-
return (
|
|
34
|
-
<BaseAvatar.Root
|
|
35
|
-
ref={ref}
|
|
36
|
-
className={cn(avatar, byKey[`avatar--${size}`], className)}
|
|
37
|
-
{...props}
|
|
38
|
-
/>
|
|
39
|
-
);
|
|
40
|
-
},
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
/** Avatar 내부의 실제 이미지. 로드 실패 시 자동으로 가려지고 fallback이 노출된다. */
|
|
44
|
-
export const AvatarImage = React.forwardRef<
|
|
45
|
-
HTMLImageElement,
|
|
46
|
-
WithStringClassName<
|
|
47
|
-
React.ComponentPropsWithoutRef<typeof BaseAvatar.Image>
|
|
48
|
-
>
|
|
49
|
-
>(function AvatarImage({ className, ...props }, ref) {
|
|
50
|
-
return (
|
|
51
|
-
<BaseAvatar.Image
|
|
52
|
-
ref={ref}
|
|
53
|
-
className={cn(avatar__image, className)}
|
|
54
|
-
{...props}
|
|
55
|
-
/>
|
|
56
|
-
);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
/** 이미지가 로드되지 않을 때 표시되는 대체 콘텐츠. 이니셜이나 아이콘을 권장. */
|
|
60
|
-
export const AvatarFallback = React.forwardRef<
|
|
61
|
-
HTMLSpanElement,
|
|
62
|
-
WithStringClassName<
|
|
63
|
-
React.ComponentPropsWithoutRef<typeof BaseAvatar.Fallback>
|
|
64
|
-
>
|
|
65
|
-
>(function AvatarFallback({ className, ...props }, ref) {
|
|
66
|
-
return (
|
|
67
|
-
<BaseAvatar.Fallback
|
|
68
|
-
ref={ref}
|
|
69
|
-
className={cn(avatar__fallback, className)}
|
|
70
|
-
{...props}
|
|
71
|
-
/>
|
|
72
|
-
);
|
|
73
|
-
});
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { style } from "@vanilla-extract/css";
|
|
2
|
-
|
|
3
|
-
export const avatar = style({
|
|
4
|
-
position: "relative",
|
|
5
|
-
display: "inline-flex",
|
|
6
|
-
alignItems: "center",
|
|
7
|
-
justifyContent: "center",
|
|
8
|
-
flexShrink: 0,
|
|
9
|
-
verticalAlign: "middle",
|
|
10
|
-
overflow: "hidden",
|
|
11
|
-
borderRadius: "999px",
|
|
12
|
-
background: "var(--background-muted)",
|
|
13
|
-
color: "var(--foreground-muted)",
|
|
14
|
-
fontWeight: "var(--weight-medium)",
|
|
15
|
-
userSelect: "none",
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
export const avatarSm = style({
|
|
19
|
-
width: "1.75rem",
|
|
20
|
-
height: "1.75rem",
|
|
21
|
-
fontSize: "var(--text-xs)",
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
export const avatarMd = style({
|
|
25
|
-
width: "2.5rem",
|
|
26
|
-
height: "2.5rem",
|
|
27
|
-
fontSize: "0.8125rem",
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
export const avatarLg = style({
|
|
31
|
-
width: "3rem",
|
|
32
|
-
height: "3rem",
|
|
33
|
-
fontSize: "var(--text-sm)",
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
export const avatarXl = style({
|
|
37
|
-
width: "4rem",
|
|
38
|
-
height: "4rem",
|
|
39
|
-
fontSize: "var(--text-base)",
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
export const avatar__image = style({
|
|
43
|
-
width: "100%",
|
|
44
|
-
height: "100%",
|
|
45
|
-
objectFit: "cover",
|
|
46
|
-
display: "block",
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
export const avatar__fallback = style({
|
|
50
|
-
display: "inline-flex",
|
|
51
|
-
alignItems: "center",
|
|
52
|
-
justifyContent: "center",
|
|
53
|
-
width: "100%",
|
|
54
|
-
height: "100%",
|
|
55
|
-
textTransform: "uppercase",
|
|
56
|
-
letterSpacing: "0.02em",
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
/** 동적 키로 클래스 참조용 — `byKey[\`badge--${variant}\`]` 같은 패턴 지원. */
|
|
60
|
-
export const byKey: Record<string, string> = {
|
|
61
|
-
"avatar": avatar,
|
|
62
|
-
"avatar--sm": avatarSm,
|
|
63
|
-
"avatar--md": avatarMd,
|
|
64
|
-
"avatar--lg": avatarLg,
|
|
65
|
-
"avatar--xl": avatarXl,
|
|
66
|
-
"avatar__image": avatar__image,
|
|
67
|
-
"avatar__fallback": avatar__fallback,
|
|
68
|
-
};
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { byKey, badge, badgeSm, badgeMd, badgePrimary, badgeSecondary, badgeSuccess, badgeWarning, badgeDanger, badgeOutline } from "./styles.css";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { cn } from "@SH_UI_UTILS@";
|
|
6
|
-
export type BadgeVariant =
|
|
7
|
-
| "primary"
|
|
8
|
-
| "secondary"
|
|
9
|
-
| "success"
|
|
10
|
-
| "warning"
|
|
11
|
-
| "danger"
|
|
12
|
-
| "outline";
|
|
13
|
-
|
|
14
|
-
export type BadgeSize = "sm" | "md";
|
|
15
|
-
|
|
16
|
-
export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
17
|
-
variant?: BadgeVariant;
|
|
18
|
-
size?: BadgeSize;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* 상태·카테고리·수량 등을 짧게 표기하는 인라인 라벨. 의미 전달이 색에만
|
|
23
|
-
* 의존하지 않도록 텍스트나 아이콘과 함께 사용할 것.
|
|
24
|
-
*/
|
|
25
|
-
export const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
|
|
26
|
-
function Badge({ className, variant = "primary", size = "md", ...props }, ref) {
|
|
27
|
-
return (
|
|
28
|
-
<span
|
|
29
|
-
ref={ref}
|
|
30
|
-
className={cn(
|
|
31
|
-
badge,
|
|
32
|
-
byKey[`badge--${variant}`],
|
|
33
|
-
byKey[`badge--${size}`],
|
|
34
|
-
className,
|
|
35
|
-
)}
|
|
36
|
-
{...props}
|
|
37
|
-
/>
|
|
38
|
-
);
|
|
39
|
-
},
|
|
40
|
-
);
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { style } from "@vanilla-extract/css";
|
|
2
|
-
|
|
3
|
-
export const badge = style({
|
|
4
|
-
display: "inline-flex",
|
|
5
|
-
alignItems: "center",
|
|
6
|
-
gap: "0.25rem",
|
|
7
|
-
padding: "0 0.5rem",
|
|
8
|
-
border: "1px solid transparent",
|
|
9
|
-
borderRadius: "999px",
|
|
10
|
-
fontWeight: "var(--weight-medium)",
|
|
11
|
-
lineHeight: 1,
|
|
12
|
-
whiteSpace: "nowrap",
|
|
13
|
-
verticalAlign: "middle",
|
|
14
|
-
userSelect: "none",
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
export const badgeSm = style({
|
|
18
|
-
height: "1.25rem",
|
|
19
|
-
fontSize: "0.6875rem",
|
|
20
|
-
padding: "0 0.375rem",
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
export const badgeMd = style({
|
|
24
|
-
height: "1.5rem",
|
|
25
|
-
fontSize: "var(--text-xs)",
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
export const badgePrimary = style({
|
|
29
|
-
background: "var(--primary)",
|
|
30
|
-
color: "var(--primary-foreground)",
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
export const badgeSecondary = style({
|
|
34
|
-
background: "var(--background-muted)",
|
|
35
|
-
color: "var(--foreground)",
|
|
36
|
-
borderColor: "var(--border)",
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
export const badgeSuccess = style({
|
|
40
|
-
background: "var(--success, #16a34a)",
|
|
41
|
-
color: "#fff",
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
export const badgeWarning = style({
|
|
45
|
-
background: "var(--warning, #d97706)",
|
|
46
|
-
color: "#fff",
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
export const badgeDanger = style({
|
|
50
|
-
background: "var(--danger)",
|
|
51
|
-
color: "var(--danger-foreground, #fff)",
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
export const badgeOutline = style({
|
|
55
|
-
background: "transparent",
|
|
56
|
-
color: "var(--foreground)",
|
|
57
|
-
borderColor: "var(--border-strong)",
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
/** 동적 키로 클래스 참조용 — `byKey[\`badge--${variant}\`]` 같은 패턴 지원. */
|
|
61
|
-
export const byKey: Record<string, string> = {
|
|
62
|
-
"badge": badge,
|
|
63
|
-
"badge--sm": badgeSm,
|
|
64
|
-
"badge--md": badgeMd,
|
|
65
|
-
"badge--primary": badgePrimary,
|
|
66
|
-
"badge--secondary": badgeSecondary,
|
|
67
|
-
"badge--success": badgeSuccess,
|
|
68
|
-
"badge--warning": badgeWarning,
|
|
69
|
-
"badge--danger": badgeDanger,
|
|
70
|
-
"badge--outline": badgeOutline,
|
|
71
|
-
};
|