sh-ui-cli 0.52.1 → 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.
Files changed (88) hide show
  1. package/data/changelog/versions.json +14 -0
  2. package/data/registry/react/components/_smoke/vanilla-extract.test.ts +33 -0
  3. package/data/registry/react/components/input/styles.css.ts +6 -6
  4. package/data/registry/react/registry.json +35 -852
  5. package/package.json +1 -1
  6. package/src/api.d.ts +3 -4
  7. package/src/constants.js +9 -5
  8. package/src/mcp.mjs +0 -1
  9. package/data/registry/react/components/accordion/index.vanilla-extract.tsx +0 -97
  10. package/data/registry/react/components/accordion/styles.css.ts +0 -131
  11. package/data/registry/react/components/avatar/index.vanilla-extract.tsx +0 -73
  12. package/data/registry/react/components/avatar/styles.css.ts +0 -68
  13. package/data/registry/react/components/badge/index.vanilla-extract.tsx +0 -40
  14. package/data/registry/react/components/badge/styles.css.ts +0 -71
  15. package/data/registry/react/components/breadcrumb/index.vanilla-extract.tsx +0 -152
  16. package/data/registry/react/components/breadcrumb/styles.css.ts +0 -95
  17. package/data/registry/react/components/calendar/index.vanilla-extract.tsx +0 -806
  18. package/data/registry/react/components/calendar/styles.css.ts +0 -250
  19. package/data/registry/react/components/carousel/index.vanilla-extract.tsx +0 -430
  20. package/data/registry/react/components/carousel/styles.css.ts +0 -169
  21. package/data/registry/react/components/checkbox/index.vanilla-extract.tsx +0 -96
  22. package/data/registry/react/components/checkbox/styles.css.ts +0 -74
  23. package/data/registry/react/components/code-editor/index.vanilla-extract.tsx +0 -230
  24. package/data/registry/react/components/code-editor/styles.css.ts +0 -97
  25. package/data/registry/react/components/code-panel/index.vanilla-extract.tsx +0 -191
  26. package/data/registry/react/components/code-panel/styles.css.ts +0 -151
  27. package/data/registry/react/components/color-picker/index.vanilla-extract.tsx +0 -467
  28. package/data/registry/react/components/color-picker/styles.css.ts +0 -169
  29. package/data/registry/react/components/combobox/index.vanilla-extract.tsx +0 -165
  30. package/data/registry/react/components/combobox/styles.css.ts +0 -174
  31. package/data/registry/react/components/context-menu/index.vanilla-extract.tsx +0 -251
  32. package/data/registry/react/components/context-menu/styles.css.ts +0 -167
  33. package/data/registry/react/components/date-picker/index.vanilla-extract.tsx +0 -520
  34. package/data/registry/react/components/date-picker/styles.css.ts +0 -111
  35. package/data/registry/react/components/dialog/index.vanilla-extract.tsx +0 -95
  36. package/data/registry/react/components/dialog/styles.css.ts +0 -140
  37. package/data/registry/react/components/dropdown-menu/index.vanilla-extract.tsx +0 -255
  38. package/data/registry/react/components/dropdown-menu/styles.css.ts +0 -175
  39. package/data/registry/react/components/file-upload/index.vanilla-extract.tsx +0 -487
  40. package/data/registry/react/components/file-upload/styles.css.ts +0 -193
  41. package/data/registry/react/components/form/index.vanilla-extract.tsx +0 -61
  42. package/data/registry/react/components/form/styles.css.ts +0 -56
  43. package/data/registry/react/components/header/index.vanilla-extract.tsx +0 -805
  44. package/data/registry/react/components/header/styles.css.ts +0 -413
  45. package/data/registry/react/components/label/index.vanilla-extract.tsx +0 -52
  46. package/data/registry/react/components/label/styles.css.ts +0 -141
  47. package/data/registry/react/components/markdown-editor/index.vanilla-extract.tsx +0 -119
  48. package/data/registry/react/components/markdown-editor/styles.css.ts +0 -231
  49. package/data/registry/react/components/menubar/index.vanilla-extract.tsx +0 -32
  50. package/data/registry/react/components/menubar/styles.css.ts +0 -53
  51. package/data/registry/react/components/numeric-input/index.vanilla-extract.tsx +0 -148
  52. package/data/registry/react/components/numeric-input/styles.css.ts +0 -65
  53. package/data/registry/react/components/page-toc/index.vanilla-extract.tsx +0 -174
  54. package/data/registry/react/components/page-toc/styles.css.ts +0 -97
  55. package/data/registry/react/components/pagination/index.vanilla-extract.tsx +0 -269
  56. package/data/registry/react/components/pagination/styles.css.ts +0 -113
  57. package/data/registry/react/components/popover/index.vanilla-extract.tsx +0 -113
  58. package/data/registry/react/components/popover/styles.css.ts +0 -78
  59. package/data/registry/react/components/progress/index.vanilla-extract.tsx +0 -54
  60. package/data/registry/react/components/progress/styles.css.ts +0 -53
  61. package/data/registry/react/components/radio/index.vanilla-extract.tsx +0 -65
  62. package/data/registry/react/components/radio/styles.css.ts +0 -79
  63. package/data/registry/react/components/rich-text-editor/index.vanilla-extract.tsx +0 -348
  64. package/data/registry/react/components/rich-text-editor/styles.css.ts +0 -243
  65. package/data/registry/react/components/select/index.vanilla-extract.tsx +0 -234
  66. package/data/registry/react/components/select/styles.css.ts +0 -225
  67. package/data/registry/react/components/separator/index.vanilla-extract.tsx +0 -46
  68. package/data/registry/react/components/separator/styles.css.ts +0 -24
  69. package/data/registry/react/components/sidebar/index.vanilla-extract.tsx +0 -1067
  70. package/data/registry/react/components/sidebar/styles.css.ts +0 -578
  71. package/data/registry/react/components/skeleton/index.vanilla-extract.tsx +0 -22
  72. package/data/registry/react/components/skeleton/styles.css.ts +0 -30
  73. package/data/registry/react/components/slider/index.vanilla-extract.tsx +0 -298
  74. package/data/registry/react/components/slider/styles.css.ts +0 -75
  75. package/data/registry/react/components/spinner/index.vanilla-extract.tsx +0 -38
  76. package/data/registry/react/components/spinner/styles.css.ts +0 -60
  77. package/data/registry/react/components/switch/index.vanilla-extract.tsx +0 -39
  78. package/data/registry/react/components/switch/styles.css.ts +0 -87
  79. package/data/registry/react/components/tabs/index.vanilla-extract.tsx +0 -91
  80. package/data/registry/react/components/tabs/styles.css.ts +0 -145
  81. package/data/registry/react/components/textarea/index.vanilla-extract.tsx +0 -23
  82. package/data/registry/react/components/textarea/styles.css.ts +0 -55
  83. package/data/registry/react/components/toast/index.vanilla-extract.tsx +0 -258
  84. package/data/registry/react/components/toast/styles.css.ts +0 -307
  85. package/data/registry/react/components/toggle/index.vanilla-extract.tsx +0 -131
  86. package/data/registry/react/components/toggle/styles.css.ts +0 -109
  87. package/data/registry/react/components/tooltip/index.vanilla-extract.tsx +0 -83
  88. package/data/registry/react/components/tooltip/styles.css.ts +0 -59
