sh-ui-cli 0.38.0 → 0.40.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/data/changelog/versions.json +36 -0
- package/data/registry/react/components/accordion/index.tsx +24 -10
- package/data/registry/react/components/accordion/styles.css +20 -3
- package/data/registry/react/components/numeric-input/index.tsx +150 -0
- package/data/registry/react/components/numeric-input/styles.css +56 -0
- package/data/registry/react/registry.json +16 -0
- package/data/summaries/react.json +2 -1
- package/package.json +1 -1
- package/src/api.d.ts +7 -0
- package/src/create/theme/presets.js +18 -5
|
@@ -2,6 +2,42 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$description": "sh-ui 릴리즈 노트 단일 소스. docs(React)와 showcase(Flutter)가 함께 읽는다. 새 릴리즈마다 맨 앞에 추가.",
|
|
4
4
|
"versions": [
|
|
5
|
+
{
|
|
6
|
+
"version": "0.40.0",
|
|
7
|
+
"date": "2026-04-29",
|
|
8
|
+
"title": "NumericInput 컴포넌트 + 토큰 편집기 UX 대폭 개선 (Accordion size, ShadowBuilder, XY 패드)",
|
|
9
|
+
"type": "minor",
|
|
10
|
+
"highlights": [
|
|
11
|
+
"**NumericInput** — 정식 sh-ui 컴포넌트 신설. 슬라이더 동반·토큰 편집기 같은 컴팩트 컨텍스트용 숫자 입력. type=text + inputMode=decimal + buffer state — Chrome 의 type=number select() 미지원 회피, '-'/'1.' 같은 transient 입력 허용, ArrowUp/Down step. NumberInput(폼·금액용) 과 책임 분리. `npx sh-ui-cli add numeric-input`",
|
|
12
|
+
"**Accordion size prop** — `size: 'sm' | 'md'`. sm 은 padding 8/4 + font 12px + chevron 12px 로 좁은 사이드바·다중 섹션에 적합. 페이지별 CSS override 대신 컴포넌트 레벨 variant 로 승격",
|
|
13
|
+
"**ShadowBuilder** (playground) — raw CSS `0 4px 12px rgba(...)` 직접 입력 → Figma 스타일 시각 편집기. X/Y/Blur/Spread NumericInput + ColorPicker + alpha %",
|
|
14
|
+
"**XY 패드 드래그** — ShadowBuilder 의 미리보기 박스가 ColorPicker 색공간처럼 클릭/드래그 가능. pointer 위치 → X/Y offset (1:1 px, ±50 클램프). pointer capture + touch-action: none 으로 모바일 터치 드래그도 지원",
|
|
15
|
+
"**advanced 모드 8 섹션** native details → sh-ui Accordion size=sm 으로 교체. chevron 인디케이터로 클릭 가능 신호 명확. 내보내기 섹션도 동일 패턴",
|
|
16
|
+
"**Accordion trigger hover** underline → background var(--background-muted) 틴트, disabled 가드(`:not([disabled]):not([data-disabled])`) — disabled 항목은 hover 무효과",
|
|
17
|
+
"**슬라이더 동반 키보드 입력** — 8 카테고리 31 슬라이더 + 그라데이션 9 슬라이더 옆 값 표시를 NumericInput 으로 교체. 드래그 + 키보드 입력 둘 다 지원. Slider docs 의 ControlledDemo 도 양방향 동기화로 업그레이드",
|
|
18
|
+
"**초기화 3분기** — '전체 초기화' / 'Light 초기화' / 'Dark 초기화'. 이전엔 모드별 한 버튼 (색만 reset) 또는 전체 한 버튼만 — 모드 색만 빠르게 되돌리는 use case + 모든 카테고리 nuke 둘 다 지원",
|
|
19
|
+
"**encodeTheme UTF-8 안전** — shadow/ease/gradient 가 자유 string 이라 한글 IME 입력 시 btoa(Latin1-only) InvalidCharacterError 회귀. TextEncoder UTF-8 바이트 → byte 별 String.fromCharCode → btoa 패턴으로 교체",
|
|
20
|
+
"**docs 페이지** — /components/numeric-input 신규 (server component + _demos 분리), Accordion size 예시 + API Reference 보강, '스타일 커스터마이즈' 에 hover 효과 override 안내"
|
|
21
|
+
],
|
|
22
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.40.0"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"version": "0.39.0",
|
|
26
|
+
"date": "2026-04-29",
|
|
27
|
+
"title": "프리셋 정체성 강화 — slate / rose / violet 이 typography·controls·borders 도 차별화",
|
|
28
|
+
"type": "minor",
|
|
29
|
+
"highlights": [
|
|
30
|
+
"0.38.0 까지 5 프리셋이 색·radius 만 차별화 → 이제 typography·controls·borders 까지 프리셋 별 인상이 다름. neutral / emerald 는 디폴트 baseline (변화 없음), 나머지 셋은 정체성 부여",
|
|
31
|
+
"**slate** (정보 밀도) — text-base 14px (xs 11/sm 12/lg 16/xl 18/2xl 21/3xl 26/4xl 32), control-md 36px (sm 28/lg 44). 대시보드/관리자 인상",
|
|
32
|
+
"**rose** (친근·여유) — control-md 44px (sm 36/lg 52). 큼직한 터치 타겟 + 0.75rem 라운드 → 소비자 앱 인상",
|
|
33
|
+
"**violet** (모던·또렷) — control-md 42px (sm 34/lg 50), border-width-strong 3px (디폴트 2px). 약간 두꺼운 강조 보더로 창의·디자인 도구 인상",
|
|
34
|
+
"getThemePreset 시그니처 — 이전 `{light, dark, radius}` 만 → 이제 label 빼고 모든 카테고리 forward. CLI inject 가 자동으로 마커 섹션 교체",
|
|
35
|
+
"api.d.ts ThemePreset 인터페이스에 옵셔널 카테고리 6종 추가 (typography/controls/borders/spacing/weights/durations) — apps/docs 등 외부 사용자가 타입 안전",
|
|
36
|
+
"테스트 111개 (전 104 → +7). end-to-end 검증 — `--theme rose` 가 control-md 40 → 44 로 덮어쓰는지, `--theme slate` 가 text-base 16 → 14 로, `--theme violet` 이 border-width-strong 2 → 3 으로",
|
|
37
|
+
"참고: 기존 사용자가 0.38.0 까지 `--theme rose` 로 만든 프로젝트는 영향 없음 (이미 생성된 tokens.css 는 그대로). 새로 `--theme rose` 하면 0.39.0 정체성 그대로 들어감"
|
|
38
|
+
],
|
|
39
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.39.0"
|
|
40
|
+
},
|
|
5
41
|
{
|
|
6
42
|
"version": "0.38.0",
|
|
7
43
|
"date": "2026-04-29",
|
|
@@ -8,16 +8,30 @@ function cx(...args: (string | undefined | false)[]) {
|
|
|
8
8
|
|
|
9
9
|
type WithStringClassName<T> = Omit<T, "className"> & { className?: string };
|
|
10
10
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
export type AccordionSize = "sm" | "md";
|
|
12
|
+
|
|
13
|
+
type AccordionProps = WithStringClassName<
|
|
14
|
+
React.ComponentPropsWithoutRef<typeof BaseAccordion.Root>
|
|
15
|
+
> & {
|
|
16
|
+
/**
|
|
17
|
+
* 트리거 + chevron + content 의 패딩·폰트 크기 묶음.
|
|
18
|
+
* - `md` (기본) — padding 16/4, font 15px, chevron 16px
|
|
19
|
+
* - `sm` — padding 8/4, font 12px, chevron 12px. 좁은 사이드바·다중 섹션에 적합.
|
|
20
|
+
* @default "md"
|
|
21
|
+
*/
|
|
22
|
+
size?: AccordionSize;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const Accordion = React.forwardRef<HTMLDivElement, AccordionProps>(
|
|
26
|
+
({ className, size = "md", ...props }, ref) => (
|
|
27
|
+
<BaseAccordion.Root
|
|
28
|
+
ref={ref}
|
|
29
|
+
className={cx("sh-ui-accordion", className)}
|
|
30
|
+
data-size={size}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
),
|
|
34
|
+
);
|
|
21
35
|
Accordion.displayName = "Accordion";
|
|
22
36
|
|
|
23
37
|
export const AccordionItem = React.forwardRef<
|
|
@@ -32,12 +32,13 @@
|
|
|
32
32
|
line-height: 1.4;
|
|
33
33
|
text-align: left;
|
|
34
34
|
cursor: pointer;
|
|
35
|
+
transition: background-color var(--duration-fast) var(--ease-standard);
|
|
35
36
|
-webkit-tap-highlight-color: transparent;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
/* hover는 enabled 일 때만. background tint — 다른 hover 효과를 원하면 className/style 로 override. */
|
|
40
|
+
.sh-ui-accordion__trigger:not([disabled]):not([data-disabled]):hover {
|
|
41
|
+
background: var(--background-muted);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
.sh-ui-accordion__trigger:focus-visible {
|
|
@@ -86,6 +87,22 @@
|
|
|
86
87
|
color: var(--foreground-muted);
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
/* size="sm" — 좁은 사이드바·다중 섹션에 적합한 컴팩트 변형. */
|
|
91
|
+
.sh-ui-accordion[data-size="sm"] .sh-ui-accordion__trigger {
|
|
92
|
+
padding: var(--space-2) var(--space-1);
|
|
93
|
+
font-size: var(--text-xs);
|
|
94
|
+
line-height: 1.2;
|
|
95
|
+
}
|
|
96
|
+
.sh-ui-accordion[data-size="sm"] .sh-ui-accordion__chevron {
|
|
97
|
+
width: 12px;
|
|
98
|
+
height: 12px;
|
|
99
|
+
}
|
|
100
|
+
.sh-ui-accordion[data-size="sm"] .sh-ui-accordion__content {
|
|
101
|
+
padding: 0 var(--space-1) var(--space-2);
|
|
102
|
+
font-size: var(--text-xs);
|
|
103
|
+
line-height: 1.5;
|
|
104
|
+
}
|
|
105
|
+
|
|
89
106
|
@media (prefers-reduced-motion: reduce) {
|
|
90
107
|
.sh-ui-accordion__panel,
|
|
91
108
|
.sh-ui-accordion__chevron {
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import "./styles.css";
|
|
5
|
+
|
|
6
|
+
function cx(...args: (string | undefined | null | false)[]) {
|
|
7
|
+
return args.filter(Boolean).join(" ");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface NumericInputProps
|
|
11
|
+
extends Omit<
|
|
12
|
+
React.InputHTMLAttributes<HTMLInputElement>,
|
|
13
|
+
"value" | "defaultValue" | "onChange" | "type" | "min" | "max" | "step"
|
|
14
|
+
> {
|
|
15
|
+
/** 제어 모드 값. */
|
|
16
|
+
value?: number;
|
|
17
|
+
/** 비제어 모드 초기값. */
|
|
18
|
+
defaultValue?: number;
|
|
19
|
+
/** 값 변경 콜백. min/max 범위로 자동 clamp 된 값이 전달된다. */
|
|
20
|
+
onValueChange?: (value: number) => void;
|
|
21
|
+
/** 허용 최솟값. 입력값이 이보다 작으면 자동 clamp. */
|
|
22
|
+
min?: number;
|
|
23
|
+
/** 허용 최댓값. 입력값이 이보다 크면 자동 clamp. */
|
|
24
|
+
max?: number;
|
|
25
|
+
/** 화살표 키 step 폭. 디폴트 1. */
|
|
26
|
+
step?: number;
|
|
27
|
+
/** 값 우측에 부착할 단위 표시 (px / ms / % / ° 등). */
|
|
28
|
+
unit?: React.ReactNode;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 슬라이더 동반·토큰 편집 등 컴팩트 컨텍스트에 적합한 숫자 입력.
|
|
33
|
+
*
|
|
34
|
+
* 구현 특이점:
|
|
35
|
+
* - `type="text"` + `inputMode="decimal"` — type=number 가 Chrome 에서 select() 와
|
|
36
|
+
* selectionStart/End 를 지원하지 않아 "0 위에 2 타이핑 → 02" 회귀가 발생함.
|
|
37
|
+
* text 로 바꾸고 우리가 직접 숫자 검증/클램프.
|
|
38
|
+
* - 내부 buffer state — "-", "1.", "" 같이 입력 중간 transient 상태 허용. 유효한
|
|
39
|
+
* 숫자가 되는 순간 onValueChange 즉시 호출. 포커스 잃을 때 정규화.
|
|
40
|
+
* - focus 시 setTimeout(0) → select() — mouseup 의 커서 재배치 이후에 selection
|
|
41
|
+
* 적용되도록.
|
|
42
|
+
* - ArrowUp/Down 으로 step 조정, Enter 로 blur(commit).
|
|
43
|
+
*
|
|
44
|
+
* 일반 폼 입력에는 `Input` / `NumberInput` 사용 권장.
|
|
45
|
+
*/
|
|
46
|
+
export const NumericInput = React.forwardRef<HTMLInputElement, NumericInputProps>(
|
|
47
|
+
(
|
|
48
|
+
{
|
|
49
|
+
value,
|
|
50
|
+
defaultValue,
|
|
51
|
+
onValueChange,
|
|
52
|
+
min,
|
|
53
|
+
max,
|
|
54
|
+
step = 1,
|
|
55
|
+
unit,
|
|
56
|
+
className,
|
|
57
|
+
onFocus,
|
|
58
|
+
onBlur,
|
|
59
|
+
onKeyDown,
|
|
60
|
+
...props
|
|
61
|
+
},
|
|
62
|
+
ref,
|
|
63
|
+
) => {
|
|
64
|
+
const isControlled = value !== undefined;
|
|
65
|
+
const [internal, setInternal] = React.useState<number>(defaultValue ?? 0);
|
|
66
|
+
const current = isControlled ? value! : internal;
|
|
67
|
+
|
|
68
|
+
const [buffer, setBuffer] = React.useState<string>(() => String(current));
|
|
69
|
+
const focusedRef = React.useRef(false);
|
|
70
|
+
|
|
71
|
+
// 포커스 잡지 않은 동안 외부 value 변경되면 buffer 동기화.
|
|
72
|
+
React.useEffect(() => {
|
|
73
|
+
if (!focusedRef.current) setBuffer(String(current));
|
|
74
|
+
}, [current]);
|
|
75
|
+
|
|
76
|
+
const clamp = (n: number) => {
|
|
77
|
+
let v = n;
|
|
78
|
+
if (min !== undefined && v < min) v = min;
|
|
79
|
+
if (max !== undefined && v > max) v = max;
|
|
80
|
+
return v;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const commit = (n: number): number => {
|
|
84
|
+
const c = clamp(n);
|
|
85
|
+
if (!isControlled) setInternal(c);
|
|
86
|
+
onValueChange?.(c);
|
|
87
|
+
return c;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<span className="sh-ui-numeric-input">
|
|
92
|
+
<input
|
|
93
|
+
ref={ref}
|
|
94
|
+
type="text"
|
|
95
|
+
inputMode="decimal"
|
|
96
|
+
className={cx("sh-ui-numeric-input__input", className)}
|
|
97
|
+
value={buffer}
|
|
98
|
+
onChange={(e) => {
|
|
99
|
+
const raw = e.target.value;
|
|
100
|
+
setBuffer(raw);
|
|
101
|
+
// 입력 중간 상태("", "-", ".", "-.") 는 commit 안 함 — 사용자 타이핑 흐름 유지.
|
|
102
|
+
if (raw === "" || raw === "-" || raw === "." || raw === "-.") return;
|
|
103
|
+
const n = Number(raw);
|
|
104
|
+
if (Number.isFinite(n)) commit(n);
|
|
105
|
+
}}
|
|
106
|
+
onFocus={(e) => {
|
|
107
|
+
focusedRef.current = true;
|
|
108
|
+
const t = e.currentTarget;
|
|
109
|
+
// setTimeout 0 로 미뤄야 mouseup 의 커서 재배치 이후에 select 가 적용됨.
|
|
110
|
+
setTimeout(() => t.select(), 0);
|
|
111
|
+
onFocus?.(e);
|
|
112
|
+
}}
|
|
113
|
+
onBlur={(e) => {
|
|
114
|
+
focusedRef.current = false;
|
|
115
|
+
const n = Number(buffer);
|
|
116
|
+
if (buffer !== "" && Number.isFinite(n)) {
|
|
117
|
+
const c = commit(n);
|
|
118
|
+
setBuffer(String(c));
|
|
119
|
+
} else {
|
|
120
|
+
// 비어있거나 NaN — 마지막 유효 값으로 복원
|
|
121
|
+
setBuffer(String(current));
|
|
122
|
+
}
|
|
123
|
+
onBlur?.(e);
|
|
124
|
+
}}
|
|
125
|
+
onKeyDown={(e) => {
|
|
126
|
+
if (e.key === "ArrowUp") {
|
|
127
|
+
e.preventDefault();
|
|
128
|
+
const next = commit(current + step);
|
|
129
|
+
setBuffer(String(next));
|
|
130
|
+
} else if (e.key === "ArrowDown") {
|
|
131
|
+
e.preventDefault();
|
|
132
|
+
const next = commit(current - step);
|
|
133
|
+
setBuffer(String(next));
|
|
134
|
+
} else if (e.key === "Enter") {
|
|
135
|
+
e.currentTarget.blur();
|
|
136
|
+
}
|
|
137
|
+
onKeyDown?.(e);
|
|
138
|
+
}}
|
|
139
|
+
{...props}
|
|
140
|
+
/>
|
|
141
|
+
{unit !== undefined && unit !== "" && (
|
|
142
|
+
<span className="sh-ui-numeric-input__unit" aria-hidden>
|
|
143
|
+
{unit}
|
|
144
|
+
</span>
|
|
145
|
+
)}
|
|
146
|
+
</span>
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
NumericInput.displayName = "NumericInput";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* NumericInput — 컴팩트 monospace 숫자 입력. 슬라이더 동반·토큰 편집기 같은
|
|
2
|
+
좁은 영역용. 일반 폼 입력은 Input / NumberInput 사용. */
|
|
3
|
+
|
|
4
|
+
.sh-ui-numeric-input {
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
align-items: baseline;
|
|
7
|
+
gap: 2px;
|
|
8
|
+
min-width: 3rem;
|
|
9
|
+
justify-content: flex-end;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.sh-ui-numeric-input__input {
|
|
13
|
+
width: 2.5rem;
|
|
14
|
+
padding: 2px 4px;
|
|
15
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
16
|
+
font-size: var(--text-xs);
|
|
17
|
+
line-height: 1.2;
|
|
18
|
+
text-align: right;
|
|
19
|
+
border: 1px solid transparent;
|
|
20
|
+
border-radius: calc(var(--radius) - 4px);
|
|
21
|
+
background: transparent;
|
|
22
|
+
color: var(--foreground);
|
|
23
|
+
appearance: textfield;
|
|
24
|
+
-moz-appearance: textfield;
|
|
25
|
+
transition: border-color var(--duration-fast) var(--ease-standard),
|
|
26
|
+
background-color var(--duration-fast) var(--ease-standard);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* WebKit 의 스피너 버튼 숨김 — 컴팩트 영역에 노이즈. 키보드 step 은 유지됨. */
|
|
30
|
+
.sh-ui-numeric-input__input::-webkit-inner-spin-button,
|
|
31
|
+
.sh-ui-numeric-input__input::-webkit-outer-spin-button {
|
|
32
|
+
-webkit-appearance: none;
|
|
33
|
+
margin: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.sh-ui-numeric-input__input:hover:not(:disabled):not(:focus) {
|
|
37
|
+
border-color: var(--border);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.sh-ui-numeric-input__input:focus,
|
|
41
|
+
.sh-ui-numeric-input__input:focus-visible {
|
|
42
|
+
outline: none;
|
|
43
|
+
border-color: var(--foreground);
|
|
44
|
+
background: var(--background);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.sh-ui-numeric-input__input:disabled {
|
|
48
|
+
cursor: not-allowed;
|
|
49
|
+
opacity: var(--opacity-disabled);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.sh-ui-numeric-input__unit {
|
|
53
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
54
|
+
font-size: var(--text-xs);
|
|
55
|
+
color: var(--foreground-muted);
|
|
56
|
+
}
|
|
@@ -49,6 +49,22 @@
|
|
|
49
49
|
"dependencies": [],
|
|
50
50
|
"registryDependencies": []
|
|
51
51
|
},
|
|
52
|
+
"numeric-input": {
|
|
53
|
+
"name": "numeric-input",
|
|
54
|
+
"type": "component",
|
|
55
|
+
"files": [
|
|
56
|
+
{
|
|
57
|
+
"src": "components/numeric-input/index.tsx",
|
|
58
|
+
"dest": "{components}/numeric-input/index.tsx"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"src": "components/numeric-input/styles.css",
|
|
62
|
+
"dest": "{components}/numeric-input/styles.css"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"dependencies": [],
|
|
66
|
+
"registryDependencies": []
|
|
67
|
+
},
|
|
52
68
|
"file-upload": {
|
|
53
69
|
"name": "file-upload",
|
|
54
70
|
"type": "component",
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
"summaries": {
|
|
4
4
|
"button": "기본 버튼 — variant(primary/secondary/ghost/danger/link) + size(sm/md/lg).",
|
|
5
5
|
"card": "카드 컨테이너 — compound (Card.Header / Card.Body / Card.Footer / Card.Title / Card.Description).",
|
|
6
|
-
"input": "단일 행 텍스트 입력 — hasError 지원.",
|
|
6
|
+
"input": "단일 행 텍스트 입력 — hasError 지원. 같은 모듈에 NumberInput / PasswordInput / PhoneInput / BusinessNumberInput 변형 포함.",
|
|
7
|
+
"numeric-input": "슬라이더 동반·토큰 편집기용 컴팩트 숫자 입력 — onChange 즉시 min/max clamp, focus select-all, 단위(px/ms/%/° 등) suffix. 일반 폼 입력은 NumberInput 권장.",
|
|
7
8
|
"textarea": "여러 행 텍스트 입력 — rows, autoResize.",
|
|
8
9
|
"label": "폼 레이블 — htmlFor로 입력과 연결.",
|
|
9
10
|
"checkbox": "체크박스 — indeterminate 지원 (Base UI).",
|
package/package.json
CHANGED
package/src/api.d.ts
CHANGED
|
@@ -60,6 +60,13 @@ export interface ThemePreset {
|
|
|
60
60
|
dark: Record<ThemeTokenKey, string>;
|
|
61
61
|
/** rem 단위 (0~1.5). */
|
|
62
62
|
radius: number;
|
|
63
|
+
/** v0.39.0+ — 프리셋 별 정체성 차별화 (옵셔널). decode.js SCALAR_CATEGORIES 와 동일 키. */
|
|
64
|
+
typography?: Record<string, number>;
|
|
65
|
+
controls?: Record<string, number>;
|
|
66
|
+
borders?: Record<string, number>;
|
|
67
|
+
spacing?: Record<string, number>;
|
|
68
|
+
weights?: Record<string, number>;
|
|
69
|
+
durations?: Record<string, number>;
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
export const THEME_PRESETS: Record<ThemePresetName, ThemePreset>;
|
|
@@ -50,7 +50,7 @@ export const THEME_PRESETS = {
|
|
|
50
50
|
radius: 0.5,
|
|
51
51
|
},
|
|
52
52
|
slate: {
|
|
53
|
-
label: '슬레이트 — 차분한 슬레이트 + 인디고',
|
|
53
|
+
label: '슬레이트 — 차분한 슬레이트 + 인디고 (정보 밀도)',
|
|
54
54
|
light: {
|
|
55
55
|
'background': '#FFFFFF',
|
|
56
56
|
'background-subtle': '#F8FAFC',
|
|
@@ -86,9 +86,12 @@ export const THEME_PRESETS = {
|
|
|
86
86
|
'danger-foreground': '#450A0A',
|
|
87
87
|
},
|
|
88
88
|
radius: 0.375,
|
|
89
|
+
// 정보 밀도 ↑ — 본문 14px 부터, 컨트롤 36px (대시보드/관리자 인상)
|
|
90
|
+
typography: { xs: 11, sm: 12, base: 14, lg: 16, xl: 18, '2xl': 21, '3xl': 26, '4xl': 32 },
|
|
91
|
+
controls: { sm: 28, md: 36, lg: 44 },
|
|
89
92
|
},
|
|
90
93
|
rose: {
|
|
91
|
-
label: '로즈 — 핑크 강조 + 둥근 모서리',
|
|
94
|
+
label: '로즈 — 핑크 강조 + 둥근 모서리 (친근·여유)',
|
|
92
95
|
light: {
|
|
93
96
|
...NEUTRAL_LIGHT,
|
|
94
97
|
'primary': '#E11D48',
|
|
@@ -102,6 +105,8 @@ export const THEME_PRESETS = {
|
|
|
102
105
|
'primary-hover': '#FDA4AF',
|
|
103
106
|
},
|
|
104
107
|
radius: 0.75,
|
|
108
|
+
// 큰 모서리 + 큼직한 컨트롤 — 소비자 앱 / 캐주얼 인상
|
|
109
|
+
controls: { sm: 36, md: 44, lg: 52 },
|
|
105
110
|
},
|
|
106
111
|
emerald: {
|
|
107
112
|
label: '에메랄드 — 그린 강조',
|
|
@@ -120,7 +125,7 @@ export const THEME_PRESETS = {
|
|
|
120
125
|
radius: 0.5,
|
|
121
126
|
},
|
|
122
127
|
violet: {
|
|
123
|
-
label: '바이올렛 — 퍼플 강조 + 살짝 라운드',
|
|
128
|
+
label: '바이올렛 — 퍼플 강조 + 살짝 라운드 (모던·또렷)',
|
|
124
129
|
light: {
|
|
125
130
|
...NEUTRAL_LIGHT,
|
|
126
131
|
'primary': '#7C3AED',
|
|
@@ -134,14 +139,22 @@ export const THEME_PRESETS = {
|
|
|
134
139
|
'primary-hover': '#C4B5FD',
|
|
135
140
|
},
|
|
136
141
|
radius: 0.625,
|
|
142
|
+
// 살짝 큰 컨트롤 + 진한 강조 보더 (creative/디자인 도구 인상)
|
|
143
|
+
controls: { sm: 34, md: 42, lg: 50 },
|
|
144
|
+
borders: { width: 1, widthStrong: 3 },
|
|
137
145
|
},
|
|
138
146
|
};
|
|
139
147
|
|
|
140
148
|
export const THEME_PRESET_NAMES = Object.keys(THEME_PRESETS);
|
|
141
149
|
|
|
142
|
-
/**
|
|
150
|
+
/**
|
|
151
|
+
* 프리셋 객체에서 ThemeConfig 형태로 변환 — 사용자에게 보이는 label 빼고 모두 forward.
|
|
152
|
+
* v0.39.0 부터 light/dark/radius 외에 옵셔널 카테고리(typography/controls/borders/...)
|
|
153
|
+
* 도 그대로 전달돼 CLI inject 가 카테고리별 마커 섹션을 교체한다.
|
|
154
|
+
*/
|
|
143
155
|
export const getThemePreset = (name) => {
|
|
144
156
|
const preset = THEME_PRESETS[name];
|
|
145
157
|
if (!preset) return null;
|
|
146
|
-
|
|
158
|
+
const { label: _label, ...themeConfig } = preset;
|
|
159
|
+
return themeConfig;
|
|
147
160
|
};
|