@reopt-ai/opt-ui 1.10.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/COMPONENT_CATALOG.md +522 -218
  2. package/README.md +61 -7
  3. package/dist/core/index.cjs +10 -8
  4. package/dist/core/index.d.cts +3 -3
  5. package/dist/core/index.d.ts +3 -3
  6. package/dist/core/index.js +3 -3
  7. package/dist/docs/02-components/01-core.md +213 -96
  8. package/dist/docs/02-components/02-visuals.md +1 -1
  9. package/dist/docs/02-components/03-shells.md +334 -123
  10. package/dist/docs/02-components/04-surfaces.md +1 -1
  11. package/dist/docs/02-components/index.md +5 -5
  12. package/dist/docs/03-recipes/02-dashboards.md +126 -10
  13. package/dist/{field-sidebar-B6Ndr8HE.cjs → field-sidebar-BYgEmdy0.cjs} +2128 -1558
  14. package/dist/{field-sidebar-BXjvV7Cz.js → field-sidebar-BuO37l4c.js} +2111 -1561
  15. package/dist/{field-sidebar-nwcE9LC4.d.cts → field-sidebar-CrYlRZew.d.ts} +375 -154
  16. package/dist/{field-sidebar-DWfJAJlx.d.ts → field-sidebar-uXmoyvd7.d.cts} +375 -154
  17. package/dist/id-registry.cjs +124 -12
  18. package/dist/id-registry.d.cts +1 -2
  19. package/dist/id-registry.d.ts +1 -2
  20. package/dist/id-registry.js +124 -14
  21. package/dist/id-registry.json +265 -31
  22. package/dist/index.cjs +1162 -253
  23. package/dist/index.d.cts +573 -60
  24. package/dist/index.d.ts +573 -60
  25. package/dist/index.js +1134 -242
  26. package/dist/{key-pad-menu-DfZbeXhG.d.cts → key-pad-menu-CwcY_Vps.d.cts} +151 -7
  27. package/dist/{key-pad-menu-BQunU20e.js → key-pad-menu-Di2aRGbM.js} +388 -461
  28. package/dist/{key-pad-menu-D5mQcS5v.cjs → key-pad-menu-SItoHPLu.cjs} +490 -609
  29. package/dist/{key-pad-menu-BsgJMjD4.d.ts → key-pad-menu-WBJShCJn.d.ts} +152 -8
  30. package/dist/meta.cjs +1304 -168
  31. package/dist/meta.d.cts +12 -36
  32. package/dist/meta.d.ts +12 -36
  33. package/dist/meta.js +1304 -170
  34. package/dist/query.d.cts +1 -2
  35. package/dist/query.d.ts +1 -2
  36. package/dist/query.js +0 -2
  37. package/dist/shells/index.cjs +4 -1
  38. package/dist/shells/index.d.cts +2 -2
  39. package/dist/shells/index.d.ts +2 -2
  40. package/dist/shells/index.js +2 -2
  41. package/dist/{text-truncate-BGr7bB_8.d.cts → text-truncate-CQyfUZvq.d.cts} +48 -5
  42. package/dist/{text-truncate-BGr7bB_8.d.ts → text-truncate-CQyfUZvq.d.ts} +48 -5
  43. package/dist/{text-truncate-j6X3z7Pv.js → text-truncate-DSuZECy0.js} +446 -94
  44. package/dist/{text-truncate-CxdzAnhS.cjs → text-truncate-I1XLWmRa.cjs} +522 -90
  45. package/dist/theme/server.d.cts +1 -2
  46. package/dist/theme/server.d.ts +1 -2
  47. package/dist/theme/server.js +0 -2
  48. package/dist/visuals/index.js +0 -1
  49. package/package.json +4 -3
  50. package/dist/field-sidebar-BXjvV7Cz.js.map +0 -1
  51. package/dist/field-sidebar-DWfJAJlx.d.ts.map +0 -1
  52. package/dist/field-sidebar-nwcE9LC4.d.cts.map +0 -1
  53. package/dist/id-registry.d.cts.map +0 -1
  54. package/dist/id-registry.d.ts.map +0 -1
  55. package/dist/id-registry.js.map +0 -1
  56. package/dist/index.d.cts.map +0 -1
  57. package/dist/index.d.ts.map +0 -1
  58. package/dist/index.js.map +0 -1
  59. package/dist/key-pad-menu-BQunU20e.js.map +0 -1
  60. package/dist/key-pad-menu-BsgJMjD4.d.ts.map +0 -1
  61. package/dist/key-pad-menu-DfZbeXhG.d.cts.map +0 -1
  62. package/dist/meta.d.cts.map +0 -1
  63. package/dist/meta.d.ts.map +0 -1
  64. package/dist/meta.js.map +0 -1
  65. package/dist/query.d.cts.map +0 -1
  66. package/dist/query.d.ts.map +0 -1
  67. package/dist/query.js.map +0 -1
  68. package/dist/text-truncate-BGr7bB_8.d.cts.map +0 -1
  69. package/dist/text-truncate-BGr7bB_8.d.ts.map +0 -1
  70. package/dist/text-truncate-j6X3z7Pv.js.map +0 -1
  71. package/dist/theme/server.d.cts.map +0 -1
  72. package/dist/theme/server.d.ts.map +0 -1
  73. package/dist/theme/server.js.map +0 -1
package/dist/meta.cjs CHANGED
@@ -721,7 +721,7 @@ const coreMetas = [
721
721
  },
722
722
  {
723
723
  name: "variant",
724
- type: "\"heading-1\" \"heading-6\" | \"display-1\" | \"display-2\" | \"display-3\"",
724
+ type: "\"heading-1\" | \"heading-2\" | \"heading-3\" | \"heading-4\" | \"heading-5\" | \"heading-6\" | \"display-1\" | \"display-2\" | \"display-3\"",
725
725
  description: "시각적 제목 역할"
726
726
  },
727
727
  {
@@ -1050,7 +1050,7 @@ const coreMetas = [
1050
1050
  {
1051
1051
  name: "StatCard",
1052
1052
  category: "core",
1053
- description: "트렌드 방향과 변화량을 표시하는 통계 카드.",
1053
+ description: "단일 지표를 값·변화량·보조 설명으로 표시하는 통계 카드. 비교 기준이 없으면 변화량 줄을 렌더하지 않는다.",
1054
1054
  slug: "stat-card",
1055
1055
  exports: ["StatCard"],
1056
1056
  props: { StatCard: [
@@ -1069,14 +1069,61 @@ const coreMetas = [
1069
1069
  {
1070
1070
  name: "change",
1071
1071
  type: "string",
1072
- required: true,
1073
- description: "변화량 텍스트 (예: '+12.5%')"
1072
+ description: "변화량 텍스트 (예: '+12.5%'). 비교 기준이 없으면 생략 — trend와 함께 있을 때만 렌더된다"
1074
1073
  },
1075
1074
  {
1076
1075
  name: "trend",
1077
1076
  type: "\"up\" | \"down\" | \"neutral\"",
1078
- required: true,
1079
- description: "트렌드 방향. 색상과 아이콘 결정"
1077
+ description: "변화 방향. change와 함께 있을 때만 의미를 갖는다"
1078
+ },
1079
+ {
1080
+ name: "polarity",
1081
+ type: "\"positive\" | \"negative\"",
1082
+ default: "\"positive\"",
1083
+ description: "상승이 좋은 지표인지. 이탈률·에러 수처럼 반대인 지표는 negative로 색을 뒤집는다"
1084
+ },
1085
+ {
1086
+ name: "description",
1087
+ type: "string",
1088
+ description: "값 아래 한 줄 보조 설명 (집계 대상·기간 등)"
1089
+ },
1090
+ {
1091
+ name: "hint",
1092
+ type: "string",
1093
+ description: "산출 근거 등 긴 설명. 툴팁과 aria-describedby로 노출"
1094
+ },
1095
+ {
1096
+ name: "size",
1097
+ type: "\"sm\" | \"md\"",
1098
+ default: "\"md\"",
1099
+ description: "밀도 프리셋. 요약 행에는 sm"
1100
+ },
1101
+ {
1102
+ name: "loading",
1103
+ type: "boolean",
1104
+ default: "false",
1105
+ description: "값 로딩 중 스켈레톤 표시"
1106
+ },
1107
+ {
1108
+ name: "href",
1109
+ type: "string",
1110
+ description: "드릴다운 링크. 지정 시 카드가 앵커로 렌더된다"
1111
+ },
1112
+ {
1113
+ name: "onSelect",
1114
+ type: "() => void",
1115
+ description: "드릴다운 핸들러. 지정 시 카드가 버튼으로 렌더된다"
1116
+ },
1117
+ {
1118
+ name: "selected",
1119
+ type: "boolean",
1120
+ default: "false",
1121
+ description: "선택 가능한 카드 행에서 현재 선택 상태"
1122
+ },
1123
+ {
1124
+ name: "sparklineData",
1125
+ type: "number[]",
1126
+ description: "값 옆에 그릴 미니 추세선 데이터"
1080
1127
  }
1081
1128
  ] },
1082
1129
  ariaRole: "presentation",
@@ -2679,10 +2726,19 @@ const store = useFormStore({ defaultValues: { name: "" } });
2679
2726
  type: "string",
2680
2727
  description: "스위치 레이블"
2681
2728
  },
2729
+ {
2730
+ name: "hint",
2731
+ type: "string",
2732
+ description: "레이블 아래 보조 설명 텍스트"
2733
+ },
2682
2734
  {
2683
2735
  name: "description",
2684
2736
  type: "string",
2685
- description: "레이블 아래 설명 텍스트"
2737
+ description: "레이블 아래 설명 텍스트",
2738
+ deprecated: {
2739
+ since: "1.9.0",
2740
+ replacement: "hint"
2741
+ }
2686
2742
  },
2687
2743
  {
2688
2744
  name: "checked",
@@ -3103,12 +3159,180 @@ const store = useFormStore({ defaultValues: { name: "" } });
3103
3159
  },
3104
3160
  {
3105
3161
  name: "actions",
3106
- type: "EmptyStateAction[]",
3107
- description: "액션 버튼 배열. { label, onClick, variant? }"
3162
+ type: "EmptyStateAction[] | ReactNode",
3163
+ description: "액션 버튼 배열 { label, onClick, variant? } 또는 직접 렌더할 노드"
3164
+ },
3165
+ {
3166
+ name: "body",
3167
+ type: "ReactNode",
3168
+ description: "description 대신 넣는 리치 본문 (문단·목록·코드 스니펫)"
3169
+ },
3170
+ {
3171
+ name: "footer",
3172
+ type: "ReactNode",
3173
+ description: "구분선 아래 보조 콘텐츠. 문서 링크·단축키 안내 등"
3174
+ },
3175
+ {
3176
+ name: "layout",
3177
+ type: "\"vertical\" | \"horizontal\"",
3178
+ default: "\"vertical\"",
3179
+ description: "horizontal은 아이콘을 텍스트 옆으로 옮긴다 (전체 페이지용)"
3180
+ },
3181
+ {
3182
+ name: "titleSize",
3183
+ type: "\"sm\" | \"md\" | \"lg\"",
3184
+ default: "\"md\"",
3185
+ description: "제목 시각 크기. headingLevel과 독립"
3186
+ },
3187
+ {
3188
+ name: "hasBorder",
3189
+ type: "boolean",
3190
+ default: "false",
3191
+ description: "테두리 있는 패널로 렌더"
3192
+ },
3193
+ {
3194
+ name: "tone",
3195
+ type: "\"default\" | \"subdued\" | \"error\"",
3196
+ default: "\"default\"",
3197
+ description: "error는 실패를 알리고 role=alert로 렌더. subdued는 아직 데이터가 없음을 뜻한다"
3198
+ },
3199
+ {
3200
+ name: "iconSize",
3201
+ type: "\"sm\" | \"md\" | \"lg\"",
3202
+ default: "\"md\"",
3203
+ description: "아이콘 크기 프리셋"
3204
+ },
3205
+ {
3206
+ name: "headingLevel",
3207
+ type: "\"h2\" | \"h3\" | \"h4\"",
3208
+ default: "\"h3\"",
3209
+ description: "문서 구조상 제목 레벨"
3108
3210
  }
3109
3211
  ] },
3110
3212
  testDescribe: "EmptyState"
3111
3213
  },