@@ -1,169 +0,0 @@
1
- import { style } from "@vanilla-extract/css";
2
-
3
- export const carousel = style({
4
- position: "relative",
5
- width: "100%",
6
- });
7
-
8
- export const carousel__content = style({
9
- display: "flex",
10
- gap: "var(--space-4)",
11
- overflowX: "auto",
12
- overflowY: "hidden",
13
- scrollSnapType: "x mandatory",
14
- scrollBehavior: "smooth",
15
- scrollbarWidth: "none",
16
- MsOverflowStyle: "none",
17
- WebkitOverflowScrolling: "touch",
18
- overscrollBehaviorInline: "contain",
19
- selectors: {
20
- "&::-webkit-scrollbar": {
21
- display: "none",
22
- },
23
- "&[data-orientation="vertical"]": {
24
- flexDirection: "column",
25
- overflowX: "hidden",
26
- overflowY: "auto",
27
- scrollSnapType: "y mandatory",
28
- height: "20rem",
29
- },
30
- },
31
- "@media": {
32
- "(prefers-reduced-motion: reduce)": {
33
- scrollBehavior: "auto",
34
- },
35
- },
36
- });
37
-
38
- export const carousel__item = style({
39
- flex: "0 0 100%",
40
- minWidth: 0,
41
- scrollSnapAlign: "start",
42
- scrollSnapStop: "always",
43
- selectors: {
44
- "&[data-orientation="vertical"]": {
45
- flexBasis: "auto",
46
- },
47
- },
48
- });
49
-
50
- export const carousel__nav = style({
51
- position: "absolute",
52
- top: "50%",
53
- width: "2rem",
54
- height: "2rem",
55
- display: "inline-flex",
56
- alignItems: "center",
57
- justifyContent: "center",
58
- background: "var(--background)",
59
- color: "var(--foreground)",
60
- border: "1px solid var(--border)",
61
- borderRadius: "999px",
62
- cursor: "pointer",
63
- transform: "translateY(-50%)",
64
- zIndex: 1,
65
- transition: "opacity var(--duration-fast) ease,\n background var(--duration-fast) ease",
66
- selectors: {
67
- "&:hover:not(:disabled)": {
68
- background: "var(--background-muted)",
69
- },
70
- "&:focus-visible": {
71
- outline: "var(--border-width-strong) solid var(--foreground)",
72
- outlineOffset: "2px",
73
- },
74
- "&:disabled": {
75
- opacity: 0.4,
76
- cursor: "not-allowed",
77
- },
78
- "&[data-orientation="vertical"]": {
79
- top: "auto",
80
- left: "50%",
81
- transform: "translateX(-50%)",
82
- },
83
- },
84
- "@media": {
85
- "(prefers-reduced-motion: reduce)": {
86
- transition: "none",
87
- },
88
- },
89
- });
90
-
91
- export const carouselNavPrev = style({
92
- left: "-1rem",
93
- selectors: {
94
- "&[data-orientation="vertical"]": {
95
- top: "-1rem",
96
- left: "50%",
97
- },
98
- },
99
- });
100
-
101
- export const carouselNavNext = style({
102
- right: "-1rem",
103
- selectors: {
104
- "&[data-orientation="vertical"]": {
105
- bottom: "-1rem",
106
- top: "auto",
107
- left: "50%",
108
- },
109
- },
110
- });
111
-
112
- export const carousel__indicators = style({
113
- display: "flex",
114
- justifyContent: "center",
115
- alignItems: "center",
116
- gap: "var(--space-2)",
117
- marginTop: "var(--space-3)",
118
- selectors: {
119
- "&[data-orientation="vertical"]": {
120
- position: "absolute",
121
- top: "50%",
122
- right: "0.5rem",
123
- marginTop: 0,
124
- flexDirection: "column",
125
- transform: "translateY(-50%)",
126
- },
127
- },
128
- });
129
-
130
- export const carousel__indicator = style({
131
- width: "0.5rem",
132
- height: "0.5rem",
133
- padding: 0,
134
- background: "var(--border)",
135
- border: "none",
136
- borderRadius: "999px",
137
- cursor: "pointer",
138
- transition: "background var(--duration-fast) ease,\n transform var(--duration-fast) ease",
139
- selectors: {
140
- "&:hover": {
141
- background: "var(--border-strong)",
142
- },
143
- "&[data-active]": {
144
- background: "var(--foreground)",
145
- transform: "scale(1.2)",
146
- },
147
- "&:focus-visible": {
148
- outline: "var(--border-width-strong) solid var(--foreground)",
149
- outlineOffset: "2px",
150
- },
151
- },
152
- "@media": {
153
- "(prefers-reduced-motion: reduce)": {
154
- transition: "none",
155
- },
156
- },
157
- });
158
-
159
- /** 동적 키로 클래스 참조용 — `byKey[\`badge--${variant}\`]` 같은 패턴 지원. */
160
- export const byKey: Record<string, string> = {
161
- "carousel": carousel,
162
- "carousel__content": carousel__content,
163
- "carousel__item": carousel__item,
164
- "carousel__nav": carousel__nav,
165
- "carousel__nav--prev": carouselNavPrev,
166
- "carousel__nav--next": carouselNavNext,
167
- "carousel__indicators": carousel__indicators,
168
- "carousel__indicator": carousel__indicator,
169
- };
@@ -1,96 +0,0 @@
1
- import * as React from "react";
2
- import { Checkbox as BaseCheckbox } from "@base-ui/react/checkbox";
3
- import { CheckboxGroup as BaseCheckboxGroup } from "@base-ui/react/checkbox-group";
4
- import { byKey, checkbox, checkbox__indicator, checkboxGroup } from "./styles.css";
5
-
6
-
7
- import { cn } from "@SH_UI_UTILS@";
8
- /* ───────────── Checkbox ───────────── */
9
-
10
- export type CheckboxProps = Omit<
11
- React.ComponentPropsWithoutRef<typeof BaseCheckbox.Root>,
12
- "className"
13
- > & {
14
- className?: string;
15
- };
16
-
17
- /**
18
- * 폼 제출 시 함께 적용되는 다중 선택. `indeterminate`로 부분 선택 상태를
19
- * 표현할 수 있고, 여러 개를 묶을 때는 `CheckboxGroup`으로 감싸 그룹 단위 상태를 관리한다.
20
- */
21
- export const Checkbox = React.forwardRef<HTMLElement, CheckboxProps>(
22
- ({ className, ...props }, ref) => (
23
- <BaseCheckbox.Root
24
- ref={ref}
25
- className={cn(checkbox, className)}
26
- {...props}
27
- >
28
- <BaseCheckbox.Indicator className={checkbox__indicator}>
29
- {props.indeterminate ? <MinusIcon /> : <CheckIcon />}
30
- </BaseCheckbox.Indicator>
31
- </BaseCheckbox.Root>
32
- ),
33
- );
34
- Checkbox.displayName = "Checkbox";
35
-
36
- /* ───────────── CheckboxGroup ───────────── */
37
-
38
- export type CheckboxGroupProps = Omit<
39
- React.ComponentPropsWithoutRef<typeof BaseCheckboxGroup>,
40
- "className"
41
- > & {
42
- className?: string;
43
- /**
44
- * 그룹 내 체크박스 배치 방향.
45
- * - `vertical` — 세로 나열 (기본)
46
- * - `horizontal` — 가로 나열. 짧은 라벨 2~3개에만 권장
47
- *
48
- * @default "vertical"
49
- */
50
- orientation?: "horizontal" | "vertical";
51
- };
52
-
53
- /**
54
- * 여러 Checkbox를 묶는 컨테이너. `value`/`onValueChange`로 선택된 값 배열을 관리하고,
55
- * `orientation`으로 가로/세로 배치를 정한다. 그룹 라벨은 외부 `<Label>`로 제공할 것.
56
- */
57
- export const CheckboxGroup = React.forwardRef<HTMLDivElement, CheckboxGroupProps>(
58
- ({ className, orientation = "vertical", ...props }, ref) => (
59
- <BaseCheckboxGroup
60
- ref={ref}
61
- className={cn(checkboxGroup, className)}
62
- data-orientation={orientation}
63
- {...props}
64
- />
65
- ),
66
- );
67
- CheckboxGroup.displayName = "CheckboxGroup";
68
-
69
- /* ───────────── Icons ───────────── */
70
-
71
- function CheckIcon() {
72
- return (
73
- <svg viewBox="0 0 16 16" width="12" height="12" fill="none" aria-hidden>
74
- <path
75
- d="M3.5 8.5l3 3 6-7"
76
- stroke="currentColor"
77
- strokeWidth="2"
78
- strokeLinecap="round"
79
- strokeLinejoin="round"
80
- />
81
- </svg>
82
- );
83
- }
84
-
85
- function MinusIcon() {
86
- return (
87
- <svg viewBox="0 0 16 16" width="12" height="12" fill="none" aria-hidden>
88
- <path
89
- d="M4 8h8"
90
- stroke="currentColor"
91
- strokeWidth="2"
92
- strokeLinecap="round"
93
- />
94
- </svg>
95
- );
96
- }
@@ -1,74 +0,0 @@
1
- import { style } from "@vanilla-extract/css";
2
-
3
- export const checkbox = style({
4
- display: "inline-flex",
5
- alignItems: "center",
6
- justifyContent: "center",
7
- width: "1.125rem",
8
- height: "1.125rem",
9
- border: "1px solid var(--border-strong)",
10
- borderRadius: "calc(var(--radius) - 2px)",
11
- background: "var(--background)",
12
- color: "var(--primary-foreground)",
13
- cursor: "pointer",
14
- flexShrink: 0,
15
- transition: "background-color var(--duration-fast), border-color var(--duration-fast)",
16
- WebkitTapHighlightColor: "transparent",
17
- selectors: {
18
- "&:hover:not([data-disabled])": {
19
- borderColor: "var(--foreground)",
20
- },
21
- "&:focus-visible": {
22
- outline: "var(--border-width-strong) solid var(--foreground)",
23
- outlineOffset: "2px",
24
- },
25
- "&[data-checked]": {
26
- background: "var(--primary)",
27
- borderColor: "var(--primary)",
28
- },
29
- "&[data-indeterminate]": {
30
- background: "var(--primary)",
31
- borderColor: "var(--primary)",
32
- },
33
- "&[data-disabled]": {
34
- opacity: "var(--opacity-disabled)",
35
- cursor: "not-allowed",
36
- },
37
- },
38
- "@media": {
39
- "(hover: none) and (pointer: coarse)": {
40
- width: "1.25rem",
41
- height: "1.25rem",
42
- },
43
- "(prefers-reduced-motion: reduce)": {
44
- transition: "none",
45
- },
46
- },
47
- });
48
-
49
- export const checkbox__indicator = style({
50
- display: "inline-flex",
51
- alignItems: "center",
52
- justifyContent: "center",
53
- });
54
-
55
- export const checkboxGroup = style({
56
- display: "flex",
57
- gap: "0.625rem",
58
- selectors: {
59
- "&[data-orientation="vertical"]": {
60
- flexDirection: "column",
61
- },
62
- "&[data-orientation="horizontal"]": {
63
- flexDirection: "row",
64
- flexWrap: "wrap",
65
- },
66
- },
67
- });
68
-
69
- /** 동적 키로 클래스 참조용 — `byKey[\`badge--${variant}\`]` 같은 패턴 지원. */
70
- export const byKey: Record<string, string> = {
71
- "checkbox": checkbox,
72
- "checkbox__indicator": checkbox__indicator,
73
- "checkbox-group": checkboxGroup,
74
- };
@@ -1,230 +0,0 @@
1
- "use client";
2
-
3
- import { useEffect, useMemo, useRef } from "react";
4
- import { Compartment, EditorState, type Extension } from "@codemirror/state";
5
- import { EditorView, placeholder as placeholderExt } from "@codemirror/view";
6
- import { basicSetup } from "codemirror";
7
- import { javascript } from "@codemirror/lang-javascript";
8
- import { json } from "@codemirror/lang-json";
9
- import { css as cssLang } from "@codemirror/lang-css";
10
- import { html } from "@codemirror/lang-html";
11
- import { markdown } from "@codemirror/lang-markdown";
12
- import { byKey, codeEditor } from "./styles.css";
13
-
14
- import { cn } from "@SH_UI_UTILS@";
15
- export type CodeEditorLanguage =
16
- | "text"
17
- | "javascript"
18
- | "typescript"
19
- | "jsx"
20
- | "tsx"
21
- | "json"
22
- | "css"
23
- | "html"
24
- | "markdown";
25
-
26
- export interface CodeEditorProps {
27
- /**
28
- * Controlled — 현재 코드. 명시 시 value 가 진실원천이 되고 onChange 로 외부에서 갱신해야 한다.
29
- * 미지정이면 uncontrolled — 에디터가 자체 내부 문서로 동작.
30
- */
31
- value?: string;
32
- /**
33
- * Uncontrolled 초기값. value 미지정 시에만 사용된다.
34
- * @default ""
35
- */
36
- defaultValue?: string;
37
- /** 코드가 바뀔 때마다 호출 (controlled · uncontrolled 모두). */
38
- onChange?: (value: string) => void;
39
- /**
40
- * 신택스 하이라이팅 언어.
41
- * @default "text"
42
- */
43
- language?: CodeEditorLanguage;
44
- /** 비어 있을 때 표시할 placeholder. */
45
- placeholder?: string;
46
- /** 읽기 전용. 키 입력은 막지만 선택·복사는 가능. */
47
- readOnly?: boolean;
48
- /**
49
- * 좌측 줄 번호 표시 여부.
50
- * @default true
51
- */
52
- showLineNumbers?: boolean;
53
- /** 에디터 최소 높이 (CSS 길이 단위). */
54
- minHeight?: string;
55
- /** 에디터 최대 높이 (CSS 길이 단위). 초과 시 내부 스크롤. */
56
- maxHeight?: string;
57
- className?: string;
58
- id?: string;
59
- "aria-label"?: string;
60
- "aria-labelledby"?: string;
61
- }
62
-
63
-
64
- function languageExtension(language: CodeEditorLanguage): Extension {
65
- switch (language) {
66
- case "javascript":
67
- return javascript();
68
- case "typescript":
69
- return javascript({ typescript: true });
70
- case "jsx":
71
- return javascript({ jsx: true });
72
- case "tsx":
73
- return javascript({ jsx: true, typescript: true });
74
- case "json":
75
- return json();
76
- case "css":
77
- return cssLang();
78
- case "html":
79
- return html();
80
- case "markdown":
81
- return markdown();
82
- case "text":
83
- default:
84
- return [];
85
- }
86
- }
87
-
88
- /**
89
- * CodeMirror 6 기반 인라인 코드 에디터.
90
- *
91
- * Controlled (value/onChange) · Uncontrolled (defaultValue + 선택 onChange) 모두 지원.
92
- * 라우터·외부 상태와 동기화할 게 없는 경우 defaultValue 한 줄로 끝 — useState 불필요.
93
- *
94
- * 신택스 하이라이팅·자동 들여쓰기·괄호 매칭 등은 CodeMirror `basicSetup` 을 그대로 사용,
95
- * 컬러·여백은 sh-ui 토큰(`--background`, `--foreground`, `--border` 등)으로 매핑돼 테마에 자동 추종.
96
- */
97
- export function CodeEditor({
98
- value: valueProp,
99
- defaultValue,
100
- onChange,
101
- language = "text",
102
- placeholder,
103
- readOnly = false,
104
- showLineNumbers = true,
105
- minHeight,
106
- maxHeight,
107
- className,
108
- id,
109
- "aria-label": ariaLabel,
110
- "aria-labelledby": ariaLabelledBy,
111
- }: CodeEditorProps) {
112
- const isControlled = valueProp !== undefined;
113
- const hostRef = useRef<HTMLDivElement>(null);
114
- const viewRef = useRef<EditorView | null>(null);
115
- const onChangeRef = useRef(onChange);
116
- onChangeRef.current = onChange;
117
- const initialDocRef = useRef(valueProp ?? defaultValue ?? "");
118
-
119
- const compartments = useMemo(
120
- () => ({
121
- language: new Compartment(),
122
- readOnly: new Compartment(),
123
- lineNumbers: new Compartment(),
124
- placeholder: new Compartment(),
125
- }),
126
- [],
127
- );
128
-
129
- useEffect(() => {
130
- if (!hostRef.current) return;
131
-
132
- const extensions: Extension[] = [
133
- basicSetup,
134
- EditorView.lineWrapping,
135
- EditorView.updateListener.of((update) => {
136
- if (update.docChanged) {
137
- onChangeRef.current?.(update.state.doc.toString());
138
- }
139
- }),
140
- compartments.language.of(languageExtension(language)),
141
- compartments.readOnly.of(EditorState.readOnly.of(readOnly)),
142
- compartments.lineNumbers.of(
143
- showLineNumbers
144
- ? []
145
- : EditorView.theme({ ".cm-gutters": { display: "none" } }),
146
- ),
147
- compartments.placeholder.of(placeholder ? placeholderExt(placeholder) : []),
148
- ];
149
-
150
- const view = new EditorView({
151
- state: EditorState.create({ doc: initialDocRef.current, extensions }),
152
- parent: hostRef.current,
153
- });
154
- viewRef.current = view;
155
-
156
- return () => {
157
- view.destroy();
158
- viewRef.current = null;
159
- };
160
- // 초기 마운트 1회만 — 후속 동기화는 별도 이펙트가 처리
161
- // eslint-disable-next-line react-hooks/exhaustive-deps
162
- }, []);
163
-
164
- // controlled 모드에서만 외부 value 를 에디터 doc 에 동기화. uncontrolled 면 에디터가 자체 source-of-truth.
165
- useEffect(() => {
166
- if (!isControlled) return;
167
- const view = viewRef.current;
168
- if (!view) return;
169
- const current = view.state.doc.toString();
170
- if (current === valueProp) return;
171
- view.dispatch({
172
- changes: { from: 0, to: current.length, insert: valueProp ?? "" },
173
- });
174
- }, [isControlled, valueProp]);
175
-
176
- useEffect(() => {
177
- viewRef.current?.dispatch({
178
- effects: compartments.language.reconfigure(languageExtension(language)),
179
- });
180
- }, [language, compartments.language]);
181
-
182
- useEffect(() => {
183
- viewRef.current?.dispatch({
184
- effects: compartments.readOnly.reconfigure(EditorState.readOnly.of(readOnly)),
185
- });
186
- }, [readOnly, compartments.readOnly]);
187
-
188
- useEffect(() => {
189
- viewRef.current?.dispatch({
190
- effects: compartments.lineNumbers.reconfigure(
191
- showLineNumbers
192
- ? []
193
- : EditorView.theme({ ".cm-gutters": { display: "none" } }),
194
- ),
195
- });
196
- }, [showLineNumbers, compartments.lineNumbers]);
197
-
198
- useEffect(() => {
199
- viewRef.current?.dispatch({
200
- effects: compartments.placeholder.reconfigure(
201
- placeholder ? placeholderExt(placeholder) : [],
202
- ),
203
- });
204
- }, [placeholder, compartments.placeholder]);
205
-
206
- useEffect(() => {
207
- const view = viewRef.current;
208
- if (!view) return;
209
- const node = view.contentDOM;
210
- if (id) node.id = id;
211
- if (ariaLabel) node.setAttribute("aria-label", ariaLabel);
212
- else node.removeAttribute("aria-label");
213
- if (ariaLabelledBy) node.setAttribute("aria-labelledby", ariaLabelledBy);
214
- else node.removeAttribute("aria-labelledby");
215
- }, [id, ariaLabel, ariaLabelledBy]);
216
-
217
- return (
218
- <div
219
- ref={hostRef}
220
- className={cn(codeEditor, className)}
221
- data-readonly={readOnly || undefined}
222
- style={
223
- {
224
- "--sh-ui-code-editor-min-height": minHeight,
225
- "--sh-ui-code-editor-max-height": maxHeight,
226
- } as React.CSSProperties
227
- }
228
- />
229
- );
230
- }
@@ -1,97 +0,0 @@
1
- import { style } from "@vanilla-extract/css";
2
-
3
- export const codeEditor = style({
4
- position: "relative",
5
- border: "1px solid var(--border)",
6
- borderRadius: "var(--radius)",
7
- background: "var(--background)",
8
- fontSize: "0.8125rem",
9
- lineHeight: 1.6,
10
- overflow: "hidden",
11
- transition: "border-color var(--duration-fast)",
12
- selectors: {
13
- "&:focus-within": {
14
- borderColor: "var(--foreground)",
15
- outline: "var(--border-width-strong) solid var(--foreground)",
16
- outlineOffset: "2px",
17
- },
18
- "&[data-readonly]": {
19
- background: "var(--background-subtle)",
20
- },
21
- "& .cm-editor": {
22
- background: "transparent",
23
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
24
- },
25
- "& .cm-editor.cm-focused": {
26
- outline: "none",
27
- },
28
- "& .cm-scroller": {
29
- fontFamily: "inherit",
30
- minHeight: "var(--sh-ui-code-editor-min-height, 7.5rem)",
31
- maxHeight: "var(--sh-ui-code-editor-max-height, 25rem)",
32
- },
33
- "& .cm-content": {
34
- caretColor: "var(--foreground)",
35
- color: "var(--foreground)",
36
- padding: "var(--space-3) 0",
37
- },
38
- "& .cm-line": {
39
- padding: "0 var(--space-3)",
40
- },
41
- "& .cm-gutters": {
42
- background: "var(--background-subtle)",
43
- color: "var(--foreground-muted)",
44
- borderRight: "1px solid var(--border)",
45
- },
46
- "& .cm-activeLineGutter": {
47
- background: "var(--background-muted)",
48
- },
49
- "& .cm-activeLine": {
50
- background: "var(--background-muted)",
51
- },
52
- "& .cm-cursor": {
53
- borderLeftColor: "var(--foreground)",
54
- },
55
- "& .cm-dropCursor": {
56
- borderLeftColor: "var(--foreground)",
57
- },
58
- "& .cm-selectionBackground": {
59
- background: "var(--background-muted) !important",
60
- },
61
- "& .cm-editor .cm-selectionBackground": {
62
- background: "var(--background-muted) !important",
63
- },
64
- "& .cm-editor.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground": {
65
- background: "var(--background-muted) !important",
66
- },
67
- "& ::selection": {
68
- background: "var(--background-muted) !important",
69
- },
70
- "& .cm-placeholder": {
71
- color: "var(--foreground-muted)",
72
- },
73
- "& .cm-tooltip": {
74
- background: "var(--background)",
75
- border: "1px solid var(--border)",
76
- color: "var(--foreground)",
77
- borderRadius: "calc(var(--radius) - 2px)",
78
- },
79
- "& .cm-tooltip-autocomplete > ul > li[aria-selected]": {
80
- background: "var(--background-muted)",
81
- color: "var(--foreground)",
82
- },
83
- "& .cm-matchingBracket": {
84
- background: "var(--background-muted)",
85
- color: "var(--foreground)",
86
- },
87
- "& .cm-nonmatchingBracket": {
88
- background: "var(--background-muted)",
89
- color: "var(--foreground)",
90
- },
91
- },
92
- });
93
-
94
- /** 동적 키로 클래스 참조용 — `byKey[\`badge--${variant}\`]` 같은 패턴 지원. */
95
- export const byKey: Record<string, string> = {
96
- "code-editor": codeEditor,
97
- };