@opsnow-mcp/opsnow-mcp-common-ui-server 1.0.28 → 1.0.29

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.
@@ -6,6 +6,7 @@ import { TooltipExamples } from "./examples/opsnow-common-tooltip-examples-data.
6
6
  import { CoachMarkExamples } from "./examples/opsnow-common-coach-mark-examples-data.js";
7
7
  import { DataGridExamples } from "./examples/opsnow-common-grid-examples-data.js";
8
8
  import { PopupExamples } from "./examples/opsnow-common-popup-examples-data.js";
9
+ import { ToastPopupExamples } from "./examples/opsnow-common-toast-popup-examples-data.js";
9
10
  import { StepperExamples } from "./examples/opsnow-common-stepper-examples-data.js";
10
11
  import { StorageExamples } from "./examples/opsnow-common-storage-examples-data.js";
11
12
  import { SelectExamples } from "./examples/opsnow-common-select-examples-data.js";
@@ -19,7 +20,7 @@ import { GridLayoutExamples, MegaFilterExamples } from "./examples/opsnow-common
19
20
  import { TypographyExamples } from "./examples/opsnow-common-typography-examples-data.js";
20
21
  import { AutocompleteExamples } from "./examples/opsnow-common-select-examples-data.js";
21
22
  import { z } from "zod";