3214
+ {
3215
+ name: "DescriptionList",
3216
+ category: "core",
3217
+ functionalCategory: "data-display",
3218
+ maturity: "stable",
3219
+ keywords: [
3220
+ "detail",
3221
+ "definition",
3222
+ "key-value",
3223
+ "metadata",
3224
+ "dl"
3225
+ ],
3226
+ description: "한 대상의 라벨 있는 사실들을 시맨틱 <dl>로 표시한다. 상세 패널의 필드 나열을 표준화하고 식별자 복사를 제공한다.",
3227
+ slug: "description-list",
3228
+ exports: ["DescriptionList"],
3229
+ props: { DescriptionList: [
3230
+ {
3231
+ name: "items",
3232
+ type: "DescriptionListItem[]",
3233
+ required: true,
3234
+ description: "term/description 쌍 배열. { id, term, description, copyValue?, hint?, wrap? }"
3235
+ },
3236
+ {
3237
+ name: "layout",
3238
+ type: "\"grid\" | \"stack\" | \"inline\"",
3239
+ default: "\"grid\"",
3240
+ description: "grid는 각 쌍을 박스로 감싼다(필드가 많은 상세 패널). stack은 평범한 정의 목록, inline은 헤더 스트립용 한 줄 배치"
3241
+ },
3242
+ {
3243
+ name: "columns",
3244
+ type: "1 | 2 | 3 | 4",
3245
+ default: "2",
3246
+ description: "grid/stack 레이아웃의 컬럼 수"
3247
+ },
3248
+ {
3249
+ name: "compressed",
3250
+ type: "boolean",
3251
+ default: "false",
3252
+ description: "밀도 축소"
3253
+ },
3254
+ {
3255
+ name: "labels",
3256
+ type: "DescriptionListLabels",
3257
+ description: "복사/복사됨/빈 값 문구"
3258
+ }
3259
+ ] },
3260
+ usage: {
3261
+ bestPractices: ["세션 ID·프로필 ID처럼 다른 화면에 붙여넣게 되는 값에는 copyValue를 준다.", "값이 비면 빈 셀 대신 labels.empty가 렌더되므로 로딩 중과 구분된다."],
3262
+ avoid: ["표 형태로 비교해야 하는 다건 데이터에는 쓰지 않는다 — 그건 DataTable의 일이다."]
3263
+ },
3264
+ testDescribe: "DescriptionList"
3265
+ },
3266
+ {
3267
+ name: "Identity",
3268
+ category: "core",
3269
+ functionalCategory: "data-display",
3270
+ maturity: "stable",
3271
+ keywords: [
3272
+ "user",
3273
+ "profile",
3274
+ "avatar",
3275
+ "person",
3276
+ "anonymous"
3277
+ ],
3278
+ description: "사람(또는 익명 디바이스)을 목록 셀 한 칸으로 표현한다. 이름→이메일→ID 폴백과 익명 표식을 한 곳에서 처리한다.",
3279
+ slug: "identity",
3280
+ exports: ["Identity"],
3281
+ props: { Identity: [
3282
+ {
3283
+ name: "name",
3284
+ type: "string | null",
3285
+ description: "표시 이름. 없으면 secondary, 그다음 id로 폴백한다"
3286
+ },
3287
+ {
3288
+ name: "secondary",
3289
+ type: "string | null",
3290
+ description: "보통 이메일. 이름이 없으면 주 라벨로 승격된다"
3291
+ },
3292
+ {
3293
+ name: "id",
3294
+ type: "string",
3295
+ description: "최후 폴백 라벨이자 hover 시 title"
3296
+ },
3297
+ {
3298
+ name: "avatarSrc",
3299
+ type: "string | null",
3300
+ description: "아바타 이미지 URL"
3301
+ },
3302
+ {
3303
+ name: "state",
3304
+ type: "\"identified\" | \"anonymous\"",
3305
+ description: "익명 여부. 디바이스를 사람으로 오독하지 않게 배지로 표시한다"
3306
+ },
3307
+ {
3308
+ name: "badge",
3309
+ type: "ReactNode",
3310
+ description: "state 배지 대신 표시할 내용 (라이프사이클 단계 등)"
3311
+ },
3312
+ {
3313
+ name: "size",
3314
+ type: "\"sm\" | \"md\"",
3315
+ default: "\"md\"",
3316
+ description: "밀도 프리셋"
3317
+ },
3318
+ {
3319
+ name: "hideAvatar",
3320
+ type: "boolean",
3321
+ default: "false",
3322
+ description: "이미 밀도가 높은 행에서 아바타 제거"
3323
+ },
3324
+ {
3325
+ name: "labels",
3326
+ type: "{ identified?: string; anonymous?: string }",
3327
+ description: "식별/익명 배지 문구"
3328
+ }
3329
+ ] },
3330
+ usage: {
3331
+ bestPractices: ["사용자·세션·이벤트 표에서 주체를 나타내는 컬럼에 일관되게 사용한다."],
3332
+ avoid: ["아바타만 필요하면 Avatar를 직접 쓴다."]
3333
+ },
3334
+ testDescribe: "Identity"
3335
+ },
3112
3336
  {
3113
3337
  name: "Card",
3114
3338
  category: "core",
@@ -3188,10 +3412,19 @@ const store = useFormStore({ defaultValues: { name: "" } });
3188
3412
  type: "string",
3189
3413
  description: "체크박스 레이블"
3190
3414
  },
3415
+ {
3416
+ name: "hint",
3417
+ type: "string",
3418
+ description: "레이블 아래 보조 설명 텍스트"
3419
+ },
3191
3420
  {
3192
3421
  name: "description",
3193
3422
  type: "string",
3194
- description: "레이블 아래 설명 텍스트"
3423
+ description: "레이블 아래 설명 텍스트",
3424
+ deprecated: {
3425
+ since: "1.9.0",
3426
+ replacement: "hint"
3427
+ }
3195
3428
  },
3196
3429
  {
3197
3430
  name: "checked",
@@ -3325,10 +3558,19 @@ const store = useFormStore({ defaultValues: { name: "" } });
3325
3558
  type: "string",
3326
3559
  description: "라디오 레이블"
3327
3560
  },
3561
+ {
3562
+ name: "hint",
3563
+ type: "string",
3564
+ description: "레이블 아래 보조 설명 텍스트"
3565
+ },
3328
3566
  {
3329
3567
  name: "description",
3330
3568
  type: "string",
3331
- description: "레이블 아래 설명 텍스트"
3569
+ description: "레이블 아래 설명 텍스트",
3570
+ deprecated: {
3571
+ since: "1.9.0",
3572
+ replacement: "hint"
3573
+ }
3332
3574
  },
