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.
Files changed (89) hide show
  1. package/data/changelog/versions.json +25 -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 +2 -2
  6. package/src/api.d.ts +3 -4
  7. package/src/constants.js +9 -5
  8. package/src/create/plugins/pluginSchema.js +5 -3
  9. package/src/mcp.mjs +4 -3
  10. package/data/registry/react/components/accordion/index.vanilla-extract.tsx +0 -97
  11. package/data/registry/react/components/accordion/styles.css.ts +0 -131
  12. package/data/registry/react/components/avatar/index.vanilla-extract.tsx +0 -73
  13. package/data/registry/react/components/avatar/styles.css.ts +0 -68
  14. package/data/registry/react/components/badge/index.vanilla-extract.tsx +0 -40
  15. package/data/registry/react/components/badge/styles.css.ts +0 -71
  16. package/data/registry/react/components/breadcrumb/index.vanilla-extract.tsx +0 -152
  17. package/data/registry/react/components/breadcrumb/styles.css.ts +0 -95
  18. package/data/registry/react/components/calendar/index.vanilla-extract.tsx +0 -806
  19. package/data/registry/react/components/calendar/styles.css.ts +0 -250
  20. package/data/registry/react/components/carousel/index.vanilla-extract.tsx +0 -430
  21. package/data/registry/react/components/carousel/styles.css.ts +0 -169
  22. package/data/registry/react/components/checkbox/index.vanilla-extract.tsx +0 -96
  23. package/data/registry/react/components/checkbox/styles.css.ts +0 -74
  24. package/data/registry/react/components/code-editor/index.vanilla-extract.tsx +0 -230
  25. package/data/registry/react/components/code-editor/styles.css.ts +0 -97
  26. package/data/registry/react/components/code-panel/index.vanilla-extract.tsx +0 -191
  27. package/data/registry/react/components/code-panel/styles.css.ts +0 -151
  28. package/data/registry/react/components/color-picker/index.vanilla-extract.tsx +0 -467
  29. package/data/registry/react/components/color-picker/styles.css.ts +0 -169
  30. package/data/registry/react/components/combobox/index.vanilla-extract.tsx +0 -165
  31. package/data/registry/react/components/combobox/styles.css.ts +0 -174
  32. package/data/registry/react/components/context-menu/index.vanilla-extract.tsx +0 -251
  33. package/data/registry/react/components/context-menu/styles.css.ts +0 -167
  34. package/data/registry/react/components/date-picker/index.vanilla-extract.tsx +0 -520
  35. package/data/registry/react/components/date-picker/styles.css.ts +0 -111
  36. package/data/registry/react/components/dialog/index.vanilla-extract.tsx +0 -95
  37. package/data/registry/react/components/dialog/styles.css.ts +0 -140
  38. package/data/registry/react/components/dropdown-menu/index.vanilla-extract.tsx +0 -255
  39. package/data/registry/react/components/dropdown-menu/styles.css.ts +0 -175
  40. package/data/registry/react/components/file-upload/index.vanilla-extract.tsx +0 -487
  41. package/data/registry/react/components/file-upload/styles.css.ts +0 -193
  42. package/data/registry/react/components/form/index.vanilla-extract.tsx +0 -61
  43. package/data/registry/react/components/form/styles.css.ts +0 -56
  44. package/data/registry/react/components/header/index.vanilla-extract.tsx +0 -805
  45. package/data/registry/react/components/header/styles.css.ts +0 -413
  46. package/data/registry/react/components/label/index.vanilla-extract.tsx +0 -52
  47. package/data/registry/react/components/label/styles.css.ts +0 -141
  48. package/data/registry/react/components/markdown-editor/index.vanilla-extract.tsx +0 -119
  49. package/data/registry/react/components/markdown-editor/styles.css.ts +0 -231
  50. package/data/registry/react/components/menubar/index.vanilla-extract.tsx +0 -32
  51. package/data/registry/react/components/menubar/styles.css.ts +0 -53
  52. package/data/registry/react/components/numeric-input/index.vanilla-extract.tsx +0 -148
  53. package/data/registry/react/components/numeric-input/styles.css.ts +0 -65
  54. package/data/registry/react/components/page-toc/index.vanilla-extract.tsx +0 -174
  55. package/data/registry/react/components/page-toc/styles.css.ts +0 -97
  56. package/data/registry/react/components/pagination/index.vanilla-extract.tsx +0 -269
  57. package/data/registry/react/components/pagination/styles.css.ts +0 -113
  58. package/data/registry/react/components/popover/index.vanilla-extract.tsx +0 -113
  59. package/data/registry/react/components/popover/styles.css.ts +0 -78
  60. package/data/registry/react/components/progress/index.vanilla-extract.tsx +0 -54
  61. package/data/registry/react/components/progress/styles.css.ts +0 -53
  62. package/data/registry/react/components/radio/index.vanilla-extract.tsx +0 -65
  63. package/data/registry/react/components/radio/styles.css.ts +0 -79
  64. package/data/registry/react/components/rich-text-editor/index.vanilla-extract.tsx +0 -348
  65. package/data/registry/react/components/rich-text-editor/styles.css.ts +0 -243
  66. package/data/registry/react/components/select/index.vanilla-extract.tsx +0 -234
  67. package/data/registry/react/components/select/styles.css.ts +0 -225
  68. package/data/registry/react/components/separator/index.vanilla-extract.tsx +0 -46
  69. package/data/registry/react/components/separator/styles.css.ts +0 -24
  70. package/data/registry/react/components/sidebar/index.vanilla-extract.tsx +0 -1067
  71. package/data/registry/react/components/sidebar/styles.css.ts +0 -578
  72. package/data/registry/react/components/skeleton/index.vanilla-extract.tsx +0 -22
  73. package/data/registry/react/components/skeleton/styles.css.ts +0 -30
  74. package/data/registry/react/components/slider/index.vanilla-extract.tsx +0 -298
  75. package/data/registry/react/components/slider/styles.css.ts +0 -75
  76. package/data/registry/react/components/spinner/index.vanilla-extract.tsx +0 -38
  77. package/data/registry/react/components/spinner/styles.css.ts +0 -60
  78. package/data/registry/react/components/switch/index.vanilla-extract.tsx +0 -39
  79. package/data/registry/react/components/switch/styles.css.ts +0 -87
  80. package/data/registry/react/components/tabs/index.vanilla-extract.tsx +0 -91
  81. package/data/registry/react/components/tabs/styles.css.ts +0 -145
  82. package/data/registry/react/components/textarea/index.vanilla-extract.tsx +0 -23
  83. package/data/registry/react/components/textarea/styles.css.ts +0 -55
  84. package/data/registry/react/components/toast/index.vanilla-extract.tsx +0 -258
  85. package/data/registry/react/components/toast/styles.css.ts +0 -307
  86. package/data/registry/react/components/toggle/index.vanilla-extract.tsx +0 -131
  87. package/data/registry/react/components/toggle/styles.css.ts +0 -109
  88. package/data/registry/react/components/tooltip/index.vanilla-extract.tsx +0 -83
  89. 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
- };