@opsnow-mcp/opsnow-mcp-common-ui-server 1.0.40 → 1.0.41

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 (37) hide show
  1. package/build/components/all-component-tools.js +2 -0
  2. package/build/components/data/icon-names.generated.js +341 -0
  3. package/build/components/data/icon-names.js +32 -16
  4. package/build/components/examples/opsnow-common-chart-examples-data.js +73 -78
  5. package/build/components/examples/opsnow-common-forms-examples-data.js +1 -11
  6. package/build/components/examples/opsnow-common-grid-examples-data.js +13 -20
  7. package/build/components/examples/opsnow-common-layout-examples-data.js +12 -13
  8. package/build/components/examples/opsnow-common-popup-examples-data.js +14 -28
  9. package/build/components/examples/opsnow-common-progress-examples-data.js +0 -5
  10. package/build/components/examples/opsnow-common-stepper-examples-data.js +2 -3
  11. package/build/components/examples/opsnow-common-storage-examples-data.js +23 -4
  12. package/build/components/examples/opsnow-common-tooltip-examples-data.js +1 -21
  13. package/build/components/opsnow-common-calendar.js +36 -15
  14. package/build/components/opsnow-common-chart.js +213 -132
  15. package/build/components/opsnow-common-coach-mark.js +3 -2
  16. package/build/components/opsnow-common-data-status.js +17 -11
  17. package/build/components/opsnow-common-dropdown.js +80 -0
  18. package/build/components/opsnow-common-examples.js +4 -2
  19. package/build/components/opsnow-common-file-upload.js +4 -1
  20. package/build/components/opsnow-common-forms.js +73 -36
  21. package/build/components/opsnow-common-grid.js +57 -29
  22. package/build/components/opsnow-common-icons.js +23 -39
  23. package/build/components/opsnow-common-jsx-helper.js +36 -0
  24. package/build/components/opsnow-common-layout.js +66 -25
  25. package/build/components/opsnow-common-notification.js +3 -2
  26. package/build/components/opsnow-common-pagination.js +4 -4
  27. package/build/components/opsnow-common-popup.js +12 -11
  28. package/build/components/opsnow-common-progress.js +10 -16
  29. package/build/components/opsnow-common-select.js +26 -13
  30. package/build/components/opsnow-common-stepper.js +30 -7
  31. package/build/components/opsnow-common-tab.js +5 -3
  32. package/build/components/opsnow-common-toast-popup.js +1 -0
  33. package/build/components/opsnow-common-tooltip.js +44 -46
  34. package/build/components/opsnow-common-typography.js +11 -5
  35. package/build/components/opsnow-common-validate-props.js +0 -0
  36. package/build/index.js +2 -0
  37. package/package.json +2 -1
@@ -492,9 +492,8 @@ export const DataGridExamples = [
492
492
  defaultColDef={defaultColDef}
493
493
  rowData={rowData}
494
494
  status={status} // 상태를 전달
495
- gridOptions={gridOptions}
495
+ gridOptions={{ ...gridOptions, pinnedTopRowData }} // pinned row는 gridOptions로 전달
496
496
  pagination={true}
497
- pinnedTopRowData={pinnedTopRowData} // pinned row 전달
498
497
  langCd={i18n.getLocale()}
499
498
  />`
500
499
  },
@@ -1271,8 +1270,6 @@ export const DataGridExamples = [
1271
1270
  // 달력 컴포넌트를 searchAddon에 전달
1272
1271
  searchAddon={
1273
1272
  <OpsnowCommonDatePicker
1274
- clearable={true}
1275
- placeholderText="날짜를 선택해주세요"
1276
1273
  type="date"
1277
1274
  value={time}
1278
1275
  onChange={onChangeMonth}
@@ -2309,7 +2306,7 @@ export const DataGridExamples = [
2309
2306
  animateRows: true,
2310
2307
  rowSelection: { mode: 'singleRow', checkboxes: false, enableClickSelection: true },
2311
2308
  tooltipShowDelay: 100,
2312
- domLayout: 'autoHeight', // 그리드 전체 레이아웃 자동 높이
2309
+ domLayout: 'autoHeight', // 콘텐츠 높이에 맞춤, 최대 maxGridHeight(기본 600px)까지, 넘치면 내부 스크롤
2313
2310
  suppressCellFocus: true,
2314
2311
  }
2315
2312
 
@@ -2326,15 +2323,12 @@ export const DataGridExamples = [
2326
2323
  />`
2327
2324
  },