3333
3575
  {
3334
3576
  name: "size",
@@ -3399,7 +3641,7 @@ const store = useFormStore({ defaultValues: { name: "" } });
3399
3641
  },
3400
3642
  {
3401
3643
  name: "onComparisonChange",
3402
- type: "(comparison: boolean, mode: ComparisonMode) => void",
3644
+ type: "(mode: ComparisonMode | null) => void",
3403
3645
  description: "비교 모드 변경 콜백"
3404
3646
  },
3405
3647
  {
@@ -4087,7 +4329,12 @@ const store = useFormStore({ defaultValues: { name: "" } });
4087
4329
  {
4088
4330
  name: "autoSaveId",
4089
4331
  type: "string",
4090
- description: "id의 deprecated 별칭"
4332
+ description: "id의 deprecated 별칭",
4333
+ deprecated: {
4334
+ since: "1.6.0",
4335
+ replacement: "id",
4336
+ reason: "id + defaultLayout + onLayoutChanged 조합으로 대체되었습니다."
4337
+ }
4091
4338
  },
4092
4339
  {
4093
4340
  name: "className",
@@ -4734,82 +4981,273 @@ const MyDashboard = createBlock<MyDashboardProps>(
4734
4981
  }
4735
4982
  ] },
4736
4983
  testDescribe: "ShaderSurface"