22
- const EXAMPLES_MAP = {
23
+ export const EXAMPLES_MAP = {
23
24
  Button: ButtonExamples,
24
25
  Badge: BadgeExamples,
25
26
  Textarea: TextareaExamples,
@@ -51,6 +52,7 @@ const EXAMPLES_MAP = {
51
52
  CoachMark: CoachMarkExamples,
52
53
  DataGrid: DataGridExamples,
53
54
  Popup: PopupExamples,
55
+ ToastPopup: ToastPopupExamples,
54
56
  Stepper: StepperExamples,
55
57
  Storage: StorageExamples,
56
58
  Select: SelectExamples,
@@ -65,48 +67,130 @@ const EXAMPLES_MAP = {
65
67
  Typography: TypographyExamples,
66
68
  Autocomplete: AutocompleteExamples,
67
69
  };
70
+ // ============= 토큰 절감: 대형 예제는 목록 모드 =============
71
+ // 예제 전문 합계가 이 바이트를 넘으면 제목 목록을 먼저 반환 (keyword로 필요한 것만 조회)
72
+ // 크기는 예제 개수가 아니라 코드 분량이 좌우하므로 바이트 기준 (HeatmapChart: 4개=17KB)
73
+ const FULL_RESPONSE_BYTES = 12_000;
74
+ // 목록 모드에서도 항상 전문으로 포함할 규칙 필수 예제 (제목 부분 일치)
75
+ // DataGrid: 서버 instructions가 이름까지 지목하는 슬롯/페이지네이션 규칙 예제
76
+ // (build-skill.ts가 스킬 examples 분할 시에도 사용)
77
+ export const PRIORITY_EXAMPLES = {
78
+ DataGrid: ["Search + Pagination", "Insert Button Inside the Common Grid Search Section"],
79
+ };
80
+ // 'data-grid', 'DATAGRID' 등도 매칭되도록 정규화
81
+ function normalize(s) {
82
+ return s.toLowerCase().replace(/[-_\s]/g, "");
83
+ }
84
+ function resolveUiType(uiType) {
85
+ const target = normalize(uiType);
86
+ return Object.keys(EXAMPLES_MAP).find(k => normalize(k) === target);
87
+ }
88
+ function asCall(uiType, keyword) {
89
+ return keyword
90
+ ? `getUIExamples({ uiType: "${uiType}", keyword: "${keyword}" })`
91
+ : `getUIExamples({ uiType: "${uiType}" })`;
92
+ }
93
+ function sizeOf(examples) {
94
+ return examples.reduce((a, e) => a + e.title.length + e.code.length + e.description.length + (e.code_props_usage?.length ?? 0), 0);
95
+ }
96
+ // 기존 응답 포맷 유지 (예제 1개당 content 항목 1개)
97
+ function exampleToContent(example) {
98
+ return {
99
+ type: "text",
100
+ text: `**${example.title}** 예제:\n` +
101
+ "```jsx\n" +
102
+ `${example.code}\n` +
103
+ "```\n" +
104
+ `${example.code_props_usage ? `\n**속성 사용 예제:**\n\`\`\`jsx\n${example.code_props_usage}\n\`\`\`\n` : ''}` +
105
+ `${example.description}`,
106
+ };
107
+ }
108
+ // 목록 모드: 규칙 필수 예제는 전문 + 나머지는 제목 목록 + 복붙 가능한 재호출 예시
109
+ function buildListResponse(resolvedType, examples, header) {
110
+ const priorityTitles = PRIORITY_EXAMPLES[resolvedType] ?? [];
111
+ const priority = examples.filter(ex => priorityTitles.some(t => ex.title.includes(t)));
112
+ const rest = examples.filter(ex => !priority.includes(ex));
113
+ const lines = rest.map((ex, i) => `${i + 1}. ${ex.title} — ${ex.description.split('\n')[0].slice(0, 80)}`);
114
+ const firstWord = rest[0]?.title.split(/[\s-]/)[0] || "검색어";
115
+ let text = `${header ? header + '\n\n' : ''}`;
116
+ text += `'${resolvedType}' 예제 ${examples.length}개 중 ${priority.length > 0 ? `필수 예제 ${priority.length}개는 아래 전문으로 포함했고, ` : ''}나머지는 제목 목록입니다:\n\n`;
117
+ text += lines.join('\n');
118
+ text += `\n\n---\n필요한 예제를 keyword로 조회하세요 (제목/설명/코드 내용 부분 일치). 호출 예시:\n`;
119
+ text += `${asCall(resolvedType, firstWord)}\n`;
120
+ text += `전체 예제가 모두 필요하면: ${asCall(resolvedType, "*")}`;
121
+ return {
122
+ content: [
123
+ ...(priority.length > 0 ? priority.map(exampleToContent) : []),
124
+ { type: "text", text },
125
+ ],
126
+ };
127
+ }
68
128
  export const UIExamplesSchema = z.object({
69
129
  uiType: z
70
- .enum(Object.keys(EXAMPLES_MAP))
71
- .describe(`UI 컴포넌트 예제명 (예: 'Button', 'Icon') 호출 가능한 예제 항목: ${Object.keys(EXAMPLES_MAP).join(", ")}`)
130
+ .string()
131
+ .describe(`UI 컴포넌트 예제명 (예: 'Button', 'Icon') 호출 가능한 예제 항목: ${Object.keys(EXAMPLES_MAP).join(", ")}`),
132
+ keyword: z
133
+ .string()
134
+ .optional()
135
+ .describe("예제 제목/설명/코드에서 검색할 키워드 (부분 일치, 대소문자 무시). 예제가 큰 컴포넌트에서 특정 패턴만 조회할 때 사용. '*'를 넣으면 전체 예제 반환"),
72
136
  });
73
137
  export function createCommonExamplesComponent() {
74
138
  return [
75
139
  {
76
140
  name: "getUIExamples",
77
- description: `OpsNow 공통 UI 컴포넌트 예제 목록을 반환합니다.
78
- uiType: string (예: 'Button', 'Icon')
79
- 호출 가능한 예제 항목: ${Object.keys(EXAMPLES_MAP).join(', ')}
80
- uiType은 필수값입니다.`,
141
+ description: `OpsNow 공통 UI 컴포넌트 예제를 반환합니다.
142
+ uiType: string (예: 'Button', 'Icon') - 필수
143
+ keyword: string - 선택. 예제 분량이 큰 컴포넌트는 핵심 예제 전문 + 제목 목록이 먼저 반환되며, 목록에서 고른 키워드로 다시 호출하면 해당 예제 전문을 받습니다. '*'는 전체 반환.
144
+ 호출 가능한 예제 항목: ${Object.keys(EXAMPLES_MAP).join(', ')}`,
81
145
  parameters: UIExamplesSchema,
82
146
  handler: async (args) => {
83
- // 허용된 key만 있는지 검사
84
- const allowedKeys = ["uiType"];
85
- const extraKeys = Object.keys(args).filter(k => !allowedKeys.includes(k));
86
- if (extraKeys.length > 0) {
87
- return { content: [], error: `허용되지 않은 파라미터가 포함되어 있습니다: ${extraKeys.join(", ")}` };
88
- }
89
147
  const type = args.uiType;
90
148
  if (!type) {
91
- return { content: [] };
149
+ return {
150
+ content: [{
151
+ type: "text",
152
+ text: `uiType은 필수입니다. 호출 예시: ${asCall("DataGrid")}\n사용 가능: ${Object.keys(EXAMPLES_MAP).join(", ")}`,
153
+ }],
154
+ };
155
+ }
156
+ // 1. 정규화 매칭 ('datagrid', 'data-grid' → 'DataGrid')
157
+ const resolvedType = resolveUiType(type);
158
+ // 2. 매칭 실패 → 빈 응답 대신 복붙 가능한 대안 호출문 반환
159
+ if (!resolvedType) {
160
+ const target = normalize(type);
161
+ const candidates = Object.keys(EXAMPLES_MAP).filter(k => normalize(k).includes(target) || target.includes(normalize(k)));
162
+ const listed = (candidates.length > 0 ? candidates : Object.keys(EXAMPLES_MAP))
163
+ .map(k => asCall(k))
164
+ .join('\n');
165
+ const intro = candidates.length > 0
166
+ ? `'${type}' 예제를 찾을 수 없습니다. 아래 중 하나로 다시 호출하세요:`
167
+ : `'${type}' 예제를 찾을 수 없습니다. 사용 가능한 전체 목록입니다:`;
168
+ return { content: [{ type: "text", text: `${intro}\n${listed}` }] };
92
169
  }
93
- // 단일 예제만 반환
94
- const result = [];
95
- const examples = EXAMPLES_MAP[type];
96
- if (examples) {
97
- for (const example of examples) {
98
- result.push({
99
- type: "text",
100
- text: `**${example.title}** 예제:\n` +
101
- "```jsx\n" +
102
- `${example.code}\n` +
103
- "```\n" +
104
- `${'code_props_usage' in example && example.code_props_usage ? `\n**속성 사용 예제:**\n\`\`\`jsx\n${example.code_props_usage}\n\`\`\`\n` : ''}` +
105
- `${example.description}`,
106
- });
170
+ const examples = EXAMPLES_MAP[resolvedType];
171
+ const keyword = args.keyword?.trim();
172
+ // 3. keyword 있음 → 필터링해서 전문 반환 ('*'는 전체)
173
+ if (keyword) {
174
+ if (keyword === "*") {
175
+ return { content: examples.map(exampleToContent) };
107
176
  }
177
+ const kw = keyword.toLowerCase();
178
+ // 1차: 제목/설명 매칭, 2차: 코드 내용 매칭 (기술 용어로 찍는 경우 대응)
179
+ let matched = examples.filter(ex => ex.title.toLowerCase().includes(kw) || ex.description.toLowerCase().includes(kw));
180
+ if (matched.length === 0) {
181
+ matched = examples.filter(ex => ex.code.toLowerCase().includes(kw) || (ex.code_props_usage ?? "").toLowerCase().includes(kw));
182
+ }
183
+ if (matched.length > 0) {
184
+ return { content: matched.map(exampleToContent) };
185
+ }
186
+ // 매칭 0건 → 빈 응답 금지, 목록으로 폴백
187
+ return buildListResponse(resolvedType, examples, `'${keyword}'와 일치하는 예제가 없습니다.`);
188
+ }
189
+ // 4. keyword 없음 → 예제 분량에 따라 전문/목록 분기
190
+ if (sizeOf(examples) <= FULL_RESPONSE_BYTES) {
191
+ return { content: examples.map(exampleToContent) };
108
192
  }
109
- return { content: result };
193
+ return buildListResponse(resolvedType, examples);
110
194
  }
111
195
  }
112
196
  ];
@@ -13,6 +13,7 @@ export const FileUploaderSchema = z.object({
13
13
  loadingText: z.string().optional().describe("로딩 텍스트"),
14
14
  browseText: z.string().optional().describe("찾아보기 버튼 텍스트"),
15
15
  placeholder: z.string().optional().describe("플레이스홀더 텍스트"),
16
+ dropPlaceholder: z.string().optional().describe("파일을 드래그하여 업로드 영역 위로 올렸을 때 표시되는 안내 텍스트 (string). 미지정 시 라이브러리 기본 문구 사용"),
16
17
  onChange: z.string().optional().describe("onChange 핸들러 함수명"),
17
18
  });
18
19
  // FileUploader MCP 툴 함수 - Stepper MCP 툴 구조 참고
@@ -40,6 +40,8 @@ export const SwitchSchema = z.object({
40
40
  color: z.enum(["primary", "secondary", "error", "warning", "info", "success", "default"]).optional().describe("스위치 색상"),
41
41
  size: z.enum(["small", "medium"]).optional().describe("스위치 크기 ('small' 또는 'medium')"),
42
42
  disabled: z.boolean().optional().describe("비활성화 여부"),
43
+ label: z.string().optional().describe("스위치 옆에 표시될 라벨 텍스트"),
44
+ labelPlacement: z.enum(["bottom", "top", "start", "end"]).optional().describe("라벨 위치 ('bottom' | 'top' | 'start' | 'end', 기본값: 'end'). label과 함께 사용"),
43
45
  });
44
46
  // Chip 컴포넌트 관련 스키마 정의
45
47
  export const ChipSchema = z.object({
@@ -118,7 +120,18 @@ export const TextFieldSchema = z.object({
118
120
  adornmentLabel: z.string().optional().describe("입력 필드 시작 또는 끝 부분에 표시될 텍스트 장식"),
119
121
  adornmentIconName: z.enum(Object.keys(IconNamesMap)).optional().describe(generateIconDescription({ format: 'nested' })),
120
122
  adornmentPosition: z.enum(["start", "end"]).optional().describe("장식(adornment)의 위치"),
121
- readOnly: z.boolean().optional().describe("읽기 전용 상태 여부"),
123
+ readOnly: z.boolean().optional().describe("읽기 전용 상태 여부 - 생성 코드에서 inputProps={{ readOnly: true }}로 변환되어 출력됨 (톱레벨 readOnly prop은 lib에 없음). inputProps를 직접 지정하는 경우 이 파라미터 대신 inputProps에 readOnly를 포함할 것"),
124
+ adornmentLabelColor: z.string().optional().describe("장식(adornment) 라벨 텍스트의 색상 (CSS 색상 문자열, 예: '#666666', 'red')"),
125
+ adornmentUnit: z.boolean().optional().describe("장식(adornment) 라벨을 단위(unit) 스타일로 표시할지 여부 (boolean, 기본값: false)"),
126
+ adornmentIconColor: z.string().optional().describe("장식(adornment) 아이콘의 색상 (CSS 색상 문자열, 예: '#1976d2')"),
127
+ withBtnVoice: z.boolean().optional().describe("입력 필드 우측에 음성 입력(마이크) 버튼 표시 여부 (boolean, 기본값: false)"),
128
+ voiceOn: z.boolean().optional().describe("음성 입력 활성(녹음 중) 상태 여부 (boolean). withBtnVoice와 함께 사용하며 보통 상태 변수로 제어"),
129
+ disabledVoice: z.boolean().optional().describe("음성 입력 버튼 비활성화 여부 (boolean, 기본값: false)"),
130
+ voiceSupported: z.boolean().optional().describe("브라우저의 음성 인식 지원 여부 (boolean). false면 음성 버튼이 미지원 상태로 표시됨"),
131
+ onVoice: z.string().optional().describe("음성 입력 버튼 클릭 시 호출될 함수명 (예: 'handleVoice'). () => void 형태"),
132
+ withBtnIconName: z.enum(Object.keys(IconNamesMap)).optional().describe("입력 필드 우측 확인(withBtn) 버튼에 표시할 아이콘 이름"),
133
+ withBtnDisabled: z.boolean().optional().describe("우측 확인(withBtn) 버튼 비활성화 여부 (boolean, 기본값: false)"),
134
+ onConfirm: z.string().optional().describe("우측 확인(withBtn) 버튼 클릭 시 호출될 함수명 (예: 'handleConfirm'). () => void 형태"),
122
135
  });
123
136
  // Avatar 컴포넌트 관련 스키마 정의
124
137
  export const AvatarSchema = z.object({
@@ -130,6 +143,9 @@ export const AvatarSchema = z.object({
130
143
  sx: z.string().optional().describe('MUI sx 스타일 객체(JSX/문자열)'),
131
144
  onClick: z.string().optional().describe('클릭 이벤트 핸들러'),
132
145
  children: z.string().optional().describe('아바타 내부에 표시할 텍스트/JSX (예: 이름, 아이콘 등)'),
146
+ badgeVariant: z.enum(['standard', 'dot']).optional().describe("뱃지 스타일 ('standard' | 'dot'). showBadge와 함께 사용"),
147
+ badgeOverlap: z.enum(['circular', 'rectangular']).optional().describe("뱃지가 아바타에 겹쳐지는 방식 ('circular' | 'rectangular'). showBadge와 함께 사용"),
148
+ fontSize: z.number().optional().describe('아바타 내부 텍스트의 폰트 크기 (숫자, px 단위, 예: 14)'),
133
149
  });
134
150
  // Checkbox 컴포넌트 관련 스키마 정의
135
151
  export const CheckboxSchema = z.object({
@@ -156,6 +172,9 @@ export const CollapseSchema = z.object({
156
172
  label: z.string().describe("토글 라벨 텍스트"),
157
173
  defaultExpanded: z.boolean().optional().describe("기본 확장 여부"),
158
174
  children: z.string().describe("Collapse 내부에 들어갈 JSX/텍스트"),
175
+ btnClass: z.string().optional().describe("토글 버튼에 적용할 커스텀 CSS 클래스명"),
176
+ btnToggleClass: z.string().optional().describe("확장(펼침) 상태일 때 토글 버튼에 적용할 커스텀 CSS 클래스명"),
177
+ contentsClass: z.string().optional().describe("콘텐츠 영역에 적용할 커스텀 CSS 클래스명"),
159
178
  });
160
179
  // Slider 컴포넌트 관련 스키마 정의
161
180
  export const SliderSchema = z.object({
@@ -242,6 +261,9 @@ export const TreeViewSchema = z.object({
242
261
  renderRightContent: z.string().optional().describe("각 노드 우측에 표시할 JSX 코드 문자열 (함수 형태, node 파라미터 사용)"),
243
262
  onNodeClick: z.string().optional().describe("노드 클릭 이벤트 핸들러 함수명 (예: handleNodeClick)"),
244
263
  selectedNodeId: z.string().optional().describe("선택된 노드 ID 상태 변수명 (예: selectedNode?.id)"),
264
+ searchPlaceholder: z.string().optional().describe("검색 입력 필드의 플레이스홀더 텍스트 (searchable이 true일 때 표시, 예: 'Search...')"),
265
+ className: z.string().optional().describe("트리뷰 최상위 컨테이너에 적용할 커스텀 CSS 클래스명"),
266
+ totalMuiIconName: z.string().optional().describe("최상단 Total 라벨에 표시할 Material-UI 아이콘 이름 (예: 'folder', 'settings')"),
245
267
  });
246
268
  // InsightCard 컴포넌트 관련 스키마 정의
247
269
  export const InsightCardSchema = z.object({
@@ -253,6 +275,10 @@ export const InsightCardSchema = z.object({
253
275
  subtitle: z.string().optional().describe("타일 부제목 (기본값: 'AI-powered analysis and recommendations')"),
254
276
  onDownload: z.string().optional().describe("커스텀 다운로드 콜백 함수명 (예: handleDownload)"),
255
277
  sx: z.string().optional().describe("MUI sx 스타일 객체(JSX/문자열)"),
278
+ onDownloadHandlerReady: z.string().optional().describe("다운로드 핸들러를 외부(예: 팝업 버튼)에서 사용할 수 있도록 전달받는 콜백 함수명 (예: handleDownloadReady). (downloadHandler: () => void) => void 형태"),
279
+ onToggleHandlerReady: z.string().optional().describe("접기/펼치기 토글 핸들러를 외부에서 사용할 수 있도록 전달받는 콜백 함수명 (예: handleToggleReady). (toggleHandler: () => void) => void 형태"),
280
+ hideDownloadButton: z.boolean().optional().describe("타일 헤더의 다운로드 버튼 숨김 여부 (boolean, 기본값: false)"),
281
+ hideToggleButton: z.boolean().optional().describe("타일 헤더의 접기/펼치기 버튼 숨김 여부 (boolean, 기본값: false)"),
256
282
  });
257
283
  // InsightContent 컴포넌트 관련 스키마 정의 (타일 없이 콘텐츠만)
258
284
  export const InsightContentSchema = z.object({
@@ -412,6 +438,10 @@ export function createFormsComponent() {
412
438
  props.push(`size="${args.size}"`);
413
439
  if (args.disabled)
414
440
  props.push(`disabled`);
441
+ if (args.label)
442
+ props.push(`label="${args.label}"`);
443
+ if (args.labelPlacement)
444
+ props.push(`labelPlacement="${args.labelPlacement}"`);
415
445
  const code = `<OpsnowCommonSwitch ${props.join(' ')} />`;
416
446
  return {
417
447
  content: [
@@ -509,8 +539,31 @@ export function createFormsComponent() {
509
539
  props.push(`adornmentIconName=\"${args.adornmentIconName}\"`);
510
540
  if (args.adornmentPosition)
511
541
  props.push(`adornmentPosition=\"${args.adornmentPosition}\"`);
512
- if (args.readOnly)
513
- props.push(`readOnly`);
542
+ // readOnly는 톱레벨 prop이 아니라 inputProps 경로로만 동작 (MUI 7: rest prop은 FormControl로 가서 input에 미도달)
543
+ if (args.readOnly && !args.inputProps)
544
+ props.push(`inputProps={{ readOnly: true }}`);
545
+ if (args.adornmentLabelColor)
546
+ props.push(`adornmentLabelColor=\"${args.adornmentLabelColor}\"`);
547
+ if (args.adornmentUnit)
548
+ props.push(`adornmentUnit`);
549
+ if (args.adornmentIconColor)
550
+ props.push(`adornmentIconColor=\"${args.adornmentIconColor}\"`);
551
+ if (args.withBtnVoice)
552
+ props.push(`withBtnVoice`);
553
+ if (args.voiceOn !== undefined)
554
+ props.push(`voiceOn={${args.voiceOn}}`);
555
+ if (args.disabledVoice)
556
+ props.push(`disabledVoice`);
557
+ if (args.voiceSupported !== undefined)
558
+ props.push(`voiceSupported={${args.voiceSupported}}`);
559
+ if (args.onVoice)
560
+ props.push(`onVoice={${args.onVoice}}`);
561
+ if (args.withBtnIconName)
562
+ props.push(`withBtnIconName=\"${args.withBtnIconName}\"`);
563
+ if (args.withBtnDisabled)
564
+ props.push(`withBtnDisabled`);
565
+ if (args.onConfirm)
566
+ props.push(`onConfirm={${args.onConfirm}}`);
514
567
  // inputProps, InputProps 멀티라인/JSX 대응
515
568
  if (args.inputProps) {
516
569
  if (args.inputProps.trim().startsWith('{') && args.inputProps.includes('\n')) {
@@ -554,6 +607,12 @@ export function createFormsComponent() {
554
607
  props.push(`variant=\"${args.variant}\"`);
555
608
  if (args.showBadge)
556
609
  props.push(`showBadge`);
610
+ if (args.badgeVariant)
611
+ props.push(`badgeVariant=\"${args.badgeVariant}\"`);
612
+ if (args.badgeOverlap)
613
+ props.push(`badgeOverlap=\"${args.badgeOverlap}\"`);
614
+ if (args.fontSize !== undefined)
615
+ props.push(`fontSize={${args.fontSize}}`);
557
616
  if (args.sx)
558
617
  props.push(`sx={${args.sx}}`);
559
618
  if (args.onClick)
@@ -673,6 +732,12 @@ export function createFormsComponent() {
673
732
  props.push(`label=\"${args.label}\"`);
674
733
  if (args.defaultExpanded)
675
734
  props.push(`defaultExpanded`);
735
+ if (args.btnClass)
736
+ props.push(`btnClass=\"${args.btnClass}\"`);
737
+ if (args.btnToggleClass)
738
+ props.push(`btnToggleClass=\"${args.btnToggleClass}\"`);
739
+ if (args.contentsClass)
740
+ props.push(`contentsClass=\"${args.contentsClass}\"`);
676
741
  let code = '';
677
742
  if (args.children && args.children.trim().startsWith('<')) {
678
743
  code = `<OpsnowCommonCollapse ${props.join(' ')}>` + `\n ${args.children}\n</OpsnowCommonCollapse>`;
@@ -918,6 +983,12 @@ export function createFormsComponent() {
918
983
  props.push(`onNodeClick={${args.onNodeClick}}`);
919
984
  if (args.selectedNodeId)
920
985
  props.push(`selectedNodeId={${args.selectedNodeId}}`);
986
+ if (args.searchPlaceholder)
987
+ props.push(`searchPlaceholder=\"${args.searchPlaceholder}\"`);
988
+ if (args.className)
989
+ props.push(`className=\"${args.className}\"`);
990
+ if (args.totalMuiIconName)
991
+ props.push(`totalMuiIconName=\"${args.totalMuiIconName}\"`);
921
992
  const code = `<OpsnowCommonTreeView\n ${props.join('\n ')}\n/>`;
922
993
  return {
923
994
  content: [
@@ -957,6 +1028,14 @@ export function createFormsComponent() {
957
1028
  props.push(`onDownload={${args.onDownload}}`);
958
1029
  if (args.sx)
959
1030
  props.push(`sx={${args.sx}}`);
1031
+ if (args.onDownloadHandlerReady)
1032
+ props.push(`onDownloadHandlerReady={${args.onDownloadHandlerReady}}`);
1033
+ if (args.onToggleHandlerReady)
1034
+ props.push(`onToggleHandlerReady={${args.onToggleHandlerReady}}`);
1035
+ if (args.hideDownloadButton)
1036
+ props.push(`hideDownloadButton`);
1037
+ if (args.hideToggleButton)
1038
+ props.push(`hideToggleButton`);
960
1039
  const code = `<OpsnowCommonInsightCard\n ${props.join('\n ')}\n/>`;
961
1040
  return {
962
1041
  content: [
@@ -4,9 +4,8 @@ export const IconSchema = z.object({
4
4
  iconName: z.enum(Object.keys(IconNamesMap)).describe(generateIconDescription({ format: 'nested' })),
5
5
  size: z.number().optional().default(24).describe("아이콘 크기(px)"),
6
6
  color: z.string().optional().describe("아이콘 색상"),
7
- className: z.string().optional().describe("추가 CSS 클래스"),
8
- onClick: z.string().optional().describe("클릭 이벤트 핸들러 함수명"),
9
- disabled: z.boolean().optional().default(false).describe("비활성화 여부")
7
+ className: z.string().optional().describe("추가 CSS 클래스 (HTMLAttributes 상속 + rest 스프레드로 span에 적용. 기본 클래스 'opsnow-common-icon'을 덮어쓰므로 주의)"),
8
+ onClick: z.string().optional().describe("클릭 이벤트 핸들러 함수명 (HTMLAttributes 상속으로 span에 적용됨)"),
10
9
  });
11
10
  export const MuiIconSchema = z.object({
12
11
  theme: z.string().optional().describe("테마명"),
@@ -52,8 +51,6 @@ export function createIconsComponent() {
52
51
  props.push(`className="${args.className}"`);
53
52
  if (args.onClick)
54
53
  props.push(`onClick={${args.onClick}}`);
55
- if (args.disabled)
56
- props.push(`disabled`);
57
54
  const code = `<OpsnowCommonIcon ${props.join(" ")} />`;
58
55
  return {
59
56
  content: [
@@ -38,6 +38,7 @@ export const NotificationSchema = z.object({
38
38
  .default(false)
39
39
  .describe("배경 흰색 여부 - severity : info 일때만 사용(true시 사용 예시 : bgWhite (bgWhite={true}처럼 쓰지 않음.)"),
40
40
  action: z.string().optional().describe("액션 버튼 코드 - 예시: <OpsnowCommonButton label='확인' />"),
41
+ customActionBtn: z.string().optional().describe("커스텀 액션 버튼 JSX 코드 문자열 (React.ReactNode) - 예시: <OpsnowCommonButton label='확인' onClick={handleAction} />. action + isCustom 조합 대신 직접 커스텀 버튼을 지정할 때 사용"),
41
42
  isCustom: z
42
43
  .boolean()
43
44
  .default(false)
@@ -76,16 +77,17 @@ export function createNotificationComponent() {
76
77
  props.push("bgWhite");
77
78
  }
78
79
  if (args.action) {
79
- if (args.isCustom && args.closable && args.placement === `card`) {
80
- props.push(`customActionBtn={${args.action}}`);
81
- }
82
- else if (args.isCustom && !args.placement) {
80
+ // isCustom이면 placement와 무관하게 customActionBtn으로 출력 (커스텀 버튼 분기 스위치)
81
+ if (args.isCustom) {
83
82
  props.push(`customActionBtn={${args.action}}`);
84
83
  }
85
84
  else {
86
85
  props.push(`action={${args.action}}`);
87
86
  }
88
87
  }
88
+ if (args.customActionBtn && !args.action) {
89
+ props.push(`customActionBtn={${args.customActionBtn}}`);
90
+ }
89
91
  if (args.closable) {
90
92
  props.push(`onClose={() => {\n // 닫힐때 실행할 함수\n // 예시: setOpen(${!args.closable}})\n}}`);
91
93
  }
@@ -35,14 +35,12 @@ export const PaginationSchema = z.object({
35
35
  .string()
36
36
  .optional()
37
37
  .describe("pageNum이 이전페이지로 이동할때 핸들러 함수 코드. 함수 내부 코드(문자열)로 전달하세요. 예시: console.log('이전 페이지로 이동, 현재 페이지:', pageNum)"),
38
- pagingFromServer: z
39
- .boolean()
40
- .default(false)
41
- .describe("서버에서 페이지네이션 정보를 받아오는 여부"),
42
38
  onPageChange: z
43
39
  .string()
44
40
  .optional()
45
41
  .describe("pageData가 변경 되었을때 사용하는 핸들러 함수 코드. 함수 내부 코드(문자열)로 전달하세요. 예시: setCurrentPageData(pageData); console.log('Current Page Data:', pageData)"),
42
+ count: z.number().optional().describe("전체 항목(행) 수 (숫자). 라이브러리가 페이지 수 계산에 사용하는 필수 prop. list를 전달하면 list.length로 자동 설정되므로 생략 가능하며, 서버 페이징 등 list 없이 사용할 때 직접 지정"),
43
+ locale: z.string().optional().describe("표시 언어 로케일 문자열 (예: 'ko', 'en'). isLocale이 true(기본값)면 i18n.getLocale()이 자동 적용되므로, isLocale을 false로 두고 특정 언어로 고정할 때만 지정"),
46
44
  });
47
45
  // Pagination 컴포넌트 함수 - 배열 반환
48
46
  export function createPaginationComponent() {
@@ -103,8 +101,13 @@ export function createPaginationComponent() {
103
101
  if (args.size) {
104
102
  props.push(`size="${args.size}"`);
105
103
  }
106
- if (args.pagingFromServer) {
107
- props.push(`pagingFromServer={${args.pagingFromServer}}`);
104
+ // count: list가 있으면 위에서 list.length로 이미 push되므로 중복 방지
105
+ if (args.count !== undefined && !(args.list && Array.isArray(args.list))) {
106
+ props.push(`count={${args.count}}`);
107
+ }
108
+ // locale: isLocale이 true면 위에서 i18n.getLocale()로 이미 push되므로 중복 방지
109
+ if (args.locale && !args.isLocale) {
110
+ props.push(`locale="${args.locale}"`);
108
111
  }
109
112
  // rowsPerPage 관련 props
110
113
  if (args.rowsPerPageOptions) {
@@ -3,10 +3,7 @@ import { z } from "zod";
3
3
  export const PopupSchema = z.object({
4
4
  title: z.string().describe("팝업 제목"),
5
5
  visible: z.boolean().optional().default(false).describe("팝업 표시 여부"),
6
- width: z.number().optional().describe("팝업 너비 (px)"),
7
- height: z.number().optional().describe("팝업 높이 (px)"),
8
- closable: z.boolean().optional().default(true).describe("닫기 버튼 표시 여부"),
9
- maskClosable: z.boolean().optional().default(true).describe("마스크 클릭으로 닫기 가능 여부"),
6
+ closable: z.boolean().optional().default(true).describe("닫기 버튼 표시 여부 - false 시 생성 코드에서 hideHeaderClose로 변환 출력됨 (lib에 closable prop 없음). 크기 조절은 width/height 대신 size(sm/md/lg) 사용"),
10
7
  disableEscapeKeyDown: z.boolean().optional().default(false).describe('ESC 키 입력으로 닫기 방지 여부'),
11
8
  type: z.enum(['wing', 'standard', 'bottom']).optional().default('standard').describe("팝업 타입"),
12
9
  size: z.enum(['sm', 'md', 'lg']).optional().describe('팝업 크기 (sm, md, lg)'),
@@ -14,6 +11,25 @@ export const PopupSchema = z.object({
14
11
  color: z.string().describe('확인 버튼 색상'),
15
12
  variant: z.string().describe('확인 버튼 변형'),
16
13
  }).optional().describe('확인 버튼 설정'),
14
+ onConfirm: z.string().optional().describe('확인(Confirm) 버튼 클릭 시 실행할 함수명 (() => void 타입) - 예시: handleConfirm'),
15
+ onAction: z.string().optional().describe('액션(Action) 버튼 클릭 시 실행할 함수명 (() => void 타입) - 예시: handleAction'),
16
+ useSpecialTitle: z.boolean().optional().describe('특수 타이틀 스타일 사용 여부 (boolean, 기본값 없음)'),
17
+ useFooterCancelBtn: z.boolean().optional().describe('푸터에 취소 버튼 표시 여부 (boolean)'),
18
+ useFooterActionBtn: z.boolean().optional().describe('푸터에 액션 버튼 표시 여부 (boolean)'),
19
+ cancelButton: z.object({
20
+ text: z.string().optional().describe('취소 버튼 텍스트'),
21
+ variant: z.enum(['text', 'outlined', 'contained']).optional().describe('취소 버튼 변형 (text | outlined | contained)'),
22
+ disabled: z.boolean().optional().describe('취소 버튼 비활성화 여부'),
23
+ color: z.string().optional().describe('취소 버튼 색상'),
24
+ }).optional().describe('취소 버튼 설정 객체 - { text, variant, disabled, color }'),
25
+ actionButton: z.object({
26
+ text: z.string().optional().describe('액션 버튼 텍스트'),
27
+ variant: z.enum(['text', 'outlined', 'contained']).optional().describe('액션 버튼 변형 (text | outlined | contained)'),
28
+ disabled: z.boolean().optional().describe('액션 버튼 비활성화 여부'),
29
+ color: z.string().optional().describe('액션 버튼 색상'),
30
+ }).optional().describe('액션 버튼 설정 객체 - { text, variant, disabled, color }'),
31
+ hideHeaderClose: z.boolean().optional().describe('헤더의 닫기(X) 버튼 숨김 여부 (boolean)'),
32
+ hideFooter: z.boolean().optional().describe('푸터 전체 숨김 여부 (boolean)'),
17
33
  useCustomHeader: z.boolean().optional().default(false).describe('커스텀 헤더 사용 여부'),
18
34
  useCustomFooter: z.boolean().optional().default(false).describe('커스텀 푸터 사용 여부'),
19
35
  headerContent: z.any().optional().describe('커스텀 헤더 내용 (JSX)'),
@@ -34,20 +50,25 @@ export function createPopupComponent() {
34
50
  parameters: PopupSchema,
35
51
  handler: async (args) => {
36
52
  // <OpsnowCommonPopup ...> 코드만 동적으로 생성
37
- const { title = 'Example Popup', visible = false, width, height, closable = true, maskClosable = true, disableEscapeKeyDown = false, size, confirmButton, useCustomHeader = false, useCustomFooter = false, headerContent, footerContent, type = 'standard', } = args;
53
+ const { title = 'Example Popup', visible = false, closable = true, disableEscapeKeyDown = false, size, confirmButton, onConfirm, onAction, useSpecialTitle, useFooterCancelBtn, useFooterActionBtn, cancelButton, actionButton, hideHeaderClose, hideFooter, useCustomHeader = false, useCustomFooter = false, headerContent, footerContent, type = 'standard', } = args;
38
54
  // prop 문자열 생성
39
55
  const propStr = [
40
56
  `title=\"${title}\"`,
41
57
  `open={${visible}}`,
42
58
  `onClose={handleClose}`,
43
- width ? `width={${width}}` : '',
44
- height ? `height={${height}}` : '',
45
- closable === false ? 'closable={false}' : '',
46
- maskClosable === false ? 'maskClosable={false}' : '',
47
59
  disableEscapeKeyDown ? 'disableEscapeKeyDown' : '',
48
60
  `type=\"${type}\"`,
49
61
  `size=\"${size}\"`,
50
62
  confirmButton ? `confirmButton={${JSON.stringify(confirmButton)}}` : '',
63
+ onConfirm ? `onConfirm={${onConfirm}}` : '',
64
+ onAction ? `onAction={${onAction}}` : '',
65
+ useSpecialTitle ? 'useSpecialTitle' : '',
66
+ useFooterCancelBtn ? 'useFooterCancelBtn' : '',
67
+ useFooterActionBtn ? 'useFooterActionBtn' : '',
68
+ cancelButton ? `cancelButton={${JSON.stringify(cancelButton)}}` : '',
69
+ actionButton ? `actionButton={${JSON.stringify(actionButton)}}` : '',
70
+ (hideHeaderClose || closable === false) ? 'hideHeaderClose' : '',
71
+ hideFooter ? 'hideFooter' : '',
51
72
  useCustomHeader ? 'useCustomHeader' : '',
52
73
  useCustomFooter ? 'useCustomFooter' : '',
53
74
  headerContent ? `headerContent={${headerContent}}` : '',
@@ -12,8 +12,7 @@ export const SelectSchema = z.object({
12
12
  variant: z.enum(["standard", "outlined", "filled", "noLine"]).optional().describe("셀렉트 variant"),
13
13
  size: z.enum(["small", "medium"]).optional().describe("셀렉트 크기"),
14
14
  error: z.boolean().optional().default(false).describe("에러 상태"),
15
- disabled: z.boolean().optional().default(false).describe("비활성화 여부"),
16
- searchable: z.boolean().optional().default(false).describe("검색 가능 여부")
15
+ disabled: z.boolean().optional().default(false).describe("비활성화 여부")
17
16
  });
18
17
  // Autocomplete Select 컴포넌트 관련 스키마 정의
19
18
  export const AutocompleteSelectSchema = z.object({
@@ -32,7 +31,18 @@ export const AutocompleteSelectSchema = z.object({
32
31
  loading: z.boolean().optional().default(false).describe("로딩 상태 표시 여부"),
33
32
  label: z.string().optional().describe("라벨 텍스트"),
34
33
  placeholder: z.string().optional().describe("플레이스홀더 텍스트"),
35
- disabled: z.boolean().optional().default(false).describe("전체 비활성화 여부")
34
+ disabled: z.boolean().optional().default(false).describe("전체 비활성화 여부"),
35
+ textFieldProps: z.object({
36
+ error: z.boolean().optional().describe("에러 상태 표시 여부"),
37
+ helperText: z.string().optional().describe("입력창 하단 도움말 텍스트"),
38
+ required: z.boolean().optional().describe("필수 입력 표시 여부"),
39
+ size: z.enum(["small", "medium"]).optional().describe("입력창 크기"),
40
+ variant: z.enum(["standard", "outlined", "filled"]).optional().describe("입력창 variant")
41
+ }).passthrough().optional().describe("내부 입력창(OpsnowCommonTextField)에 전달할 props 객체. error/helperText/required/size/variant 등 MUI TextField props 사용 가능. 기본값 없음(미지정 시 기본 TextField 사용)"),
42
+ groupBy: z.string().optional().describe("옵션 그룹핑 함수명 또는 인라인 화살표 함수. (option) => string 형태로 각 옵션이 속할 그룹 라벨을 반환 (예: (option) => option.group). 기본값 없음(그룹핑 안 함)"),
43
+ fitDropdownWidth: z.boolean().optional().describe("드롭다운 너비를 입력 필드 폭에 고정하고 긴 라벨은 줄바꿈 (boolean, 기본 false). false(기본)면 드롭다운이 옵션 라벨 폭에 맞게 확장되며, 화면 오른쪽 공간이 부족하면 잘리는 대신 왼쪽으로 이동. 좁은 레이아웃에서 드롭다운이 넓어지면 안 될 때 사용"),
44
+ dropdownMaxWidth: z.number().optional().describe("확장 모드(fitDropdownWidth=false)에서 드롭다운 최대 너비(px, number). 미지정(기본) 시 고정 상한 없이 뷰포트 폭까지 확장하고 그보다 긴 라벨만 줄바꿈 (예: 360)"),
45
+ onChange: z.string().optional().describe("선택 값 변경 콜백 함수명. (value, event?, reason?, details?) => void 형태이며 value는 multiple 여부에 따라 배열 또는 단일 아이템(null 가능). 기본값 없음(미지정 시 생성 코드에 onChange={setValue} 사용)")
36
46
  });
37
47
  // Select 컴포넌트 함수 - 배열 반환
38
48
  export function createSelectComponent() {
@@ -42,7 +52,7 @@ export function createSelectComponent() {
42
52
  description: `기본 셀렉트 컴포넌트 예제입니다.
43
53
 
44
54
  - items: 옵션 배열 (value, label, disabled 등 지원)
45
- - multiple, placeholder, variant, size, error, disabled, searchable 등 다양한 props 지원
55
+ - multiple, placeholder, variant, size, error, disabled 등 다양한 props 지원
46
56
  - value/onChange는 단일/다중 선택에 따라 배열 또는 단일 값
47
57
 
48
58
  **import:**
@@ -69,8 +79,6 @@ export function createSelectComponent() {
69
79
  props.push(`error`);
70
80
  if (args.disabled)
71
81
  props.push(`disabled`);
72
- if (args.searchable)
73
- props.push(`searchable`);
74
82
  props.push(`onChange={e => setValue(e.target.value)}`);
75
83
  const code = `<OpsnowCommonSelect ${props.join(' ')} />`;
76
84
  return {
@@ -119,7 +127,15 @@ export function createSelectComponent() {
119
127
  props.push(`placeholder=\"${args.placeholder}\"`);
120
128
  if (args.disabled)
121
129
  props.push(`disabled`);
122
- props.push(`onChange={setValue}`);
130
+ if (args.textFieldProps)
131
+ props.push(`textFieldProps={${JSON.stringify(args.textFieldProps)}}`);
132
+ if (args.groupBy)
133
+ props.push(`groupBy={${args.groupBy}}`);
134
+ if (args.fitDropdownWidth)
135
+ props.push(`fitDropdownWidth`);
136
+ if (args.dropdownMaxWidth)
137
+ props.push(`dropdownMaxWidth={${args.dropdownMaxWidth}}`);
138
+ props.push(`onChange={${args.onChange ?? "setValue"}}`);
123
139
  const code = `<OpsnowCommonAutocomplete ${props.join(' ')} />`;
124
140
  return {
125
141
  content: [