2328
2325
  {
2329
- title: 'Auto Row Height (행 수 분기 + 스크롤)',
2330
- description: 'autoHeight는 모든 행을 펼쳐 그리드가 무한정 길어집니다. 행 수가 가변적이거나 많아질 수 있는 경우(목록/검색 결과 등) 행 수 기준으로 분기해야 합니다: 10행 이하면 autoHeight로 콘텐츠에 맞게 확장하고, 10행 초과면 고정 높이 + 내부 스크롤로 전환합니다. autoHeight 셀은 행마다 높이가 달라 정확한 "10행 높이"를 픽셀로 맞출 수 없으므로 고정 픽셀값(예: 600)을 사용합니다.',
2326
+ title: 'Auto Row Height (최대 높이 + 스크롤)',
2327
+ description: 'domLayout: \'autoHeight\'는 행이 적으면 콘텐츠 높이에 맞추고, maxGridHeight(기본 600px, 헤더 42px 포함)를 넘으면 본문에 세로 스크롤이 자동으로 생깁니다(grid ≥2.0.6). 행 수로 domLayout을 분기하지 마세요 — domLayout: \'normal\'을 주면 그리드 높이가 0이 되어 화면에서 사라집니다. 상한은 maxGridHeight로 바꾸고, 모든 행을 펼치려면 unlimitedHeight를 씁니다. 고정 높이가 필요하면 domLayout 키를 빼고 gridHeight를 지정합니다.',
2331
2328
  code_props_usage: `
2332
2329
  import { useCommonComponents, useGlobalContext } from '@opsnow-common/opsnow-finops-common-ui-loader'
2333
2330
  import i18n from '@opsnow-common/opsnow-finops-common-i18n'
2334
2331
 
2335
- const ROW_LIMIT = 10 // 이 행 수를 넘으면 고정 높이 + 스크롤
2336
- const SCROLL_HEIGHT = 600 // 초과 시 고정 높이(px)
2337
-
2338
2332
  const columnDefs = [
2339
2333
  { headerName: 'ID', field: 'id', flex: 1, minWidth: 80 },
2340
2334
  {
@@ -2355,10 +2349,10 @@ export const DataGridExamples = [
2355
2349
  autoHeight: true, // 셀 높이 자동 조절
2356
2350
  }
2357
2351
 
2358
- const { OpsnowCommonDataGrid } = useCommonComponents()
2352
+ const { OpsnowCommonDataGrid, OpsnowCommonButton } = useCommonComponents()
2359
2353
  const { CommonConst } = useGlobalContext()
2360
2354
 
2361
- // 4행 데이터 (10행 이하 → autoHeight, 스크롤 없음)
2355
+ // 4행 데이터 (최대 높이 미만 → 콘텐츠 높이, 스크롤 없음)
2362
2356
  const fewRowData = [
2363
2357
  { id: 1, description: 'This is a very long description text that will wrap to multiple lines when the column width is not sufficient to display all content in a single line.', status: 'Active' },
2364
2358
  { id: 2, description: 'Short text', status: 'Inactive' },
@@ -2366,8 +2360,8 @@ export const DataGridExamples = [
2366
2360
  { id: 4, description: 'Another example of long text content that needs multiple lines', status: 'Active' },
2367
2361
  ]
2368
2362
 
2369
- // 12행 데이터 (10행 초과 → 고정 높이 600px + 내부 스크롤)
2370
- const manyRowData = Array.from({ length: 12 }, (_, i) => ({
2363
+ // 30행 데이터 (최대 높이 초과 → 600px에서 잘리고 내부 스크롤)
2364
+ const manyRowData = Array.from({ length: 30 }, (_, i) => ({
2371
2365
  id: i + 1,
2372
2366
  description: 'Row ' + (i + 1) + ': long description text that wraps to multiple lines when the column is narrow, demonstrating autoHeight with scroll.',
2373
2367
  status: i % 2 === 0 ? 'Active' : 'Inactive',
@@ -2389,20 +2383,19 @@ export const DataGridExamples = [
2389
2383
  fetchData()
2390
2384
  }, [])
2391
2385
 
2392
- // 행 수 기준 분기 - 핵심
2393
- const isScroll = rowData.length > ROW_LIMIT
2386
+ // 행 수와 무관하게 autoHeight 하나만 — 높이 상한은 그리드가 처리
2394
2387
  const gridOptions = {
2395
2388
  animateRows: true,
2396
2389
  rowSelection: { mode: 'singleRow', checkboxes: false, enableClickSelection: true },
2397
2390
  tooltipShowDelay: 100,
2398
- domLayout: isScroll ? 'normal' : 'autoHeight', // 10행 이하: 콘텐츠 맞춤 / 초과: 고정+스크롤
2391
+ domLayout: 'autoHeight',
2399
2392
  suppressCellFocus: true,
2400
2393
  }
2401
2394
  `,
2402
2395
  code: `<>
2403
- <div style={{ marginBottom: '8px', display: 'flex', gap: '8px' }}>
2396
+ <div style={{ marginBottom: 'var(--opsnow-spacing-filter-label-gap)', display: 'flex', gap: 'var(--opsnow-spacing-filter-label-gap)' }}>
2404
2397
  <OpsnowCommonButton label="4행 (스크롤 X)" size="large" variant="outlined" color="secondary" onClick={() => setRowData(fewRowData)} />
2405
- <OpsnowCommonButton label="12행 (스크롤 O)" size="large" variant="outlined" color="secondary" onClick={() => setRowData(manyRowData)} />
2398
+ <OpsnowCommonButton label="30행 (스크롤 O)" size="large" variant="outlined" color="secondary" onClick={() => setRowData(manyRowData)} />
2406
2399
  </div>
2407
2400
  <OpsnowCommonDataGrid
2408
2401
  columnDefs={columnDefs}
@@ -2410,7 +2403,7 @@ export const DataGridExamples = [
2410
2403
  rowData={rowData}
2411
2404
  status={status}
2412
2405
  gridOptions={gridOptions}
2413
- gridHeight={isScroll ? SCROLL_HEIGHT : undefined}
2406
+ maxGridHeight={600} // 기본값과 같음. 상한을 바꾸려면 이 값만 조정 (모두 펼치려면 unlimitedHeight)
2414
2407
  langCd={i18n.getLocale()}
2415
2408
  />
2416
2409
  </>`
@@ -2,10 +2,10 @@
2
2
  export const GridLayoutExamples = [
3
3
  {
4
4
  title: '4열 그리드 레이아웃',
5
- description: 'Wrap="nowrap"와 간격 토큰(pagePadding·sectionGap·cardPadding·cardGap)을 적용한 4열 레이아웃 예제입니다. 색상 토큰처럼 theme.commonSpacing 을 참조하며 px 하드코딩하지 않습니다.',
5
+ description: 'MUI 9 Grid size 문법과 간격 토큰(pagePadding·sectionGap·cardPadding·cardGap)을 적용한 4열 레이아웃 예제입니다. 섹션 간격은 Grid spacing에 토큰 CSS 변수로 지정합니다(sx gap은 size 폭 계산에 들어가지 않아 줄바꿈이 깨짐). 색상 토큰처럼 theme.commonSpacing 을 참조하며 px 하드코딩하지 않습니다.',
6
6
  code: `
7
- <Grid container wrap="nowrap" spacing={0} sx={theme => ({ padding: theme.commonSpacing.pagePadding, gap: theme.commonSpacing.sectionGap })}>
8
- <Grid item xs={12} sm={6} md={3} key={0}>
7
+ <Grid container spacing="var(--opsnow-spacing-section-gap)" sx={theme => ({ padding: theme.commonSpacing.pagePadding })}>
8
+ <Grid size={{ xs: 12, sm: 6, md: 3 }} key={0}>
9
9
  <Box sx={theme => ({
10
10
  border: '1px solid #ddd',
11
11
  borderRadius: '12px',
@@ -17,8 +17,7 @@ export const GridLayoutExamples = [
17
17
  })}>
18
18
  </Box>
19
19
  </Grid>
20
- <!-- 총 4개 아이템 반복 -->
21
- <!-- ... -->
20
+ {/* 총 4개 아이템 반복 (sm: 2열, md: 4열) */}
22
21
  </Grid>
23
22
  `,
24
23
  },
@@ -26,12 +25,12 @@ export const GridLayoutExamples = [
26
25
  title: '2열 그리드 레이아웃',
27
26
  description: '2개의 박스를 한 줄에 배치하는 예제입니다.',
28
27
  code: `
29
- <Grid container wrap="nowrap" spacing={0} sx={theme => ({ padding: theme.commonSpacing.pagePadding, gap: theme.commonSpacing.sectionGap })}>
30
- <Grid item xs={12} md={6} key={0}>
28
+ <Grid container spacing="var(--opsnow-spacing-section-gap)" sx={theme => ({ padding: theme.commonSpacing.pagePadding })}>
29
+ <Grid size={{ xs: 12, md: 6 }} key={0}>
31
30
  <Box sx={{ /* 동일한 스타일 */ }}>
32
31
  </Box>
33
32
  </Grid>
34
- <Grid item xs={12} md={6} key={1}>
33
+ <Grid size={{ xs: 12, md: 6 }} key={1}>
35
34
  <Box sx={{ /* 동일한 스타일 */ }}>
36
35
  </Box>
37
36
  </Grid>
@@ -42,16 +41,16 @@ export const GridLayoutExamples = [
42
41
  title: '3열 그리드 레이아웃',
43
42
  description: '3개의 박스를 한 줄에 배치하는 예제입니다.',
44
43
  code: `
45
- <Grid container wrap="nowrap" spacing={0} sx={theme => ({ padding: theme.commonSpacing.pagePadding, gap: theme.commonSpacing.sectionGap })}>
46
- <Grid item xs={12} md={4} key={0}>
44
+ <Grid container spacing="var(--opsnow-spacing-section-gap)" sx={theme => ({ padding: theme.commonSpacing.pagePadding })}>
45
+ <Grid size={{ xs: 12, md: 4 }} key={0}>
47
46
  <Box sx={{ /* 동일한 스타일 */ }}>
48
47
  </Box>
49
48
  </Grid>
50
- <Grid item xs={12} md={4} key={1}>
49
+ <Grid size={{ xs: 12, md: 4 }} key={1}>
51
50
  <Box sx={{ /* 동일한 스타일 */ }}>
52
51
  </Box>
53
52
  </Grid>
54
- <Grid item xs={12} md={4} key={2}>
53
+ <Grid size={{ xs: 12, md: 4 }} key={2}>
55
54
  <Box sx={{ /* 동일한 스타일 */ }}>
56
55
  </Box>
57
56
  </Grid>
@@ -61,7 +60,7 @@ export const GridLayoutExamples = [
61
60
  {
62
61
  title: 'Tile with Checkbox UI',
63
62
  description: 'Tile 내부에 체크박스와 텍스트가 포함된 2열 레이아웃 예제입니다.',
64
- code: `<Grid container spacing={3} sx={{ marginTop: '20px' }}><Grid item xs={12} md={6} sx={{ display: 'flex' }}><OpsnowCommonTile sx={{ width: '100%' }}><OpsnowCommonTile.Content><Stack direction="row" alignItems="flex-start" spacing={2}><OpsnowCommonCheckbox name="organizationScope" checked={checkboxStates.organizationScope} onChange={handleCheckboxChange} color="primary" /><Stack spacing={1}><OpsnowCommonTypography variant="e-b10">Use Organization Scope(Cost Allocation)</OpsnowCommonTypography><OpsnowCommonTypography variant="e-r11" color="commonPalette.gray.600">The same scope defined in Organization Settings is used as the baseline for Budget and KPI tracking.</OpsnowCommonTypography></Stack></Stack></OpsnowCommonTile.Content></OpsnowCommonTile></Grid><Grid item xs={12} md={6} sx={{ display: 'flex' }}><OpsnowCommonTile sx={{ width: '100%' }}><OpsnowCommonTile.Content><Stack direction="row" alignItems="flex-start" spacing={2}><OpsnowCommonCheckbox name="customizeScope" checked={checkboxStates.customizeScope} onChange={handleCheckboxChange} color="primary" /><Stack spacing={1}><OpsnowCommonTypography variant="e-b10">Customize Scope</OpsnowCommonTypography><OpsnowCommonTypography variant="e-r11" color="commonPalette.gray.600">Apply only to selected dimensions, such as specific services (e.g., EC2)</OpsnowCommonTypography></Stack></Stack></OpsnowCommonTile.Content></OpsnowCommonTile></Grid></Grid>`,
63
+ code: `<Grid container spacing={3} sx={{ marginTop: '20px' }}><Grid size={{ xs: 12, md: 6 }} sx={{ display: 'flex' }}><OpsnowCommonTile><OpsnowCommonTile.Content><Stack direction="row" alignItems="flex-start" spacing={2}><OpsnowCommonCheckbox name="organizationScope" checked={checkboxStates.organizationScope} onChange={handleCheckboxChange} color="primary" /><Stack spacing={1}><OpsnowCommonTypography variant="e-b10">Use Organization Scope(Cost Allocation)</OpsnowCommonTypography><OpsnowCommonTypography variant="e-r11" color="commonPalette.gray.600">The same scope defined in Organization Settings is used as the baseline for Budget and KPI tracking.</OpsnowCommonTypography></Stack></Stack></OpsnowCommonTile.Content></OpsnowCommonTile></Grid><Grid size={{ xs: 12, md: 6 }} sx={{ display: 'flex' }}><OpsnowCommonTile><OpsnowCommonTile.Content><Stack direction="row" alignItems="flex-start" spacing={2}><OpsnowCommonCheckbox name="customizeScope" checked={checkboxStates.customizeScope} onChange={handleCheckboxChange} color="primary" /><Stack spacing={1}><OpsnowCommonTypography variant="e-b10">Customize Scope</OpsnowCommonTypography><OpsnowCommonTypography variant="e-r11" color="commonPalette.gray.600">Apply only to selected dimensions, such as specific services (e.g., EC2)</OpsnowCommonTypography></Stack></Stack></OpsnowCommonTile.Content></OpsnowCommonTile></Grid></Grid>`,
65
64
  },
66
65
  ];
67
66
  export const MegaFilterExamples = [
@@ -23,27 +23,27 @@ export const PopupExamples = [
23
23
  },
24
24
  {
25
25
  title: 'Footer 버튼 커스텀',
26
- code: `<OpsnowCommonPopup title="Footer Custom" open={true} onClose={handleClose} footer={[{ text: '확인', type: 'primary', action: 'onConfirm' }, { text: '취소', type: 'default', action: 'onCancel' }]}>
27
- <p>Popup content here</p>
28
- </OpsnowCommonPopup>`,
29
- description: 'footer prop을 통해 하단 버튼을 커스텀하는 예제입니다.'
30
- },
31
- {
32
- title: 'Closable/MaskClosable 옵션',
33
- code: `<OpsnowCommonPopup title="Closable Popup" open={true} onClose={handleClose} closable={false} maskClosable={false}>
26
+ code: `<OpsnowCommonPopup
27
+ title="Footer Custom"
28
+ open={true}
29
+ onClose={handleClose}
30
+ confirmButton={{ text: '확인', variant: 'contained' }}
31
+ cancelButton={{ text: '취소', variant: 'outlined' }}
32
+ onConfirm={handleConfirm}
33
+ >
34
34
  <p>Popup content here</p>
35
35
  </OpsnowCommonPopup>`,
36
- description: '닫기 버튼과 마스크 클릭으로 닫기 비활성화 예제입니다.'
36
+ description: '하단 버튼은 confirmButton/cancelButton 객체({ text, variant, disabled, color })로 커스텀합니다. 취소 버튼은 기본 표시(useFooterCancelBtn 기본 true)이며 onClose가 호출됩니다. footer prop은 없습니다.'
37
37
  },
38
38
  {
39
- title: 'Position(위치) 지정',
40
- code: `<OpsnowCommonPopup title="Bottom Popup" open={true} onClose={handleClose} position="bottom">
39
+ title: '헤더 닫기(X) 버튼 숨김',
40
+ code: `<OpsnowCommonPopup title="No Close Button" open={true} onClose={handleClose} hideHeaderClose>
41
41
  <p>Popup content here</p>
42
42
  </OpsnowCommonPopup>`,
43
- description: '팝업 위치를 bottom으로 지정한 예제입니다.'
43
+ description: 'hideHeaderClose로 헤더의 닫기(X) 버튼을 숨깁니다. closable/maskClosable prop은 없습니다.'
44
44
  },
45
45
  {
46
- title: 'Wing 타입, 작은 사이즈 + cancel/confirm + disableEscapeKeyDown',
46
+ title: 'Wing 타입, 작은 사이즈 + cancel/confirm',
47
47
  code: `<OpsnowCommonPopup
48
48
  title="Example Popup"
49
49
  open={true}
@@ -52,11 +52,10 @@ export const PopupExamples = [
52
52
  cancelButton={{ variant: 'outlined' }}
53
53
  confirmButton={{ color: 'primary', text: 'Save' }}
54
54
  type="wing"
55
- disableEscapeKeyDown
56
55
  >
57
56
  <p>Popup content here</p>
58
57
  </OpsnowCommonPopup>`,
59
- description: 'wing 타입, 작은 사이즈(sm)에 cancel/confirm 버튼과 disableEscapeKeyDown 옵션을 적용한 예제입니다.'
58
+ description: 'wing 타입, 작은 사이즈(sm)에 cancel/confirm 버튼을 적용한 예제입니다. (ESC·배경 클릭으로 닫히지 않게 하는 옵션은 없음 — onClose가 reason을 받지 않음)'
60
59
  },
61
60
  {
62
61
  title: 'Wing 타입, md, Special Title & Footer Action Button',
@@ -188,18 +187,5 @@ export const PopupExamples = [
188
187
  <p>Popup content here</p>
189
188
  </OpsnowCommonPopup>`,
190
189
  description: 'bottom 타입에서 cancelButton과 confirmButton을 사용하는 예제입니다.'
191
- },
192
- {
193
- title: '고정 크기(width/height)',
194
- code: `<OpsnowCommonPopup
195
- title="Size Popup"
196
- open={true}
197
- onClose={handleClose}
198
- width={400}
199
- height={300}
200
- >
201
- <p>Popup content here</p>
202
- </OpsnowCommonPopup>`,
203
- description: '팝업의 너비와 높이를 고정(px)으로 지정하는 예제입니다.'
204
190
  }
205
191
  ];
@@ -5,11 +5,6 @@ export const ProgressExamples = [
5
5
  code: `<OpsnowCommonProgress value={50} />`,
6
6
  description: "50% 진행률을 보여주는 기본 Progress 예제입니다.",
7
7
  },
8
- {
9
- title: "value-size",
10
- code: `<OpsnowCommonProgress value={50} size={32}/>`,
11
- description: "사이즈가 32이고 value가 50인 기본 Progress 예제입니다.",
12
- },
13
8
  {
14
9
  title: "value-inherit",
15
10
  code: `<OpsnowCommonProgress value={75} color="inherit" />`,
@@ -62,12 +62,11 @@ export const StepperExamples = [
62
62
  description: 'Simplified stepper view without connectors.'
63
63
  },
64
64
  {
65
- title: 'Right-aligned Stepper with Custom Connector',
65
+ title: 'Right-aligned Stepper',
66
66
  code: `<OpsnowCommonStepper
67
67
  steps={steps}
68
68
  activeStep={activeStepRight}
69
69
  align="right"
70
- connector={<CustomConnector />}
71
70
  >
72
71
  <OpsnowCommonStepContent stepIndex={0}>
73
72
  <TextField label="Input 1" variant="outlined" fullWidth value={input1} onChange={(e) => setInput1(e.target.value)} />
@@ -79,7 +78,7 @@ export const StepperExamples = [
79
78
  <Typography>This is the content for Step 3</Typography>
80
79
  </OpsnowCommonStepContent>
81
80
  </OpsnowCommonStepper>`,
82
- description: 'Stepper aligned to the right with a custom connector style.'
81
+ description: 'Stepper aligned to the right. (커넥터는 내부에서 고정되어 connector prop으로 바꿀 수 없음)'
83
82
  },
84
83
  {
85
84
  title: 'Vertical Non-linear Stepper',
@@ -1,8 +1,27 @@
1
- // Storage 컴포넌트 예제 데이터
1
+ // Storage 예제 데이터 (컴포넌트가 아니라 useGlobalContext()의 스토리지 API)
2
2
  export const StorageExamples = [
3
3
  {
4
- title: '기본 스토리지',
5
- code: `<OpsnowCommonStorage />`,
6
- description: '기본 스토리지 예제입니다.'
4
+ title: '로컬 스토리지 읽기/쓰기',
5
+ code: `const { commonUILocalStorage } = useGlobalContext()
6
+
7
+ // 저장: 문자열·숫자·객체 모두 가능 (객체는 내부에서 직렬화)
8
+ commonUILocalStorage.setItem('cost-filter', { vendor: 'AWS', period: '3M' })
9
+
10
+ // 읽기: 파싱된 값 반환, 없으면 null
11
+ const saved = commonUILocalStorage.getItem('cost-filter')
12
+
13
+ // 삭제 / 접두사로 일괄 삭제
14
+ commonUILocalStorage.removeItem('cost-filter')
15
+ commonUILocalStorage.clear('cost-')`,
16
+ description: 'localStorage 래퍼입니다. import { useGlobalContext } from \'@opsnow-common/opsnow-finops-common-ui-loader\' 후 함수 컴포넌트 안에서 꺼내 씁니다. 그 밖에 getItemsByPrefix(prefix), getKeysByPrefix(prefix), setItemByApi(...)(API 응답 캐시), 암호화 저장(setItem의 secretKey 인자)을 지원합니다.'
17
+ },
18
+ {
19
+ title: '세션 스토리지 읽기/쓰기',
20
+ code: `const { commonUISessionStorage } = useGlobalContext()
21
+
22
+ commonUISessionStorage.setItem('wizard-step', 2)
23
+ const step = commonUISessionStorage.getItem('wizard-step') ?? 0
24
+ commonUISessionStorage.removeItem('wizard-step')`,
25
+ description: 'sessionStorage 래퍼입니다. getItem, setItem, removeItem, clear 4개만 제공합니다 (clear는 접두사 인자 없음).'
7
26
  }
8
27
  ];
@@ -52,29 +52,9 @@ export const TooltipExamples = [
52
52
  code: `<OpsnowCommonTooltip title="툴팁 내용" placement="bottom" enterDelay={1000}><Button>Hover me</Button></OpsnowCommonTooltip>`,
53
53
  description: "툴팁 열림 지연 시간 예제입니다.",
54
54
  },
55
- {
56
- title: "titleProps.title-bottom-enterTouchDelay",
57
- code: `<OpsnowCommonTooltip title="툴팁 내용" placement="bottom" enterTouchDelay={1000}><Button>Hover me</Button></OpsnowCommonTooltip>`,
58
- description: "툴팁 열림 터치 지연 시간 예제입니다.",
59
- },
60
- {
61
- title: "titleProps.title-bottom-enterNextDelay",
62
- code: `<OpsnowCommonTooltip title="툴팁 내용" placement="bottom" enterNextDelay={1000}><Button>Hover me</Button></OpsnowCommonTooltip>`,
63
- description: "툴팁 열림 다음 지연 시간 예제입니다.",
64
- },
65
55
  {
66
56
  title: "titleProps.title-bottom-leaveDelay",
67
57
  code: `<OpsnowCommonTooltip title="툴팁 내용" placement="bottom" leaveDelay={1000}><Button>Hover me</Button></OpsnowCommonTooltip>`,
68
58
  description: "툴팁 닫힘 지연 시간 예제입니다.",
69
- },
70
- {
71
- title: "titleProps.title-bottom-leaveNextDelay",
72
- code: `<OpsnowCommonTooltip title="툴팁 내용" placement="bottom" leaveNextDelay={1000}><Button>Hover me</Button></OpsnowCommonTooltip>`,
73
- description: "툴팁 닫힘 다음 지연 시간 예제입니다.",
74
- },
75
- {
76
- title: "titleProps.title-bottom-green",
77
- code: `<OpsnowCommonTooltip title="툴팁 내용" placement="bottom" color={'green'}><Button>Hover me</Button></OpsnowCommonTooltip>`,
78
- description: "툴팁 색상 예제입니다.",
79
- },
59
+ }
80
60
  ];
@@ -3,12 +3,12 @@ import { z } from "zod";
3
3
  export const CalendarSchema = z.object({
4
4
  // 캘린더 관련 파라미터들
5
5
  type: z
6
- .enum(["date", "dateRange", "dateTime", "dateTimeRange"])
7
- .describe("단일 날짜, 날짜 범위, 날짜 + 시간 타입 중 선택"),
6
+ .enum(["date", "dateRange", "dateTime", "dateTimeRange", "time"])
7
+ .describe("단일 날짜, 날짜 범위, 날짜 + 시간, 시간만(time — TimePicker) 중 선택"),
8
8
  views: z
9
9
  .array(z.enum(["month", "year", "day", "hours", "minutes", "seconds"]))
10
10
  .optional()
11
- .describe("캘린더 뷰 타입 (여러개 선택 가능)"),
11
+ .describe("캘린더 뷰 타입 (여러개 선택 가능). date·dateRange·dateTimeRange는 day/month/year만, time은 hours/minutes/seconds만 유효. 월 단위 레인지는 type: 'dateRange' + views: ['year', 'month']"),
12
12
  activate: z
13
13
  .boolean()
14
14
  .describe("캘린더 전체 활성화/비활성화 (false시 전체 캘린더 사용 불가)"),
@@ -16,8 +16,17 @@ export const CalendarSchema = z.object({
16
16
  .array(z.string())
17
17
  .optional()
18
18
  .describe("캘린더는 활성화된 상태에서 특정 날짜만 비활성화 (YYYY-MM-DD 형식 배열)"),
19
- minDate: z.string().optional().describe("최소 날짜 (YYYY-MM-DD 형식)"),
20
- maxDate: z.string().optional().describe("최대 날짜 (YYYY-MM-DD 형식)"),
19
+ minDate: z.string().optional().describe("최소 날짜 (YYYY-MM-DD 형식) — 생성기 입력. 실제 prop 타입은 Dayjs이므로 직접 작성 시 minDate={dayjs('2026-01-01')}"),
20
+ maxDate: z.string().optional().describe("최대 날짜 (YYYY-MM-DD 형식) — 생성기 입력. 실제 prop 타입은 Dayjs이므로 직접 작성 시 maxDate={dayjs('2026-12-31')}"),
21
+ // 이하 실제 DatePicker prop
22
+ value: z.string().optional().describe("선택값 상태 변수명. 단일 타입은 Dayjs | null, range 타입은 [Dayjs | null, Dayjs | null]"),
23
+ disabled: z.boolean().optional().describe("캘린더 전체 비활성화"),
24
+ shouldDisableDate: z.string().optional().describe("특정 날짜 비활성화 함수명 ((date: Dayjs) => boolean)"),
25
+ format: z.string().optional().describe("입력 표시 포맷 (예: 'YYYY-MM-DD')"),
26
+ disablePast: z.boolean().optional().describe("오늘 이전 날짜 선택 불가"),
27
+ disableFuture: z.boolean().optional().describe("오늘 이후 날짜 선택 불가"),
28
+ readOnly: z.boolean().optional().describe("읽기 전용"),
29
+ size: z.enum(["small", "medium"]).optional().describe("입력 필드 크기"),
21
30
  // useShortcusts 부분
22
31
  useShortcuts: z
23
32
  .boolean()
@@ -54,9 +63,10 @@ export const CalendarSchema = z.object({
54
63
  message: "Reset일 때는 num, direction 없이 사용하고, Months/Weeks/Days 때는 num과 direction을 모두 설정하세요.",
55
64
  }))
56
65
  .optional()
57
- .describe("커스텀 쇼트컷 - 기본 쇼트컷 대신 사용자가 원하는 날짜 범위 버튼들을 직접 설정 (예: {num: 2, date: 'Months', direction: 'Last'}, {date: 'Reset'}). ⚠️ type이 'dateRange'일 때만 동작 (다른 타입에서는 무시됨)"),
66
+ .describe("커스텀 쇼트컷 - 기본 쇼트컷 대신 사용자가 원하는 날짜 범위 버튼들을 직접 설정 (예: {num: 2, date: 'Months', direction: 'Last'}, {date: 'Reset'}). ⚠️ type이 'dateRange'일 때만 동작 (다른 타입에서는 무시됨). ⚠️ 이 {num, date, direction} 형식은 생성기 입력이고, 실제 prop은 [{ label: string, getValue: () => [Dayjs, Dayjs] }] — 직접 작성 시 이 형식으로"),
58
67
  locale: z.enum(["ko", "en", "ja", "zh"]).optional().describe("달력 표시 언어 로케일 ('ko' | 'en' | 'ja' | 'zh'). 생성 코드는 항상 i18n.getLocale()과 연동된 locale 상태를 자동 전달하므로 보통 지정 불필요. 특정 언어로 고정하려면 생성된 코드의 locale 값을 직접 수정"),
59
68
  onChange: z.string().optional().describe("날짜 선택(변경) 시 호출되는 콜백 - 라이브러리 prop으로 (newValue, context) 인자를 받음. 생성 코드에 setSelectedDate 기반 onChange 핸들러가 항상 자동 포함되므로 보통 지정 불필요. 추가 로직은 생성된 onChange 내부에 덧붙이면 됨"),
69
+ copilotId: z.string().optional().describe("Copilot 연동 ID — Copilot 매니페스트의 bind.copilotId와 매칭되어 AI가 이 컨트롤을 조작할 수 있게 함. useCommonComponents()로 받은 컴포넌트에서만 동작(로더 HOC가 소비 후 제거)"),
60
70
  });
61
71
  // Calendar 컴포넌트 함수 - 배열 반환
62
72
  export function createCalendarComponent() {
@@ -83,6 +93,7 @@ export function createCalendarComponent() {
83
93
  \`\`\`javascript
84
94
  import { useCommonComponents } from '@opsnow-common/opsnow-finops-common-ui-loader';
85
95
  import i18n from '@opsnow-common/opsnow-finops-common-i18n'
96
+ import dayjs from 'dayjs' // calendar 2.x의 peer 의존성 — 앱 package.json에 dayjs 설치 필요
86
97
  const { OpsnowCommonDatePicker } = useCommonComponents();
87
98
  \`\`\``,
88
99
  parameters: CalendarSchema,
@@ -157,10 +168,13 @@ export function createCalendarComponent() {
157
168
  functionDefinitions += `]\n\n`;
158
169
  }
159
170
  // Props 생성
171
+ const isRangeType = !!args.type && args.type.includes("Range");
172
+ const valueVar = isRangeType ? "selectedDateRange" : "selectedDate";
173
+ const setValueFn = isRangeType ? "setSelectedDateRange" : "setSelectedDate";
160
174
  const props = [];
161
175
  if (args.type)
162
176
  props.push(`type="${args.type}"`);
163
- props.push(`value={selectedDate}`);
177
+ props.push(`value={${valueVar}}`);
164
178
  if (!args.activate) {
165
179
  props.push(`disabled={${!args.activate}}`);
166
180
  props.push(`onChange={(newValue) => {
@@ -171,13 +185,13 @@ export function createCalendarComponent() {
171
185
  props.push(`shouldDisableDate={disableSpecificDates}`);
172
186
  props.push(`onChange={(newValue) => {
173
187
  console.log('Selected Date with specific disabled dates:', newValue)
174
- setSelectedDate(newValue)
188
+ ${setValueFn}(newValue)
175
189
  }}`);
176
190
  }
177
191
  else {
178
192
  props.push(`onChange={(newValue) => {
179
193
  console.log('Selected ${args.type}:', newValue)
180
- setSelectedDate(newValue)
194
+ ${setValueFn}(newValue)
181
195
  }}`);
182
196
  }
183
197
  if (args.minDate)
@@ -191,8 +205,15 @@ export function createCalendarComponent() {
191
205
  if (isDateRangeType && args.customShortcuts)
192
206
  props.push(`customShortcuts={customShortcuts}`);
193
207
  props.push(`locale={locale}`);
194
- if (args.views)
195
- props.push(`views={[${args.views.map((view) => `'${view}'`).join(", ")}]}`);
208
+ // range·date 타입은 day/month/year 뷰만 허용
209
+ const dayOnlyViews = isRangeType || args.type === "date";
210
+ const views = dayOnlyViews
211
+ ? args.views?.filter((view) => ["day", "month", "year"].includes(view))
212
+ : args.type === "time"
213
+ ? args.views?.filter((view) => ["hours", "minutes", "seconds"].includes(view))
214
+ : args.views;
215
+ if (views && views.length > 0)
216
+ props.push(`views={[${views.map((view) => `'${view}'`).join(", ")}]}`);
196
217
  const datePickerCode = `<OpsnowCommonDatePicker ${props.join(" ")} />`;
197
218
  // OpsnowCommonDatePicker 아래 <p> 태그 생성
198
219
  const generateSelectedDateCode = () => {
@@ -200,21 +221,21 @@ export function createCalendarComponent() {
200
221
  return "";
201
222
  const isTime = args.type.includes("Time");
202
223
  const isRange = args.type.includes("Range");
203
- const format = isTime ? "YYYY-MM-DD HH:mm" : "YYYY-MM-DD";
224
+ const format = args.type === "time" ? "HH:mm" : isTime ? "YYYY-MM-DD HH:mm" : "YYYY-MM-DD";
204
225
  if (isRange) {
205
226
  const label = isTime
206
227
  ? "Selected DateTime Range"
207
228
  : "Selected Date Range";
208
229
  return `<p>
209
- ${label}: \${selectedDateRange[0] && selectedDateRange[1]
230
+ ${label}: {selectedDateRange[0] && selectedDateRange[1]
210
231
  ? \`\${selectedDateRange[0].format('${format}')} ~ \${selectedDateRange[1].format('${format}')}\`
211
232
  : 'None'}
212
233
  </p>`;
213
234
  }
214
235
  else {
215
- const label = isTime ? "Selected DateTime" : "Selected Date";
236
+ const label = args.type === "time" ? "Selected Time" : isTime ? "Selected DateTime" : "Selected Date";
216
237
  return `<p>
217
- ${label}: \${selectedDate ? selectedDate.format('${format}') : 'None'}
238
+ ${label}: {selectedDate ? selectedDate.format('${format}') : 'None'}
218
239
  </p>`;
219
240
  }
220
241
  };