4737
- }
4738
- ];
4739
- //#endregion
4740
- //#region src/visuals/_meta.ts
4741
- /** @deprecated Chart metadata moved to `@reopt-ai/opt-charts/meta`. */
4742
- const visualMetas = [];
4743
- const shellMetas = [
4744
- {
4745
- name: "FaqAccordion",
4746
- category: "shell",
4747
- description: "FAQ 질문/답변 아코디언. Composite + Disclosure 조합.",
4748
- slug: "faq-accordion",
4749
- exports: ["FaqAccordion"],
4750
- props: { FaqAccordion: [{
4751
- name: "items",
4752
- type: "FaqItem[]",
4753
- required: true,
4754
- description: "FAQ 항목 배열. { id, question, answer }"
4755
- }] },
4756
- dependencies: [{
4757
- slug: "disclosure",
4758
- category: "core"
4759
- }],
4760
- testDescribe: "FaqAccordion"
4761
4984
  },
4762
4985
  {
4763
- name: "PageAside",
4764
- category: "shell",
4765
- description: "Sticky 페이지 사이드 aside chrome. 고정 너비 + header/body/footer 3-slot.",
4766
- slug: "page-aside",
4767
- exports: ["PageAside"],
4768
- props: { PageAside: [
4986
+ name: "Callout",
4987
+ category: "core",
4988
+ description: "페이지에 머무는 인페이지 고지. Alert와 달리 dismiss가 없고, 조건이 참인 동안 레이아웃의 일부로 남습니다.",
4989
+ slug: "callout",
4990
+ exports: ["Callout"],
4991
+ props: { Callout: [
4769
4992
  {
4770
- name: "header",
4993
+ name: "tone",
4994
+ type: "\"info\" | \"success\" | \"warning\" | \"danger\" | \"neutral\"",
4995
+ default: "\"info\"",
4996
+ description: "고지의 성격"
4997
+ },
4998
+ {
4999
+ name: "title",
4771
5000
  type: "ReactNode",
4772
- description: "상단 고정 슬롯 (예: package switcher)."
5001
+ description: " 제목"
5002
+ },
5003
+ {
5004
+ name: "icon",
5005
+ type: "ReactNode",
5006
+ description: "tone 기본 글리프 대체. null이면 글리프 없음"
5007
+ },
5008
+ {
5009
+ name: "action",
5010
+ type: "ReactNode",
5011
+ description: "다음 한 걸음. 둘 이상이면 고지가 아니라 폼"
5012
+ },
5013
+ {
5014
+ name: "size",
5015
+ type: "\"sm\" | \"md\"",
5016
+ default: "\"md\"",
5017
+ description: "밀도"
4773
5018
  },
4774
5019
  {
4775
5020
  name: "children",
4776
5021
  type: "ReactNode",
4777
- required: true,
4778
- description: "스크롤 가능한 네비게이션 영역."
5022
+ description: "본문"
5023
+ }
5024
+ ] },
5025
+ usage: {
5026
+ bestPractices: ["조건이 참인 동안 계속 보여야 하는 안내에 사용합니다 (정리할 신규 N개, 표본 기반 수치)."],
5027
+ avoid: ["방금 일어난 일을 알릴 때는 Alert를 씁니다. Callout에는 dismiss가 없습니다."]
5028
+ },
5029
+ testDescribe: "Callout"
5030
+ },
5031
+ {
5032
+ name: "StatusDot",
5033
+ category: "core",
5034
+ description: "점 + 텍스트로 상태를 표현합니다. 라벨이 필수라 색만으로 상태를 말할 수 없습니다.",
5035
+ slug: "status-dot",
5036
+ exports: ["StatusDot"],
5037
+ props: { StatusDot: [
5038
+ {
5039
+ name: "tone",
5040
+ type: "\"neutral\" | \"muted\" | \"info\" | \"success\" | \"warning\" | \"danger\"",
5041
+ default: "\"neutral\"",
5042
+ description: "점 색상"
4779
5043
  },
4780
5044
  {
4781
- name: "footer",
5045
+ name: "size",
5046
+ type: "\"sm\" | \"md\"",
5047
+ default: "\"md\"",
5048
+ description: "점과 글자 크기"
5049
+ },
5050
+ {
5051
+ name: "children",
4782
5052
  type: "ReactNode",
4783
- description: "하단 고정 슬롯 (예: version label)."
5053
+ required: true,
5054
+ description: "상태 라벨. 필수 — 색은 상태가 아닙니다"
4784
5055
  },
4785
5056
  {
4786
- name: "widthClassName",
5057
+ name: "hint",
4787
5058
  type: "string",
4788
- description: "너비 Tailwind 클래스. 기본값 `w-60`."
4789
- },
5059
+ description: "판정 기준 툴팁"
5060
+ }
5061
+ ] },
5062
+ usage: {
5063
+ bestPractices: ["표의 상태 컬럼처럼 행마다 반복되는 자리에 씁니다. 점은 장식이고 의미는 텍스트가 담습니다."],
5064
+ avoid: ["사람이 붙인 라벨(태그, 카테고리)에는 Badge를 씁니다. StatusDot은 관측된 상태용입니다."]
5065
+ },
5066
+ testDescribe: "StatusDot"
5067
+ },
5068
+ {
5069
+ name: "CopyButton",
5070
+ category: "core",
5071
+ description: "값을 클립보드에 올리고 성공 여부를 알립니다. 클립보드 거부를 삼키지 않습니다.",
5072
+ slug: "copy-button",
5073
+ exports: ["CopyButton"],
5074
+ props: { CopyButton: [
4790
5075
  {
4791
- name: "label",
5076
+ name: "value",
4792
5077
  type: "string",
4793
- description: "`<aside>`의 aria-label."
5078
+ required: true,
5079
+ description: "복사할 텍스트"
4794
5080
  },
4795
5081
  {
4796
- name: "hideOnMobile",
4797
- type: "boolean",
4798
- description: "md 아래에서 숨김. 기본값 true."
5082
+ name: "variant",
5083
+ type: "\"icon\" | \"text\"",
5084
+ default: "\"icon\"",
5085
+ description: "글리프만 / 글리프 + 라벨"
5086
+ },
5087
+ {
5088
+ name: "size",
5089
+ type: "\"sm\" | \"md\"",
5090
+ default: "\"md\"",
5091
+ description: "버튼 크기"
5092
+ },
5093
+ {
5094
+ name: "feedbackDuration",
5095
+ type: "number",
5096
+ default: "1500",
5097
+ description: "확인 상태 유지 시간 (ms)"
5098
+ },
5099
+ {
5100
+ name: "onCopy",
5101
+ type: "(ok: boolean) => void",
5102
+ description: "실제로 복사됐는지 — 비보안 컨텍스트·권한 거부에서 false"
5103
+ },
5104
+ {
5105
+ name: "labels",
5106
+ type: "CopyButtonLabels",
5107
+ description: "copy / copied / failed 문구"
4799
5108
  }
4800
5109
  ] },
4801
- testDescribe: "PageAside"
5110
+ testDescribe: "CopyButton"
4802
5111
  },
4803
5112
  {
4804
- name: "ContentTabs",
4805
- category: "shell",
4806
- description: " 콘텐츠 패널. Tabs primitive 래퍼.",
4807
- slug: "content-tabs",
4808
- exports: ["ContentTabs"],
4809
- props: { ContentTabs: [{
4810
- name: "tabs",
4811
- type: "TabDef[]",
4812
- required: true,
5113
+ name: "SegmentedControl",
5114
+ category: "core",
5115
+ description: "선택지를 모두 보여주는 단일 선택 컨트롤. radio-group 프리미티브 위에 올려 화살표 키가 동작합니다.",
5116
+ slug: "segmented-control",
5117
+ exports: ["SegmentedControl"],
5118
+ props: { SegmentedControl: [
5119
+ {
5120
+ name: "options",
5121
+ type: "SegmentedControlOption<T>[]",
5122
+ required: true,
5123
+ description: "세그먼트 목록 (value / label / icon / badge / disabled)"
5124
+ },
5125
+ {
5126
+ name: "value",
5127
+ type: "T",
5128
+ required: true,
5129
+ description: "선택된 값"
5130
+ },
5131
+ {
5132
+ name: "onChange",
5133
+ type: "(value: T) => void",
5134
+ required: true,
5135
+ description: "선택 변경"
5136
+ },
5137
+ {
5138
+ name: "size",
5139
+ type: "\"sm\" | \"md\"",
5140
+ default: "\"md\"",
5141
+ description: "컨트롤 크기"
5142
+ },
5143
+ {
5144
+ name: "fullWidth",
5145
+ type: "boolean",
5146
+ default: "false",
5147
+ description: "컨테이너를 균등 분할"
5148
+ },
5149
+ {
5150
+ name: "disabled",
5151
+ type: "boolean",
5152
+ default: "false",
5153
+ description: "전체 비활성"
5154
+ },
5155
+ {
5156
+ name: "aria-label",
5157
+ type: "string",
5158
+ required: true,
5159
+ description: "radiogroup 이름. 필수"
5160
+ }
5161
+ ] },
5162
+ usage: {
5163
+ bestPractices: ["정확히 하나가 참이고 선택지가 적을 때 씁니다 (이벤트 | 속성)."],
5164
+ avoid: ["여러 개를 동시에 켤 수 있으면 ToggleGroup을 씁니다.", "선택지가 많으면 Select를 씁니다."]
5165
+ },
5166
+ testDescribe: "SegmentedControl"
5167
+ }
5168
+ ];
5169
+ //#endregion
5170
+ //#region src/visuals/_meta.ts
5171
+ /** @deprecated Chart metadata moved to `@reopt-ai/opt-charts/meta`. */
5172
+ const visualMetas = [];
5173
+ const shellMetas = [
5174
+ {
5175
+ name: "FaqAccordion",
5176
+ category: "shell",
5177
+ description: "FAQ 질문/답변 아코디언. Composite + Disclosure 조합.",
5178
+ slug: "faq-accordion",
5179
+ exports: ["FaqAccordion"],
5180
+ props: { FaqAccordion: [{
5181
+ name: "items",
5182
+ type: "FaqItem[]",
5183
+ required: true,
5184
+ description: "FAQ 항목 배열. { id, question, answer }"
5185
+ }] },
5186
+ ariaRole: "region",
5187
+ keyboardShortcuts: [{
5188
+ keys: "↑ ↓",
5189
+ action: "질문 사이 포커스 이동"
5190
+ }, {
5191
+ keys: "Enter / Space",
5192
+ action: "답변 펼치기 또는 접기"
5193
+ }],
5194
+ dependencies: [{
5195
+ slug: "disclosure",
5196
+ category: "core"
5197
+ }],
5198
+ testDescribe: "FaqAccordion"
5199
+ },
5200
+ {
5201
+ name: "PageAside",
5202
+ category: "shell",
5203
+ description: "Sticky 페이지 사이드 aside chrome. 고정 너비 + header/body/footer 3-slot.",
5204
+ slug: "page-aside",
5205
+ exports: ["PageAside"],
5206
+ props: { PageAside: [
5207
+ {
5208
+ name: "header",
5209
+ type: "ReactNode",
5210
+ description: "상단 고정 슬롯 (예: package switcher)."
5211
+ },
5212
+ {
5213
+ name: "children",
5214
+ type: "ReactNode",
5215
+ required: true,
5216
+ description: "스크롤 가능한 네비게이션 영역."
5217
+ },
5218
+ {
5219
+ name: "footer",
5220
+ type: "ReactNode",
5221
+ description: "하단 고정 슬롯 (예: version label)."
5222
+ },
5223
+ {
5224
+ name: "widthClassName",
5225
+ type: "string",
5226
+ description: "너비 Tailwind 클래스. 기본값 `w-60`."
5227
+ },
5228
+ {
5229
+ name: "label",
5230
+ type: "string",
5231
+ description: "`<aside>`의 aria-label."
5232
+ },
5233
+ {
5234
+ name: "hideOnMobile",
5235
+ type: "boolean",
5236
+ description: "md 아래에서 숨김. 기본값 true."
5237
+ }
5238
+ ] },
5239
+ testDescribe: "PageAside"
5240
+ },
5241
+ {
5242
+ name: "ContentTabs",
5243
+ category: "shell",
5244
+ description: "탭 콘텐츠 패널. Tabs primitive 래퍼.",
5245
+ slug: "content-tabs",
5246
+ exports: ["ContentTabs"],
5247
+ props: { ContentTabs: [{
5248
+ name: "tabs",
5249
+ type: "TabDef[]",
5250
+ required: true,
4813
5251
  description: "탭 정의 배열. { id, label, content }"
4814
5252
  }, {
4815
5253
  name: "defaultTabId",
@@ -5074,6 +5512,25 @@ const fields = [
5074
5512
  slug: "kbd",
5075
5513
  category: "core"
5076
5514
  }],
5515
+ ariaRole: "menubar",
5516
+ keyboardShortcuts: [
5517
+ {
5518
+ keys: "← →",
5519
+ action: "상위 메뉴 사이 포커스 이동"
5520
+ },
5521
+ {
5522
+ keys: "Enter / Space / ↓",
5523
+ action: "메뉴 열기"
5524
+ },
5525
+ {
5526
+ keys: "↑ ↓",
5527
+ action: "열린 메뉴 항목 이동"
5528
+ },
5529
+ {
5530
+ keys: "Escape",
5531
+ action: "메뉴 닫고 트리거로 복귀"
5532
+ }
5533
+ ],
5077
5534
  testDescribe: "AppMenubar"
5078
5535
  },
5079
5536
  {
@@ -5085,7 +5542,7 @@ const fields = [
5085
5542
  props: { CommandPalette: [
5086
5543
  {
5087
5544
  name: "commands",
5088
- type: "Omit<Command, \"action\">[]",
5545
+ type: "Omit<CommandDef, \"action\">[]",
5089
5546
  required: true,
5090
5547
  description: "커맨드 목록. { id, label, group, shortcut? }"
5091
5548
  },
@@ -5103,7 +5560,7 @@ const fields = [
5103
5560
  },
5104
5561
  {
5105
5562
  name: "onSelect",
5106
- type: "(command: Omit<Command, 'action'>) => void",
5563
+ type: "(command: Omit<CommandDef, \"action\">) => void",
5107
5564
  description: "커맨드 선택 핸들러"
5108
5565
  },
5109
5566
  {
@@ -5158,13 +5615,13 @@ const fields = [
5158
5615
  props: { CommandPaletteTrigger: [
5159
5616
  {
5160
5617
  name: "commands",
5161
- type: "Omit<Command, \"action\">[]",
5618
+ type: "Omit<CommandDef, \"action\">[]",
5162
5619
  required: true,
5163
5620
  description: "커맨드 목록"
5164
5621
  },
5165
5622
  {
5166
5623
  name: "onSelect",
5167
- type: "(command: Omit<Command, 'action'>) => void",
5624
+ type: "(command: Omit<CommandDef, \"action\">) => void",
5168
5625
  description: "커맨드 선택 핸들러"
5169
5626
  },
5170
5627
  {
@@ -5639,7 +6096,7 @@ const data = [
5639
6096
  },
5640
6097
  {
5641
6098
  name: "onFilterChange",
5642
- type: "(filterId: string, value: string | string[]) => void",
6099
+ type: "(filterId: string, value: string | string[] | boolean) => void",
5643
6100
  description: "필터 변경 핸들러"
5644
6101
  },
5645
6102
  {
@@ -5830,7 +6287,7 @@ const filters = [
5830
6287
  },
5831
6288
  {
5832
6289
  name: "onSubmit",
5833
- type: "(values: { email, password, confirmPassword?, rememberMe?, mode }) => void",
6290
+ type: "(values: { email: string; password: string; confirmPassword?: string; rememberMe?: boolean; mode: \"login\" | \"signup\" }) => void",
5834
6291
  description: "제출 핸들러"
5835
6292
  },
5836
6293
  {
@@ -5995,7 +6452,7 @@ const filters = [
5995
6452
  },
5996
6453
  {
5997
6454
  name: "onSubmit",
5998
- type: "(values: { title, description, file }) => void",
6455
+ type: "(values: { title: string; description: string; file: File | null }) => void",
5999
6456
  description: "제출 핸들러"
6000
6457
  },
6001
6458
  {
@@ -6592,7 +7049,7 @@ const filters = [
6592
7049
  },
6593
7050
  {
6594
7051
  name: "onSave",
6595
- type: "(report: { name, description, chartType, dataSourceId }) => void",
7052
+ type: "(report: { name: string; description: string; chartType: ReportBuilderChartType; dataSourceId: string }) => void",
6596
7053
  description: "저장 콜백"
6597
7054
  },
6598
7055
  {
@@ -6790,14 +7247,25 @@ const filters = [
6790
7247
  },
6791
7248
  {
6792
7249
  name: "columns",
6793
- type: "2 | 3 | 4",
7250
+ type: "2 | 3 | 4 | 5 | 6",
6794
7251
  default: "4",
6795
7252
  description: "컬럼 수"
6796
7253
  },
6797
7254
  {
6798
7255
  name: "onStatClick",
6799
7256
  type: "(stat: StatCardType) => void",
6800
- description: "카드 클릭 핸들러"
7257
+ description: "카드 클릭 핸들러. 지정 시 카드 자체가 버튼으로 렌더된다"
7258
+ },
7259
+ {
7260
+ name: "selectedStatId",
7261
+ type: "string",
7262
+ description: "선택 가능한 행에서 현재 뷰를 이끄는 카드 id"
7263
+ },
7264
+ {
7265
+ name: "size",
7266
+ type: "\"sm\" | \"md\"",
7267
+ default: "\"md\"",
7268
+ description: "카드 밀도. 표 위 헤더 스트립에는 sm"
6801
7269
  },
6802
7270
  {
6803
7271
  name: "formatValue",
@@ -6833,124 +7301,375 @@ const filters = [
6833
7301
  testDescribe: "SummaryRow"
6834
7302
  },
6835
7303
  {
6836
- name: "ComparisonSelector",
7304
+ name: "EventTimeline",
6837
7305
  category: "shell",
6838
- description: "비교 선택기. 고유 레이블이 연결된 Select × 2 + Swap 버튼 조합. CohortComparison, FunnelAnalysis 등에서 사용합니다.",
6839
- slug: "comparison-selector",
6840
- exports: ["ComparisonSelector"],
6841
- props: { ComparisonSelector: [
7306
+ functionalCategory: "data-display",
7307
+ maturity: "stable",
7308
+ keywords: [
7309
+ "timeline",
7310
+ "events",
7311
+ "session",
7312
+ "activity",
7313
+ "sequence"
7314
+ ],
7315
+ description: "이벤트를 순서로 읽히게 하는 타임라인. 인접 이벤트 사이의 경과 시간을 라벨링하고, 속성 페이로드를 제자리에서 펼치며, 세션 단위 그룹핑을 지원한다.",
7316
+ slug: "event-timeline",
7317
+ exports: ["EventTimeline"],
7318
+ props: { EventTimeline: [
6842
7319
  {
6843
- name: "segments",
6844
- type: "ComparisonOption[]",
6845
- required: true,
6846
- description: "선택 가능한 세그먼트 목록"
7320
+ name: "items",
7321
+ type: "EventTimelineItem[]",
7322
+ description: "평면 목록. groups가 있으면 무시된다"
6847
7323
  },
6848
7324
  {
6849
- name: "valueA",
6850
- type: "string",
6851
- description: "Base 선택값"
7325
+ name: "groups",
7326
+ type: "EventTimelineGroup[]",
7327
+ description: "그룹(주로 세션) 단위 목록. 접기/펼치기와 그룹 요약을 제공"
6852
7328
  },
6853
7329
  {
6854
- name: "valueB",
6855
- type: "string",
6856
- description: "Compare 선택값"
7330
+ name: "showGaps",
7331
+ type: "boolean",
7332
+ default: "true",
7333
+ description: "인접 이벤트 사이 경과 시간 표시. 체크아웃 직전 4분 정지 같은 것이 발견이 된다"
6857
7334
  },
6858
7335
  {
6859
- name: "onChangeA",
6860
- type: "(value: string) => void",
6861
- description: "Base 변경 핸들러"
7336
+ name: "order",
7337
+ type: "\"desc\" | \"asc\"",
7338
+ default: "\"desc\"",
7339
+ description: "전달된 items의 정렬 방향. 경과 시간 계산 기준"
6862
7340
  },
6863
7341
  {
6864
- name: "onChangeB",
6865
- type: "(value: string) => void",
6866
- description: "Compare 변경 핸들러"
7342
+ name: "minGapMs",
7343
+ type: "number",
7344
+ default: "1000",
7345
+ description: "이 값 미만의 간격은 라벨을 생략"
6867
7346
  },
6868
7347
  {
6869
- name: "labels",
6870
- type: "ComparisonSelectorLabels",
6871
- description: "i18n용 라벨 오버라이드"
6872
- }
6873
- ] },
6874
- dependencies: [{
6875
- slug: "select",
6876
- category: "core"
6877
- }, {
6878
- slug: "button",
6879
- category: "core"
6880
- }],
6881
- testDescribe: "ComparisonSelector"
6882
- },
6883
- {
6884
- name: "ThemeSwitcher",
6885
- category: "shell",
6886
- description: "테마 프리셋 선택기. 프리셋과 light/dark/system 모드 전환.",
6887
- slug: "theme-switcher",
6888
- exports: ["ThemeSwitcher"],
6889
- props: { ThemeSwitcher: [{
6890
- name: "labels",
6891
- type: "ThemeSwitcherLabels",
6892
- description: "i18n용 라벨 오버라이드"
6893
- }] },
6894
- testDescribe: "ThemeSwitcher"
6895
- },
6896
- {
6897
- name: "PromptTemplate",
6898
- category: "shell",
6899
- description: "AI 프롬프트 뷰어/헬퍼. 변수 입력 → 실시간 치환 → 복사 기능.",
6900
- slug: "prompt-template",
6901
- exports: ["PromptTemplate"],
6902
- props: { PromptTemplate: [
7348
+ name: "loading",
7349
+ type: "boolean",
7350
+ default: "false",
7351
+ description: "스켈레톤 표시"
7352
+ },
6903
7353
  {
6904
- name: "title",
6905
- type: "string",
6906
- required: true,
6907
- description: "프롬프트 제목"
7354
+ name: "hasMore",
7355
+ type: "boolean",
7356
+ default: "false",
7357
+ description: " 불러올 항목 존재"
6908
7358
  },
6909
7359
  {
6910
- name: "description",
6911
- type: "string",
6912
- description: "프롬프트 설명 (헤더 아래 표시)"
7360
+ name: "onLoadMore",
7361
+ type: "() => void",
7362
+ description: " 보기 핸들러"
6913
7363
  },
6914
7364
  {
6915
- name: "template",
6916
- type: "string",
6917
- required: true,
6918
- description: "프롬프트 템플릿. {변수명}으로 치환 가능"
7365
+ name: "onItemSelect",
7366
+ type: "(item: EventTimelineItem) => void",
7367
+ description: "이벤트 헤드라인 클릭 핸들러"
6919
7368
  },
6920
7369
  {
6921
- name: "variables",
6922
- type: "PromptVariable[]",
6923
- description: "변수 정의 배열. { name, label, placeholder?, defaultValue? }"
7370
+ name: "locale",
7371
+ type: "string",
7372
+ description: "경과 시간 포맷 로케일"
6924
7373
  },
6925
7374
  {
6926
7375
  name: "labels",
6927
- type: "PromptTemplateLabels",
6928
- description: "i18n용 라벨 오버라이드"
7376
+ type: "EventTimelineLabels",
7377
+ description: "문구 오버라이드"
6929
7378
  }
6930
7379
  ] },
6931
- dependencies: [{
6932
- slug: "input",
6933
- category: "core"
6934
- }],
6935
- testDescribe: "PromptTemplate"
7380
+ usage: {
7381
+ bestPractices: ["사용자 히스토리는 세션으로 그룹핑한다 — 세션 경계가 없으면 히스토리를 읽을 수 없다.", "이벤트 속성은 properties로 넘겨 제자리에서 펼치게 한다."],
7382
+ avoid: ["수천 건을 한 번에 넘기지 않는다. 페이지네이션이나 onLoadMore로 나눈다."]
7383
+ },
7384
+ testDescribe: "EventTimeline"
6936
7385
  },
6937
7386
  {
6938
- name: "Sidebar",
7387
+ name: "ScoreBreakdown",
6939
7388
  category: "shell",
6940
- description: "접기/펼치기를 지원하는 compound 사이드바. Header/Content/Footer 구조 + Group/Item으로 네비게이션 구성. Ctrl+[ 단축키.",
6941
- slug: "sidebar",
6942
- exports: ["Sidebar", "useSidebar"],
6943
- keyboardShortcuts: [
7389
+ functionalCategory: "data-display",
7390
+ maturity: "stable",
7391
+ keywords: [
7392
+ "score",
7393
+ "engagement",
7394
+ "health",
7395
+ "rubric",
7396
+ "breakdown"
7397
+ ],
7398
+ description: "합성 점수를 축별로 분해해 보여준다. 총점과 함께 각 축의 값·가중치·의미를 노출해 '43'을 판정이 아니라 발견으로 만든다.",
7399
+ slug: "score-breakdown",
7400
+ exports: ["ScoreBreakdown"],
7401
+ props: { ScoreBreakdown: [
6944
7402
  {
6945
- keys: "Ctrl+[",
6946
- action: "사이드바 접기/펼치기 토글"
7403
+ name: "score",
7404
+ type: "number",
7405
+ required: true,
7406
+ description: "총점"
6947
7407
  },
6948
7408
  {
6949
- keys: "↑ ↓",
6950
- action: "사이드바 항목 간 이동"
7409
+ name: "max",
7410
+ type: "number",
7411
+ default: "100",
7412
+ description: "총점 상한"
6951
7413
  },
6952
7414
  {
6953
- keys: "Home / End",
7415
+ name: "axes",
7416
+ type: "ScoreAxis[]",
7417
+ required: true,
7418
+ description: "기여 축 배열. { id, label, value, hint?, weight? }"
7419
+ },
7420
+ {
7421
+ name: "bands",
7422
+ type: "ScoreBand[]",
7423
+ description: "점수 구간 이름과 색. { min, label, tone? }"
7424
+ },
7425
+ {
7426
+ name: "formula",
7427
+ type: "ReactNode",
7428
+ description: "총점 산출식 설명 한 줄"
7429
+ },
7430
+ {
7431
+ name: "loading",
7432
+ type: "boolean",
7433
+ default: "false",
7434
+ description: "스켈레톤 표시"
7435
+ },
7436
+ {
7437
+ name: "labels",
7438
+ type: "ScoreBreakdownLabels",
7439
+ description: "문구 오버라이드"
7440
+ }
7441
+ ] },
7442
+ usage: {
7443
+ bestPractices: ["축의 weight 합이 1이 되도록 넘겨 총점이 검산 가능하게 한다.", "각 축에 hint를 붙여 낮은 값이 무엇을 뜻하는지 설명한다."],
7444
+ avoid: ["축 없이 총점만 넘기지 않는다 — 그건 StatCard의 일이다."]
7445
+ },
7446
+ testDescribe: "ScoreBreakdown"
7447
+ },
7448
+ {
7449
+ name: "Flyout",
7450
+ category: "shell",
7451
+ functionalCategory: "overlay",
7452
+ maturity: "stable",
7453
+ keywords: [
7454
+ "panel",
7455
+ "detail",
7456
+ "preview",
7457
+ "side-panel",
7458
+ "inspector"
7459
+ ],
7460
+ description: "목록을 화면에 둔 채 레코드를 옆에서 보여주는 상세 패널. 모달이 아니다 — 포커스 트랩·aria-modal·inert 없이 Escape로 닫고 포커스를 되돌린다.",
7461
+ slug: "flyout",
7462
+ exports: ["Flyout"],
7463
+ props: { Flyout: [
7464
+ {
7465
+ name: "open",
7466
+ type: "boolean",
7467
+ required: true,
7468
+ description: "열림 상태"
7469
+ },
7470
+ {
7471
+ name: "onClose",
7472
+ type: "() => void",
7473
+ required: true,
7474
+ description: "닫기 핸들러"
7475
+ },
7476
+ {
7477
+ name: "title",
7478
+ type: "ReactNode",
7479
+ description: "패널 제목"
7480
+ },
7481
+ {
7482
+ name: "description",
7483
+ type: "ReactNode",
7484
+ description: "제목 아래 한 줄 설명"
7485
+ },
7486
+ {
7487
+ name: "type",
7488
+ type: "\"overlay\" | \"inline\"",
7489
+ default: "\"overlay\"",
7490
+ description: "overlay는 페이지 위에 띄우고, inline은 aside로 렌더해 배치를 부모 레이아웃에 맡긴다"
7491
+ },
7492
+ {
7493
+ name: "side",
7494
+ type: "\"right\" | \"left\"",
7495
+ default: "\"right\"",
7496
+ description: "overlay일 때 붙는 방향"
7497
+ },
7498
+ {
7499
+ name: "size",
7500
+ type: "\"sm\" | \"md\" | \"lg\"",
7501
+ default: "\"md\"",
7502
+ description: "패널 너비"
7503
+ },
7504
+ {
7505
+ name: "actions",
7506
+ type: "ReactNode",
7507
+ description: "닫기 버튼 앞 컨트롤"
7508
+ },
7509
+ {
7510
+ name: "footer",
7511
+ type: "ReactNode",
7512
+ description: "하단 고정 영역"
7513
+ },
7514
+ {
7515
+ name: "scrim",
7516
+ type: "boolean",
7517
+ default: "false",
7518
+ description: "뒤 페이지를 어둡게. 기본 off — 목록이 계속 읽혀야 한다"
7519
+ },
7520
+ {
7521
+ name: "closeOnOutsideClick",
7522
+ type: "boolean",
7523
+ default: "true",
7524
+ description: "바깥 클릭 시 닫기 (overlay 전용)"
7525
+ },
7526
+ {
7527
+ name: "hideCloseButton",
7528
+ type: "boolean",
7529
+ default: "false",
7530
+ description: "닫기 버튼 숨김"
7531
+ },
7532
+ {
7533
+ name: "labels",
7534
+ type: "FlyoutLabels",
7535
+ description: "문구 오버라이드"
7536
+ },
7537
+ {
7538
+ name: "children",
7539
+ type: "ReactNode",
7540
+ required: true,
7541
+ description: "패널 본문"
7542
+ }
7543
+ ] },
7544
+ keyboardShortcuts: [{
7545
+ keys: "Escape",
7546
+ action: "패널 닫고 여는 컨트롤로 포커스 복귀"
7547
+ }],
7548
+ usage: {
7549
+ bestPractices: ["목록에서 행을 미리 볼 때 쓴다. 딥링크가 필요한 상세는 별도 페이지를 함께 유지한다.", "넓은 화면에서는 type=\"inline\"으로 목록 옆에 나란히 둔다."],
7550
+ avoid: ["확인이 필요한 파괴적 작업에는 쓰지 않는다 — 그건 Dialog다."]
7551
+ },
7552
+ testDescribe: "Flyout"
7553
+ },
7554
+ {
7555
+ name: "ComparisonSelector",
7556
+ category: "shell",
7557
+ description: "비교 선택기. 고유 레이블이 연결된 Select × 2 + Swap 버튼 조합. CohortComparison, FunnelAnalysis 등에서 사용합니다.",
7558
+ slug: "comparison-selector",
7559
+ exports: ["ComparisonSelector"],
7560
+ props: { ComparisonSelector: [
7561
+ {
7562
+ name: "segments",
7563
+ type: "ComparisonOption[]",
7564
+ required: true,
7565
+ description: "선택 가능한 세그먼트 목록"
7566
+ },
7567
+ {
7568
+ name: "valueA",
7569
+ type: "string",
7570
+ description: "Base 선택값"
7571
+ },
7572
+ {
7573
+ name: "valueB",
7574
+ type: "string",
7575
+ description: "Compare 선택값"
7576
+ },
7577
+ {
7578
+ name: "onChangeA",
7579
+ type: "(value: string) => void",
7580
+ description: "Base 변경 핸들러"
7581
+ },
7582
+ {
7583
+ name: "onChangeB",
7584
+ type: "(value: string) => void",
7585
+ description: "Compare 변경 핸들러"
7586
+ },
7587
+ {
7588
+ name: "labels",
7589
+ type: "ComparisonSelectorLabels",
7590
+ description: "i18n용 라벨 오버라이드"
7591
+ }
7592
+ ] },
7593
+ dependencies: [{
7594
+ slug: "select",
7595
+ category: "core"
7596
+ }, {
7597
+ slug: "button",
7598
+ category: "core"
7599
+ }],
7600
+ testDescribe: "ComparisonSelector"
7601
+ },
7602
+ {
7603
+ name: "ThemeSwitcher",
7604
+ category: "shell",
7605
+ description: "테마 프리셋 선택기. 프리셋과 light/dark/system 모드 전환.",
7606
+ slug: "theme-switcher",
7607
+ exports: ["ThemeSwitcher"],
7608
+ props: { ThemeSwitcher: [{
7609
+ name: "labels",
7610
+ type: "ThemeSwitcherLabels",
7611
+ description: "i18n용 라벨 오버라이드"
7612
+ }] },
7613
+ testDescribe: "ThemeSwitcher"
7614
+ },
7615
+ {
7616
+ name: "PromptTemplate",
7617
+ category: "shell",
7618
+ description: "AI 프롬프트 뷰어/헬퍼. 변수 입력 → 실시간 치환 → 복사 기능.",
7619
+ slug: "prompt-template",
7620
+ exports: ["PromptTemplate"],
7621
+ props: { PromptTemplate: [
7622
+ {
7623
+ name: "title",
7624
+ type: "string",
7625
+ required: true,
7626
+ description: "프롬프트 제목"
7627
+ },
7628
+ {
7629
+ name: "description",
7630
+ type: "string",
7631
+ description: "프롬프트 설명 (헤더 아래 표시)"
7632
+ },
7633
+ {
7634
+ name: "template",
7635
+ type: "string",
7636
+ required: true,
7637
+ description: "프롬프트 템플릿. {변수명}으로 치환 가능"
7638
+ },
7639
+ {
7640
+ name: "variables",
7641
+ type: "PromptVariable[]",
7642
+ description: "변수 정의 배열. { name, label, placeholder?, defaultValue? }"
7643
+ },
7644
+ {
7645
+ name: "labels",
7646
+ type: "PromptTemplateLabels",
7647
+ description: "i18n용 라벨 오버라이드"
7648
+ }
7649
+ ] },
7650
+ dependencies: [{
7651
+ slug: "input",
7652
+ category: "core"
7653
+ }],
7654
+ testDescribe: "PromptTemplate"
7655
+ },
7656
+ {
7657
+ name: "Sidebar",
7658
+ category: "shell",
7659
+ description: "접기/펼치기를 지원하는 compound 사이드바. Header/Content/Footer 구조 + Group/Item으로 네비게이션 구성. Ctrl+[ 단축키.",
7660
+ slug: "sidebar",
7661
+ exports: ["Sidebar", "useSidebar"],
7662
+ keyboardShortcuts: [
7663
+ {
7664
+ keys: "Ctrl+[",
7665
+ action: "사이드바 접기/펼치기 토글"
7666
+ },
7667
+ {
7668
+ keys: "↑ ↓",
7669
+ action: "사이드바 항목 간 이동"
7670
+ },
7671
+ {
7672
+ keys: "Home / End",
6954
7673
  action: "첫 번째/마지막 사이드바 항목으로 이동"
6955
7674
  }
6956
7675
  ],
@@ -7188,7 +7907,7 @@ const filters = [
7188
7907
  props: { AsyncCombobox: [
7189
7908
  {
7190
7909
  name: "onSearch",
7191
- type: "(q: string) => Promise<string[]>",
7910
+ type: "(query: string) => Promise<string[]>",
7192
7911
  required: true,
7193
7912
  description: "검색 콜백"
7194
7913
  },
@@ -7208,7 +7927,11 @@ const filters = [
7208
7927
  name: "placeholder",
7209
7928
  type: "string",
7210
7929
  default: "\"Search…\"",
7211
- description: "플레이스홀더"
7930
+ description: "플레이스홀더",
7931
+ deprecated: {
7932
+ since: "1.9.0",
7933
+ replacement: "labels.placeholder"
7934
+ }
7212
7935
  },
7213
7936
  {
7214
7937
  name: "onSelect",
@@ -7223,7 +7946,11 @@ const filters = [
7223
7946
  {
7224
7947
  name: "loadingText",
7225
7948
  type: "string",
7226
- description: "로딩 문구. labels.loading 사용 권장"
7949
+ description: "로딩 문구",
7950
+ deprecated: {
7951
+ since: "1.9.0",
7952
+ replacement: "labels.loading"
7953
+ }
7227
7954
  }
7228
7955
  ] },
7229
7956
  dependencies: [{
@@ -7259,6 +7986,14 @@ const filters = [
7259
7986
  description: "커스텀 CSS 클래스"
7260
7987
  }
7261
7988
  ] },
7989
+ ariaRole: "tablist",
7990
+ keyboardShortcuts: [{
7991
+ keys: "↑ ↓",
7992
+ action: "세로 탭 사이 이동 및 선택"
7993
+ }, {
7994
+ keys: "Home / End",
7995
+ action: "첫 번째 또는 마지막 탭으로 이동"
7996
+ }],
7262
7997
  dependencies: [{
7263
7998
  slug: "tabs",
7264
7999
  category: "core"
@@ -7331,12 +8066,30 @@ const filters = [
7331
8066
  default: "24",
7332
8067
  description: "들여쓰기 크기 (px)"
7333
8068
  },
8069
+ {
8070
+ name: "getRowLabel",
8071
+ type: "(node: TreeNode<T>) => string",
8072
+ description: "행별 펼침/접기 컨트롤의 접근 가능한 이름 생성기"
8073
+ },
8074
+ {
8075
+ name: "aria-label",
8076
+ type: "string",
8077
+ description: "표의 접근 가능한 이름"
8078
+ },
7334
8079
  {
7335
8080
  name: "className",
7336
8081
  type: "string",
7337
8082
  description: "커스텀 CSS 클래스"
7338
8083
  }
7339
8084
  ] },
8085
+ ariaRole: "table",
8086
+ keyboardShortcuts: [{
8087
+ keys: "Tab",
8088
+ action: "펼침/접기 컨트롤 사이 이동"
8089
+ }, {
8090
+ keys: "Enter / Space",
8091
+ action: "하위 행 펼치기 또는 접기"
8092
+ }],
7340
8093
  dependencies: [{
7341
8094
  slug: "data-table",
7342
8095
  category: "shells"
@@ -7400,7 +8153,7 @@ const filters = [
7400
8153
  },
7401
8154
  {
7402
8155
  name: "onDragEnd",
7403
- type: "(itemId, fromCol, toCol, toIndex) => void",
8156
+ type: "(itemId: string, fromColumnId: string, toColumnId: string, toIndex: number) => void",
7404
8157
  required: true,
7405
8158
  description: "드래그 완료 콜백"
7406
8159
  },
@@ -7636,7 +8389,7 @@ const filters = [
7636
8389
  },
7637
8390
  {
7638
8391
  name: "onImport",
7639
- type: "(mapping, data) => void",
8392
+ type: "(mapping: Record<string, string>, data: string[][]) => void",
7640
8393
  required: true,
7641
8394
  description: "임포트 확정 콜백"
7642
8395
  },
@@ -7688,12 +8441,32 @@ const filters = [
7688
8441
  default: "\"Menu\"",
7689
8442
  description: "트리거 버튼 텍스트"
7690
8443
  },
8444
+ {
8445
+ name: "aria-label",
8446
+ type: "string",
8447
+ description: "열린 메가 메뉴 탐색 영역의 접근 가능한 이름"
8448
+ },
7691
8449
  {
7692
8450
  name: "className",
7693
8451
  type: "string",
7694
8452
  description: "커스텀 CSS 클래스"
7695
8453
  }
7696
8454
  ] },
8455
+ ariaRole: "navigation",
8456
+ keyboardShortcuts: [
8457
+ {
8458
+ keys: "Enter / Space",
8459
+ action: "메가 메뉴 열기"
8460
+ },
8461
+ {
8462
+ keys: "Tab",
8463
+ action: "메뉴 링크와 액션 사이 이동"
8464
+ },
8465
+ {
8466
+ keys: "Escape",
8467
+ action: "메가 메뉴 닫기"
8468
+ }
8469
+ ],
7697
8470
  dependencies: [{
7698
8471
  slug: "popover",
7699
8472
  category: "core"
@@ -8290,6 +9063,16 @@ const messages = [
8290
9063
  slug: "time-series-panel",
8291
9064
  exports: ["TimeSeriesPanel"],
8292
9065
  props: { TimeSeriesPanel: [
9066
+ {
9067
+ name: "title",
9068
+ type: "ReactNode",
9069
+ description: "패널 제목. aria-label이 없으면 region 이름으로 연결됩니다"
9070
+ },
9071
+ {
9072
+ name: "description",
9073
+ type: "ReactNode",
9074
+ description: "패널 보조 설명"
9075
+ },
8293
9076
  {
8294
9077
  name: "data",
8295
9078
  type: "TimeSeriesPoint[]",
@@ -8336,6 +9119,11 @@ const messages = [
8336
9119
  type: "number",
8337
9120
  default: "280",
8338
9121
  description: "차트 높이"
9122
+ },
9123
+ {
9124
+ name: "aria-label",
9125
+ type: "string",
9126
+ description: "제목 대신 사용할 패널 region의 접근 가능한 이름"
8339
9127
  }
8340
9128
  ] },
8341
9129
  ariaRole: "region",
@@ -8519,6 +9307,354 @@ const messages = [
8519
9307
  ] },
8520
9308
  ariaRole: "complementary",
8521
9309
  testDescribe: "FieldSidebar"
9310
+ },
9311
+ {
9312
+ name: "InspectorLayout",
9313
+ category: "shells",
9314
+ description: "목록과 인스펙터의 2-pane 레이아웃. 넓으면 밀어내고(push) 좁으면 덮으며(overlay), 닫을 때 포커스를 열었던 자리로 되돌립니다.",
9315
+ slug: "inspector-layout",
9316
+ exports: ["InspectorLayout"],
9317
+ props: { InspectorLayout: [
9318
+ {
9319
+ name: "children",
9320
+ type: "ReactNode",
9321
+ required: true,
9322
+ description: "목록. 인스펙터가 열려도 마운트와 스크롤을 유지"
9323
+ },
9324
+ {
9325
+ name: "inspector",
9326
+ type: "ReactNode",
9327
+ description: "인스펙터 내용. 닫혀 있으면 렌더하지 않음"
9328
+ },
9329
+ {
9330
+ name: "open",
9331
+ type: "boolean",
9332
+ required: true,
9333
+ description: "열림"
9334
+ },
9335
+ {
9336
+ name: "onClose",
9337
+ type: "() => void",
9338
+ required: true,
9339
+ description: "닫기"
9340
+ },
9341
+ {
9342
+ name: "title",
9343
+ type: "ReactNode",
9344
+ description: "인스펙터 제목"
9345
+ },
9346
+ {
9347
+ name: "description",
9348
+ type: "ReactNode",
9349
+ description: "제목 아래 한 줄"
9350
+ },
9351
+ {
9352
+ name: "actions",
9353
+ type: "ReactNode",
9354
+ description: "닫기 버튼 옆 컨트롤"
9355
+ },
9356
+ {
9357
+ name: "footer",
9358
+ type: "ReactNode",
9359
+ description: "인스펙터 하단"
9360
+ },
9361
+ {
9362
+ name: "breakpoint",
9363
+ type: "\"lg\" | \"xl\" | \"2xl\"",
9364
+ default: "\"xl\"",
9365
+ description: "이 폭 이상이면 push"
9366
+ },
9367
+ {
9368
+ name: "width",
9369
+ type: "\"sm\" | \"md\" | \"lg\"",
9370
+ default: "\"md\"",
9371
+ description: "push 레이아웃에서의 인스펙터 폭"
9372
+ }
9373
+ ] },
9374
+ usage: {
9375
+ bestPractices: ["목록 → 상세 → 목록 왕복이 잦은 화면에 씁니다. 다이얼로그는 목록을 가져가 버립니다."],
9376
+ avoid: ["확인이 필요한 파괴적 작업에는 ConfirmDialog를 씁니다.", "포커스 복귀를 직접 다루지 않습니다. Flyout이 소유하며, 부모가 겹쳐 잡으면 패널을 열었던 요소로 오인합니다."]
9377
+ },
9378
+ testDescribe: "InspectorLayout"
9379
+ },
9380
+ {
9381
+ name: "QueryFilterBar",
9382
+ category: "shells",
9383
+ description: "QueryBar와 필터 버튼이 하나의 Query를 공유합니다. 버튼은 상태를 갖지 않고 Query를 읽어 활성 여부를 계산합니다.",
9384
+ slug: "query-filter-bar",
9385
+ exports: ["QueryFilterBar", "hasActiveQuery"],
9386
+ props: { QueryFilterBar: [
9387
+ {
9388
+ name: "value",
9389
+ type: "Query",
9390
+ required: true,
9391
+ description: "필터 상태 전부"
9392
+ },
9393
+ {
9394
+ name: "onChange",
9395
+ type: "(query: Query) => void",
9396
+ required: true,
9397
+ description: "Query 변경"
9398
+ },
9399
+ {
9400
+ name: "filters",
9401
+ type: "QueryFilterDef[]",
9402
+ required: true,
9403
+ description: "필터 컨트롤 정의 (is / field_value_selection / field_value_toggle)"
9404
+ },
9405
+ {
9406
+ name: "fields",
9407
+ type: "QueryField[]",
9408
+ description: "텍스트 입력 자동완성"
9409
+ },
9410
+ {
9411
+ name: "actions",
9412
+ type: "ReactNode",
9413
+ description: "필터 행 끝 슬롯"
9414
+ },
9415
+ {
9416
+ name: "showClauses",
9417
+ type: "boolean",
9418
+ default: "true",
9419
+ description: "절을 제거 가능한 칩으로 표시"
9420
+ },
9421
+ {
9422
+ name: "live",
9423
+ type: "boolean",
9424
+ default: "false",
9425
+ description: "매 입력마다 커밋"
9426
+ },
9427
+ {
9428
+ name: "disabled",
9429
+ type: "boolean",
9430
+ default: "false",
9431
+ description: "전체 비활성"
9432
+ },
9433
+ {
9434
+ name: "queryBarLabels",
9435
+ type: "QueryBarLabels",
9436
+ description: "QueryBar로 통과되는 나머지 문구. placeholder/ariaLabel은 labels가 이깁니다"
9437
+ }
9438
+ ] },
9439
+ usage: {
9440
+ bestPractices: ["URL에 Query 한 줄만 실어 필터 상태 전체를 공유 가능하게 만듭니다."],
9441
+ avoid: ["필터 버튼이 자체 상태를 갖게 하지 않습니다. 진실 원천이 둘로 갈라집니다."]
9442
+ },
9443
+ testDescribe: "QueryFilterBar"
9444
+ },
9445
+ {
9446
+ name: "CatalogFrame",
9447
+ category: "shells",
9448
+ description: "카탈로그 목록의 공통 뼈대. 헤더·통계·고지·필터·인스펙터·페이지네이션·일괄 작업과 빈/에러 상태를 담고, 필터나 페이지가 바뀌면 선택을 비웁니다.",
9449
+ slug: "catalog-frame",
9450
+ exports: [
9451
+ "CatalogFrame",
9452
+ "CatalogRowActions",
9453
+ "CatalogCellControls"
9454
+ ],
9455
+ props: {
9456
+ CatalogFrame: [
9457
+ {
9458
+ name: "children",
9459
+ type: "ReactNode",
9460
+ required: true,
9461
+ description: "테이블. 빈/에러 상태에서는 대체됨"
9462
+ },
9463
+ {
9464
+ name: "isEmpty",
9465
+ type: "boolean",
9466
+ required: true,
9467
+ description: "테이블이 0행인지"
9468
+ },
9469
+ {
9470
+ name: "query",
9471
+ type: "Query",
9472
+ required: true,
9473
+ description: "필터 상태"
9474
+ },
9475
+ {
9476
+ name: "onQueryChange",
9477
+ type: "(query: Query) => void",
9478
+ required: true,
9479
+ description: "Query 변경"
9480
+ },
9481
+ {
9482
+ name: "filters",
9483
+ type: "QueryFilterDef[]",
9484
+ description: "필터 정의"
9485
+ },
9486
+ {
9487
+ name: "stats",
9488
+ type: "StatCardType[]",
9489
+ description: "통계 카드"
9490
+ },
9491
+ {
9492
+ name: "onStatClick",
9493
+ type: "(stat: StatCardType) => void",
9494
+ description: "카드 클릭 = 필터"
9495
+ },
9496
+ {
9497
+ name: "notice",
9498
+ type: "ReactNode",
9499
+ description: "목록 위 고지"
9500
+ },
9501
+ {
9502
+ name: "emptyActions",
9503
+ type: "ReactNode",
9504
+ description: "필터 없는 빈 상태의 다음 행동 (필터된 빈 상태는 필터 지우기 유지)"
9505
+ },
9506
+ {
9507
+ name: "header",
9508
+ type: "ReactNode",
9509
+ description: "헤더 슬롯"
9510
+ },
9511
+ {
9512
+ name: "actions",
9513
+ type: "ReactNode",
9514
+ description: "액션 슬롯"
9515
+ },
9516
+ {
9517
+ name: "toolbar",
9518
+ type: "ReactNode",
9519
+ description: "통계와 필터 사이 컨트롤 (스코프 세그먼트 등)"
9520
+ },
9521
+ {
9522
+ name: "inspector",
9523
+ type: "ReactNode",
9524
+ description: "인스펙터 내용"
9525
+ },
9526
+ {
9527
+ name: "inspectorOpen",
9528
+ type: "boolean",
9529
+ required: true,
9530
+ description: "인스펙터 열림"
9531
+ },
9532
+ {
9533
+ name: "onInspectorClose",
9534
+ type: "() => void",
9535
+ required: true,
9536
+ description: "인스펙터 닫기"
9537
+ },
9538
+ {
9539
+ name: "inspectorTitle",
9540
+ type: "ReactNode",
9541
+ description: "인스펙터 제목"
9542
+ },
9543
+ {
9544
+ name: "inspectorDescription",
9545
+ type: "ReactNode",
9546
+ description: "인스펙터 설명"
9547
+ },
9548
+ {
9549
+ name: "inspectorActions",
9550
+ type: "ReactNode",
9551
+ description: "인스펙터 헤더 컨트롤"
9552
+ },
9553
+ {
9554
+ name: "inspectorFooter",
9555
+ type: "ReactNode",
9556
+ description: "인스펙터 하단"
9557
+ },
9558
+ {
9559
+ name: "inspectorWidth",
9560
+ type: "\"sm\" | \"md\" | \"lg\"",
9561
+ description: "push 레이아웃 폭"
9562
+ },
9563
+ {
9564
+ name: "page",
9565
+ type: "number",
9566
+ default: "1",
9567
+ description: "현재 페이지"
9568
+ },
9569
+ {
9570
+ name: "pageSize",
9571
+ type: "number",
9572
+ default: "50",
9573
+ description: "페이지 크기"
9574
+ },
9575
+ {
9576
+ name: "totalCount",
9577
+ type: "number",
9578
+ description: "필터 반영 총 개수"
9579
+ },
9580
+ {
9581
+ name: "onPageChange",
9582
+ type: "(page: number) => void",
9583
+ description: "페이지 변경"
9584
+ },
9585
+ {
9586
+ name: "selectedKeys",
9587
+ type: "string[]",
9588
+ description: "선택된 행"
9589
+ },
9590
+ {
9591
+ name: "onSelectionChange",
9592
+ type: "(keys: string[]) => void",
9593
+ description: "선택 변경. 필터·페이지가 바뀌면 프레임이 비웁니다"
9594
+ },
9595
+ {
9596
+ name: "bulkActions",
9597
+ type: "FloatingAction[]",
9598
+ description: "일괄 작업"
9599
+ },
9600
+ {
9601
+ name: "onBulkAction",
9602
+ type: "(actionId: string) => void",
9603
+ description: "일괄 작업 실행"
9604
+ },
9605
+ {
9606
+ name: "error",
9607
+ type: "ReactNode",
9608
+ description: "읽기 실패. 빈 목록과 다른 답"
9609
+ },
9610
+ {
9611
+ name: "onRetry",
9612
+ type: "() => void",
9613
+ description: "재시도"
9614
+ },
9615
+ {
9616
+ name: "labels",
9617
+ type: "CatalogFrameLabels",
9618
+ description: "빈/필터된 빈/에러 문구"
9619
+ }
9620
+ ],
9621
+ CatalogRowActions: [
9622
+ {
9623
+ name: "item",
9624
+ type: "T",
9625
+ required: true,
9626
+ description: "이 행의 항목"
9627
+ },
9628
+ {
9629
+ name: "actions",
9630
+ type: "CatalogRowAction<T>[]",
9631
+ required: true,
9632
+ description: "행 액션"
9633
+ },
9634
+ {
9635
+ name: "visibleCount",
9636
+ type: "number",
9637
+ default: "2",
9638
+ description: "오버플로 전에 노출할 개수. available 필터링 후에 적용됨"
9639
+ },
9640
+ {
9641
+ name: "labels",
9642
+ type: "CatalogRowActionsLabels",
9643
+ description: "그룹·오버플로 라벨"
9644
+ }
9645
+ ],
9646
+ CatalogCellControls: [{
9647
+ name: "children",
9648
+ type: "ReactNode",
9649
+ required: true,
9650
+ description: "행 클릭으로 새면 안 되는 셀 컨트롤"
9651
+ }]
9652
+ },
9653
+ usage: {
9654
+ bestPractices: ["카탈로그 성격의 목록 화면은 이 프레임 위에 컬럼 정의와 labels만 얹습니다."],
9655
+ avoid: ["선택 초기화를 호출자에게 맡기지 않습니다 — 보이지 않는 행에 일괄 작업이 적용됩니다."]
9656
+ },
9657
+ testDescribe: "CatalogFrame"
8522
9658
  }
8523
9659
  ];
8524
9660
  //#endregion