@wipco/sdui 0.50.1 → 1.0.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 (111) hide show
  1. package/dist/{DatePicker-BK_jsV7i.d.ts → DatePicker-C_TT_SOw.d.ts} +25 -0
  2. package/dist/{chunk-OEWTVLPZ.js → chunk-2HLXCKK7.js} +2 -2
  3. package/dist/{chunk-PWVHAUJC.js → chunk-2YP5N4VV.js} +6 -4
  4. package/dist/{chunk-QTHYLNJW.js → chunk-37SFFW37.js} +2 -13
  5. package/dist/{chunk-YQOMSWW5.js → chunk-5WW4MBRO.js} +1 -1
  6. package/dist/{chunk-LIOS3PF2.js → chunk-5XPZ3RSA.js} +3 -4
  7. package/dist/{chunk-ZD3VNVRF.js → chunk-7BGDSSAN.js} +5 -13
  8. package/dist/{chunk-Y2LIEWQX.js → chunk-7FZTUI6U.js} +6 -5
  9. package/dist/{chunk-HJ5VSXR4.js → chunk-7P7N43JV.js} +2 -2
  10. package/dist/{chunk-55VB6SDU.js → chunk-A5XQOEVH.js} +23 -20
  11. package/dist/{chunk-77LKJCMM.js → chunk-A6LAIGUS.js} +20 -108
  12. package/dist/{chunk-YXS5FXVS.js → chunk-AC5TI3BE.js} +7 -16
  13. package/dist/{chunk-AIOJYVJ2.js → chunk-AXGF4M57.js} +1 -1
  14. package/dist/{chunk-5MPK3NZK.js → chunk-B6WMC2AN.js} +3 -3
  15. package/dist/{chunk-C6ZV4VBN.js → chunk-C37U3GCC.js} +4 -4
  16. package/dist/{chunk-265YPW4W.js → chunk-DMLDKW7K.js} +1 -1
  17. package/dist/{chunk-FZ72IDVF.js → chunk-E6ISNQK3.js} +2 -2
  18. package/dist/{chunk-STX2AX6Y.js → chunk-FLAN7CV3.js} +7 -39
  19. package/dist/{chunk-IV5YTVHR.js → chunk-GE6BYNQJ.js} +18 -33
  20. package/dist/{chunk-ZX36LKJ7.js → chunk-GTGS2QRL.js} +7 -6
  21. package/dist/{chunk-NL3I6F2I.js → chunk-HBBTENSV.js} +3 -3
  22. package/dist/{chunk-RUU7WBGR.js → chunk-I45EUZC6.js} +19 -9
  23. package/dist/{chunk-JBKNIALK.js → chunk-I5SD7XRA.js} +9 -6
  24. package/dist/{chunk-X7GHY2RI.js → chunk-IFKYGJXP.js} +14 -42
  25. package/dist/{chunk-J3YMUR6R.js → chunk-IZ3JA2BR.js} +3 -3
  26. package/dist/{chunk-AUW5RP24.js → chunk-JIV4YHCZ.js} +7 -6
  27. package/dist/{chunk-M6KM3FJS.js → chunk-JYAR7D5U.js} +2 -2
  28. package/dist/{chunk-DKPNFEVU.js → chunk-KO6OEY3G.js} +19 -6
  29. package/dist/{chunk-JHHMFL5D.js → chunk-LFEJLOFN.js} +28 -16
  30. package/dist/chunk-MMIGOPO5.js +111 -0
  31. package/dist/{chunk-YSSNP6JM.js → chunk-N25YP7ID.js} +6 -21
  32. package/dist/{chunk-F2W2FWOT.js → chunk-N5RKM6IC.js} +7 -7
  33. package/dist/{chunk-C4HYWZML.js → chunk-O4IISNE7.js} +1 -1
  34. package/dist/chunk-Q4SWUINA.js +51 -0
  35. package/dist/{chunk-TUOG7EEI.js → chunk-RNOUXUBO.js} +418 -22
  36. package/dist/{chunk-4VZI3PLI.js → chunk-RYHEV5E3.js} +15 -39
  37. package/dist/{chunk-AVRNQOB5.js → chunk-SAPBDJJQ.js} +14 -13
  38. package/dist/{chunk-OPJ4DYNI.js → chunk-SJ3737RA.js} +156 -158
  39. package/dist/{chunk-UMANGYRD.js → chunk-TFGNKFL2.js} +1 -1
  40. package/dist/{chunk-KYGSMUUU.js → chunk-VY7MVBSE.js} +2 -2
  41. package/dist/{chunk-VDBUXYEB.js → chunk-WOYMEEPH.js} +8 -8
  42. package/dist/{chunk-I5Z2RVDF.js → chunk-WSLT32Q4.js} +278 -30
  43. package/dist/{chunk-NYUHSMUU.js → chunk-XGFDY43I.js} +2 -2
  44. package/dist/{chunk-ODKZF4IT.js → chunk-YOZN35HD.js} +17 -9
  45. package/dist/{chunk-EKP6Z3G6.js → chunk-YVTV4ZV6.js} +1 -1
  46. package/dist/{chunk-6BCWFPNJ.js → chunk-YWUDBLO6.js} +2 -2
  47. package/dist/chunk-Z4PYMTTS.js +41 -0
  48. package/dist/{chunk-WXIQLWIX.js → chunk-ZFNFPXPR.js} +2 -2
  49. package/dist/{chunk-7GOWOSX6.js → chunk-ZORQPB6B.js} +11 -5
  50. package/dist/index.d.ts +1096 -941
  51. package/dist/index.js +369 -610
  52. package/dist/meta.d.ts +13 -12
  53. package/dist/meta.js +84 -16
  54. package/dist/rich/ExcelUploadCard.d.ts +7 -0
  55. package/dist/rich/ExcelUploadCard.js +17 -13
  56. package/dist/rich.d.ts +1 -1
  57. package/dist/rich.js +25 -12
  58. package/dist/styles.css +10 -2
  59. package/dist/{types-D14eqLpf.d.ts → types-C7y9CpgF.d.ts} +7 -5
  60. package/dist/widgets/ApprovalLineEditor.js +7 -6
  61. package/dist/widgets/ApprovalOverridesEditor.d.ts +11 -1
  62. package/dist/widgets/ApprovalOverridesEditor.js +9 -8
  63. package/dist/widgets/ApprovalStepper.js +3 -3
  64. package/dist/widgets/AutocompleteField.d.ts +1 -0
  65. package/dist/widgets/AutocompleteField.js +7 -6
  66. package/dist/widgets/ColumnBoard.js +3 -3
  67. package/dist/widgets/DiffView.js +3 -3
  68. package/dist/widgets/Disclosure.js +3 -3
  69. package/dist/widgets/EventCalendar.d.ts +16 -1
  70. package/dist/widgets/EventCalendar.js +3 -3
  71. package/dist/widgets/FileDropzone.js +7 -6
  72. package/dist/widgets/ImageLightbox.js +5 -4
  73. package/dist/widgets/InlineEdit.js +3 -3
  74. package/dist/widgets/JsonField.js +3 -3
  75. package/dist/widgets/LiveClock.js +2 -2
  76. package/dist/widgets/OptionSchemaEditor.js +4 -4
  77. package/dist/widgets/OptionValueFields.d.ts +5 -0
  78. package/dist/widgets/OptionValueFields.js +4 -4
  79. package/dist/widgets/OrgChart.js +4 -3
  80. package/dist/widgets/OrgChartBoard.d.ts +93 -4
  81. package/dist/widgets/OrgChartBoard.js +9 -6
  82. package/dist/widgets/PeriodFilter.js +4 -4
  83. package/dist/widgets/PermissionMatrixEditor.d.ts +16 -0
  84. package/dist/widgets/PermissionMatrixEditor.js +4 -4
  85. package/dist/widgets/PositionSelect.d.ts +6 -1
  86. package/dist/widgets/PositionSelect.js +5 -4
  87. package/dist/widgets/PrintSheet.js +3 -3
  88. package/dist/widgets/ProfileEditDiffCard.d.ts +6 -0
  89. package/dist/widgets/ProfileEditDiffCard.js +4 -4
  90. package/dist/widgets/QRDisplay.js +4 -4
  91. package/dist/widgets/RankBoard.d.ts +6 -0
  92. package/dist/widgets/RankBoard.js +6 -5
  93. package/dist/widgets/RecordSheet.js +2 -2
  94. package/dist/widgets/RoleMemberPicker.d.ts +11 -5
  95. package/dist/widgets/RoleMemberPicker.js +5 -4
  96. package/dist/widgets/SearchableMultiPicker.js +5 -5
  97. package/dist/widgets/SecureScreen.d.ts +12 -0
  98. package/dist/widgets/SecureScreen.js +3 -3
  99. package/dist/widgets/StackedMeter.js +2 -2
  100. package/dist/widgets/StagePeriodEditor.js +4 -5
  101. package/dist/widgets/Strip.js +2 -2
  102. package/dist/widgets/SummaryTable.js +2 -2
  103. package/dist/widgets/TagMultiSelect.d.ts +0 -1
  104. package/dist/widgets/TagMultiSelect.js +4 -4
  105. package/dist/widgets/Timeline.js +4 -4
  106. package/dist/widgets/TreePicker.js +6 -6
  107. package/dist/widgets/TreeView.js +5 -5
  108. package/dist/widgets/index.d.ts +3 -3
  109. package/dist/widgets/index.js +81 -79
  110. package/package.json +1 -1
  111. package/dist/chunk-EKVA7T7P.js +0 -198
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { CSSProperties, ReactNode, ComponentType } from 'react';
3
- import { S as SduiPage, a as SduiAction, b as SduiPropValue, c as SduiRule } from './types-D14eqLpf.js';
4
- export { d as SduiArgRef, e as SduiCalcRef, f as SduiChild, g as SduiIndexRef, h as SduiItemRef, i as SduiNode, j as SduiPageQuery, k as SduiScalar, l as SduiStateRef } from './types-D14eqLpf.js';
3
+ import { S as SduiPage, a as SduiAction, b as SduiPropValue, c as SduiRule } from './types-C7y9CpgF.js';
4
+ export { d as SduiArgRef, e as SduiCalcRef, f as SduiChild, g as SduiIndexRef, h as SduiItemRef, i as SduiNode, j as SduiPageQuery, k as SduiScalar, l as SduiStateRef } from './types-C7y9CpgF.js';
5
5
  import { S as SduiBoxProps, a as SduiContainerLayoutProps, b as SduiColumnsScale, c as SduiGapScale, d as SduiWeightScale, e as SduiTextAlignScale } from './layout-C3nRXMwb.js';
6
6
  export { C as CHILD_LAYOUT_PROPS, L as LAYOUT_CSS, f as SduiAlignScale, g as SduiChildLayoutProps, h as SduiSizeScale, i as SduiSpanScale, j as childLayoutFrom, k as columnsClass, l as containerLayoutStyle, m as joinClass, n as mergeStyle, t as typographyStyle } from './layout-C3nRXMwb.js';
7
- import { D as DatePickerProps, a as DateRangePickerProps, T as TimePickerProps } from './DatePicker-BK_jsV7i.js';
8
- export { b as DatePicker, c as DateRangePicker, d as DateRangeValue, e as TimePicker } from './DatePicker-BK_jsV7i.js';
7
+ import { D as DatePickerProps, a as DateRangePickerProps, T as TimePickerProps } from './DatePicker-C_TT_SOw.js';
8
+ export { b as DatePicker, c as DateRangePicker, d as DateRangeValue, e as TimePicker } from './DatePicker-C_TT_SOw.js';
9
9
 
10
10
  /**
11
11
  * 호스트 앱 연결점 — ERP에서는 라우터·erpFetch·SSE가 하던 일을
@@ -54,7 +54,13 @@ interface SduiAdapter {
54
54
  }) => Promise<Response>;
55
55
  /** invalidate 액션 — 클라이언트 조회 캐시 무효화 (TanStack Query 등) */
56
56
  invalidate?: (prefix: string, exact: boolean) => void;
57
- /** refresh 액션 — 페이지 스펙 재조회 */
57
+ /**
58
+ * refresh 액션 — 페이지 스펙 재조회. **호스트의 데이터 캐시도 함께 갱신하기를 권장한다.**
59
+ *
60
+ * 스펙만 다시 받으면 스펙에 내장된 데이터는 새로 와도 스스로 조회하는 목록 위젯이
61
+ * 옛 캐시를 계속 보여준다(ERP 어댑터가 실측으로 확인하고 둘 다 하도록 구현했다).
62
+ * `page.data` 조회는 렌더러가 이 액션에서 스스로 재조회하므로 여기서 신경 쓸 필요 없다.
63
+ */
58
64
  refresh?: () => void;
59
65
  /**
60
66
  * 페이지 스펙의 subscribe 채널 구독(SSE 등).
@@ -192,1063 +198,1116 @@ type SduiDispatch = (action: SduiAction, eventValue?: unknown) => void;
192
198
  declare function useSduiDispatch(): SduiDispatch;
193
199
 
194
200
  /**
195
- * 목록 원격 조회 어댑터.
196
- *
197
- * core는 특정 HTTP 클라이언트에 묶이지 않는다 — ERP는 SWR(useErpQuery)로 조회했지만
198
- * 라이브러리는 "endpoint + 파라미터 → { data, total }" 한 줄짜리 계약만 요구하고,
199
- * 호스트가 자기 클라이언트(TanStack Query, SWR, 사내 fetch 래퍼…)를 끼워 넣는다.
200
- *
201
- * 조회 함수를 고르는 순서는 셋이다:
202
- *
203
- * 1. `SduiSearchDataProvider`로 **명시 주입**한 fetcher — 가장 세다. 조회를 통째로 소유하는
204
- * 소비자(ERP RemoteSearchResults 등)의 뜻을 다른 무엇도 덮지 않는다.
205
- * 2. 렌더러 어댑터의 `fetch` — 호스트가 자기 인증·에러 처리를 꽂아 둔 자리다. 여기를 거치면
206
- * 소비 앱은 세션 규약을 그대로 태우고, 편집기 캔버스처럼 어댑터로 바깥을 막아 둔
207
- * 자리에서는 목록 조회도 **함께 막힌다**. 예전에는 이 경로만 잠금 밖이라, 캔버스가
208
- * 뜨는 것만으로 저장하지도 않은 스펙이 운영 API를 세션 쿠키와 함께 때렸다.
209
- * 3. 둘 다 없으면 브라우저 fetch(어댑터를 안 준 SduiRenderer와 같은 정책) — 감싸지 않은
210
- * 드롭인 소비자의 동작을 바꾸지 않기 위한 폴백이다.
201
+ * 내장 기본 컴포넌트 세트 — WIPCO 디자인 시스템(wp-* 토큰)을 따르는
202
+ * 독립 구현. ERP 디자인 시스템과 같은 프롭 규약을 쓰므로 스펙이 호환된다.
203
+ * 스타일은 styles.css 한 파일이 담당한다.
211
204
  */
212
205
 
213
- interface SearchRow {
214
- id: string;
215
- [key: string]: unknown;
216
- }
217
- interface SearchPage {
218
- data: SearchRow[];
219
- total: number;
220
- }
221
- /**
222
- * 목록 한 페이지를 가져온다. 실패하면 Error(사용자에게 보여줄 메시지)를 던진다.
223
- * signal은 파라미터가 바뀌거나 언마운트될 때 중단 신호로 넘어온다.
224
- */
225
- type SearchFetcher = (endpoint: string, params: URLSearchParams, signal: AbortSignal) => Promise<SearchPage>;
226
- declare function SduiSearchDataProvider({ fetcher, children, }: {
227
- fetcher: SearchFetcher;
228
- children?: React__default.ReactNode;
229
- }): React__default.JSX.Element;
230
- declare function useSearchFetcher(): SearchFetcher;
231
- interface RemotePageState {
232
- page: SearchPage;
233
- isLoading: boolean;
234
- error: Error | null;
235
- reload: () => void;
206
+ interface IconProps extends React__default.SVGAttributes<SVGSVGElement> {
207
+ /** kebab-case Lucide 이름 — "scan-line", "layers", "circle-alert". */
208
+ name: string;
209
+ /** 픽셀 박스. 기본 18. */
210
+ size?: number;
211
+ /** 기본 1.5. 32px 위에서만 2를 쓴다. */
212
+ strokeWidth?: number;
213
+ /** 주면 role="img" + aria-label. 장식 아이콘은 생략한다. */
214
+ title?: string;
236
215
  }
237
216
  /**
238
- * endpoint+params 조회 훅. 라이브러리 안에 캐시를 두지 않는다(호스트 클라이언트의 몫) —
239
- * 대신 직전 페이지를 유지해(keepPreviousData 상당) 페이지 이동 중 목록이 깜빡이지 않게 한다.
240
- *
241
- * 중단 신호는 fetcher에 넘기지만 호스트가 무시할 수도 있다(어댑터 계약상 선택 사항) —
242
- * 그래서 늦게 도착한 응답은 아래 `alive` 검사로 한 번 더 버린다.
243
- */
244
- declare function useRemoteSearchPage(endpoint: string, params: URLSearchParams): RemotePageState;
245
-
246
- /**
247
- * 검색 툴바 — ERP `app/components/search/SearchToolbar.tsx`의 계약(타입·헬퍼·동작)을
248
- * 그대로 옮긴 것. 스펙 호환이 목적이므로 값 규칙(쉼표 다중값, 느슨한 검색, 범위 키 이름)은
249
- * 한 글자도 바꾸지 않았다.
217
+ * 스트로크 아이콘. 130종을 24px 그리드 위에 그린다.
250
218
  *
251
- * - 검색 버튼을 입력창과 상세검색 토글 사이에 둔다(Enter 도 그대로 동작) —
252
- * 원래 시안은 Enter 전용이었는데 실사용 지적으로 명시 버튼을 되돌렸다
253
- * - 상세검색은 아이콘 버튼 → 드롭다운 패널(초기화/적용)
254
- * - 적용된 조건은 칩(Tag)으로 나열되고, 다중 선택 칩은 값 하나씩 제거된다
219
+ * **등록되지 않은 이름은 `null`이다** — 예전엔 점(●)으로 대체했는데, 그러면 스펙 오타나
220
+ * 아직 안 넣은 이름이 "없던 자국"으로 화면에 남는다. 빠진 아이콘은 비어 보이는 편이
221
+ * 낫고, 어차피 잘못된 이름은 check-meta가 팔레트 쪽에서 잡는다.
255
222
  */
256
-
257
- interface SearchOption {
258
- label: string;
259
- value: string;
260
- }
261
- interface CascadingSearchOption extends SearchOption {
262
- children?: CascadingSearchOption[];
223
+ declare function Icon({ name, size, strokeWidth, className, style, title, ...rest }: IconProps): React__default.JSX.Element | null;
224
+ interface ButtonProps extends React__default.ButtonHTMLAttributes<HTMLButtonElement> {
225
+ /** primary = WIPCO Blue, secondary = 외곽선(기본), ghost = 투명, danger = WIPCO Red, link = 인라인 */
226
+ variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';
227
+ /** xs 24 · sm 28 · md 32(기본) · lg 40 · xl 48 px */
228
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
229
+ /** 선행 아이콘 이름 */
230
+ icon?: string;
231
+ /** 후행 아이콘 이름 */
232
+ iconEnd?: string;
233
+ /**
234
+ * 라벨을 감추고 스피너를 돌린다.
235
+ *
236
+ * 스피너는 **CSS가 그린다**(`.wp-btn--loading::after`) — 엘리먼트를 따로 넣지 않는다.
237
+ * 예전엔 `<span class="wp-spinner">`를 렌더했는데, 같은 클래스에 ::after 규칙을 가진
238
+ * 디자인 시스템 스타일시트와 겹치면 원이 두 개 돈다. 그릴 주체를 CSS 한쪽으로 모은다.
239
+ */
240
+ loading?: boolean;
241
+ block?: boolean;
242
+ disabled?: boolean;
243
+ /**
244
+ * 다른 태그로 렌더한다. 링크로 보내야 하는 자리(SSO 이동 등)는 `as="a"` + `href`다 —
245
+ * `<button>`에 href를 달면 아무 데도 가지 않는다.
246
+ *
247
+ * button이 아닐 때는 `disabled` 속성이 없으므로 비활성은 `aria-disabled`로만 알린다
248
+ * (그래서 button일 때도 둘 다 붙인다 — 표기가 태그에 따라 달라지지 않게).
249
+ */
250
+ as?: 'button' | 'a' | 'span';
251
+ /**
252
+ * 버튼 **안쪽** 내용(아이콘·글자)의 가로 정렬. 기본은 가운데다.
253
+ *
254
+ * `block`으로 폭을 늘리거나 `width`를 준 버튼에서만 뜻이 있다 — 그전에는 버튼이
255
+ * 내용만큼만 넓어 정렬할 여백이 없다. 실측: 스펙 85개가 `style.justifyContent:
256
+ * 'flex-start'`로 4번 손으로 쓰고 있었고, 전부 폭을 늘린 버튼이었다.
257
+ */
258
+ justify?: 'start' | 'center' | 'end' | 'between';
259
+ children?: React__default.ReactNode;
263
260
  }
264
- type SearchField = {
265
- type?: 'text';
266
- key: string;
267
- label: string;
268
- placeholder?: string;
269
- } | {
261
+ declare function Button({ variant, size, icon, iconEnd, loading, block, disabled, as, justify, children, className, style, ...rest }: ButtonProps): React__default.JSX.Element;
262
+ interface IconButtonProps extends React__default.ButtonHTMLAttributes<HTMLButtonElement> {
263
+ icon: string;
264
+ /** 접근성 라벨(필수 권장) — title이자 aria-label이 된다. */
265
+ label?: string;
266
+ /** xs 24 · sm 28 · md 32(기본) · lg 40 px 정사각 */
267
+ size?: 'xs' | 'sm' | 'md' | 'lg';
270
268
  /**
271
- * 다중 선택 — 값은 쉼표로 합쳐 저장한다.
269
+ * 예전 이름. `variant`를 주지 않았을 때만 쓰인다 — `variant` 주석 참고.
270
+ * @deprecated `variant`로 옮겨라. `tone="danger"`는 `variant="danger"`와 같다.
271
+ */
272
+ tone?: 'default' | 'danger';
273
+ /**
274
+ * 디자인 시스템 어휘. ghost = 투명(기본) · outlined = 하드라인 테두리 ·
275
+ * solid = 채움 · danger = 빨강 · onchrome = 네이비 사이드바/탑바 위.
272
276
  *
273
- * `valueKey`/`labelKey` 를 주면 options 를 도메인 객체 배열로 받는다
274
- * (`Select` 0.7.0 과 같은 계약). 스펙에서는 options 에
275
- * `{"$state":"rt.departments","default":[]}` 를 바인딩하면 page.data 가 채운
276
- * 실데이터가 그대로 선택지가 된다 — 정적 {label,value}만 받던 것이
277
- * 예시값 필터가 화면마다 남던 근본 원인이었다(실사용 지적).
277
+ * 같은 자리를 가리키는 프롭이 `tone`과 둘로 갈라져 있는데 **variant가 이긴다** —
278
+ * 스펙과 디자인 시스템 문서가 쓰는 이름이 이쪽이고, 값 어휘도 넓다(outlined는 tone에 없다).
279
+ * variant를 주지 않으면 tone이 그대로 먹으므로 tone만 쓰던 코드는 그대로 돈다.
278
280
  */
279
- type: 'select';
280
- key: string;
281
- label: string;
282
- options: Array<SearchOption | Record<string, unknown>>;
283
- valueKey?: string;
284
- labelKey?: string;
285
- allLabel?: string;
286
- } | {
287
- type: 'dateRange';
288
- key: string;
289
- label: string;
290
- fromKey?: string;
291
- toKey?: string;
292
- } | {
293
- type: 'numberRange';
294
- key: string;
295
- label: string;
296
- fromKey?: string;
297
- toKey?: string;
298
- minPlaceholder?: string;
299
- maxPlaceholder?: string;
300
- } | {
281
+ variant?: 'ghost' | 'outlined' | 'solid' | 'danger' | 'onchrome';
282
+ /** 토글 상태 — aria-pressed로 나가고 선택 틴트를 입는다. */
283
+ pressed?: boolean;
284
+ disabled?: boolean;
285
+ }
286
+ declare function IconButton({ icon, label, size, tone, variant, pressed, disabled, className, type, ...rest }: IconButtonProps): React__default.JSX.Element;
287
+ interface InputProps extends Omit<React__default.InputHTMLAttributes<HTMLInputElement>, 'size'> {
288
+ /** sm 28 · md 32(기본) · lg 40 px */
289
+ size?: 'sm' | 'md' | 'lg';
290
+ /** 선행 아이콘 이름 (예: "search") */
291
+ icon?: string;
292
+ /** 빨간 테두리 — Field error와 함께 사용 */
293
+ invalid?: boolean;
294
+ /** 고정폭 글꼴 — ID, 좌표, 해시 */
295
+ mono?: boolean;
301
296
  /**
302
- * 계층 다중 선택 — 단계별 체크박스 목록 체인. 선택된 값들을 쉼표로 합쳐 저장하며,
303
- * 부모를 선택하면 그 자손 선택은 제거되고 자손을 선택하면 그 조상 선택이 제거된다.
297
+ * 입력 오른쪽 끝에 겹쳐 그리는 후행 표시.
304
298
  *
305
- * `tree` 를 주면 options 를 **평면 행 배열**로 받아 여기서 계층을 세운다 —
306
- * `/categories` 처럼 {id, parent_id, name} 평면 목록을 주는 API 를 스펙이
307
- * 트리로 재조립할 방법이 없어서다($calc 에 그런 연산이 없다).
299
+ * **문자열은 아이콘 이름으로 읽는다** — `trailing="eye"`(비밀번호 보기)가 원래 용례다.
300
+ * 등록되지 않은 이름이면 Icon이 null을 내므로 자리만 남는다. 단위 글자("원", "mm")를
301
+ * 넣고 싶으면 문자열이 아니라 노드로 줘라: `trailing={<span>원</span>}`.
308
302
  */
309
- type: 'cascading';
310
- key: string;
303
+ trailing?: React__default.ReactNode;
304
+ }
305
+ declare function Input({ size, icon, invalid, mono, trailing, className, style, ...rest }: InputProps): React__default.JSX.Element;
306
+ interface TextareaProps extends React__default.TextareaHTMLAttributes<HTMLTextAreaElement> {
307
+ invalid?: boolean;
308
+ rows?: number;
309
+ }
310
+ declare function Textarea({ invalid, rows, className, ...rest }: TextareaProps): React__default.JSX.Element;
311
+ interface SelectOption {
312
+ value: string;
311
313
  label: string;
312
- options: Array<CascadingSearchOption | Record<string, unknown>>;
313
- tree?: {
314
- idKey: string;
315
- parentIdKey: string;
316
- labelKey: string;
317
- };
318
- allLabel?: string;
319
- };
320
- type SearchValues = Record<string, string>;
321
- declare function splitMultiValue(value: string): string[];
322
- declare function joinMultiValue(values: string[]): string;
323
- declare function normalizeSearchText(value: string): string;
324
- declare function includesLooseSearch(text: string, keyword: string): boolean;
325
- /** 필드가 점유하는 파라미터 키 전체 — 범위 필드는 From/To(Min/Max) 두 개다 */
326
- declare function searchFieldKeys(fields: SearchField[]): string[];
327
- declare function searchValuesFromParams(fields: SearchField[], params: URLSearchParams): SearchValues;
328
- declare function cleanSearchValues(fields: SearchField[], values: SearchValues): SearchValues;
329
- /**
330
- * 값 → 자기 자신을 포함한 조상 값 목록(루트→자신 순).
331
- * SearchResults의 클라이언트 필터링에서 "부모 선택 = 자손 값 매칭"을 구현할 때 쓴다.
332
- */
333
- declare function cascadingValuePaths(options: CascadingSearchOption[]): Map<string, string[]>;
334
- interface SearchToolbarProps {
335
- search: string;
336
- onSearchChange: (value: string) => void;
337
- onSearch: (search: string, advancedValues: SearchValues) => void;
338
- advancedFields?: SearchField[];
339
- advancedValues?: SearchValues;
340
- placeholder?: string;
341
- className?: string;
342
- /** 필터 전용 모드: 검색 입력창 없이 상세검색 버튼+칩만 보여준다 */
343
- hideSearchInput?: boolean;
314
+ disabled?: boolean;
344
315
  }
345
- declare function SearchToolbar({ search, onSearchChange, onSearch, advancedFields: rawAdvancedFields, advancedValues, placeholder, className, hideSearchInput, }: SearchToolbarProps): React__default.JSX.Element;
346
-
347
316
  /**
348
- * 검색 결과 목록 — ERP `app/components/search/SearchResults.tsx`의 계약을 그대로 가져온 포팅.
349
- * - 목록(Table)/카드 그리드(Card) 전환, 정렬, 좌/우 필터, 선택 후 일괄 액션, 페이지네이션
350
- * - 데이터 가공(검색·필터·정렬·페이지 슬라이스)은 로컬 데이터 기준 클라이언트에서 수행하며,
351
- * 서버 페이징으로 넘어가면 params를 그대로 백엔드 질의로 옮기면 된다
317
+ * 자유 형식 옵션 — `valueKey`/`labelKey`가 가리키는 키로 읽는다.
352
318
  *
353
- * ERP와 다른 점은 하나뿐이다: 검색/필터/페이지 파라미터를 URL에 직접 쓰지 않고
354
- * `searchParams` + `onParamsChange`로만 다룬다(controlled). core는 라우터를 알지 않으며,
355
- * 한 페이지에 목록이 여러 개 있어도 서로의 URL을 오염시키지 않는다.
319
+ * `AutocompleteOption`과 같은 모양(`Record<string, unknown>`)으로 맞췄다. 두 어휘가
320
+ * 같은 자리에서 같은 프롭 이름을 쓰는데 옵션 타입만 다르면 스펙 작성자가 둘 사이를
321
+ * 옮길 때마다 다시 배워야 한다.
356
322
  */
357
-
358
- /** DS Badge 톤 */
359
- type BadgeTone = 'neutral' | 'info' | 'success' | 'warning' | 'danger' | 'brand';
360
- interface SearchAction<T> {
361
- label: string;
362
- onClick: (selectedItems: T[]) => void | Promise<void>;
363
- icon?: string;
364
- variant?: 'default' | 'destructive' | 'outline';
365
- }
366
- interface SearchColumn<T> {
367
- key: string;
368
- title: string;
369
- sortable?: boolean;
370
- /** DS Table은 left/right만 지원한다 */
371
- textAlign?: 'left' | 'right';
372
- /** 상대 폭 — 전체 ratio 합 대비 % 폭으로 환산된다 */
373
- ratio?: number;
374
- mono?: boolean;
375
- strong?: boolean;
376
- accessor?: (item: T) => string | number | Date | null | undefined;
377
- render?: (item: T) => React__default.ReactNode;
378
- }
379
- interface SearchFilter {
380
- key: string;
381
- label: string;
382
- options: {
383
- key: string;
384
- label: string;
385
- value: string;
386
- }[];
323
+ type SelectOptionRecord = Record<string, unknown>;
324
+ interface SelectProps extends Omit<React__default.SelectHTMLAttributes<HTMLSelectElement>, 'size'> {
325
+ /**
326
+ * 옵션 목록. 기본은 `{value, label}`이고, 도메인 객체(`{id, title, …}`)를 그대로
327
+ * 넘길 거면 `valueKey`/`labelKey`로 읽을 키를 알려 준다. 값과 라벨이 같은 목록은
328
+ * 문자열·숫자 배열(`["S","A","B","C"]`)을 그대로 줘도 된다.
329
+ */
330
+ options: (SelectOption | SelectOptionRecord | string | number)[];
331
+ /** 옵션에서 값을 읽을 키. 기본 'value'. */
332
+ valueKey?: string;
333
+ /** 옵션에서 표시 라벨을 읽을 키. 기본 'label'. */
334
+ labelKey?: string;
335
+ size?: 'sm' | 'md' | 'lg';
336
+ invalid?: boolean;
337
+ /** 비어 있는 첫 옵션 */
338
+ placeholder?: string;
387
339
  }
388
- /**
389
- * 목록/카드 전환 버튼 한 칸.
390
- *
391
- * 예전에는 이 두 칸(`목록`/`카드`)이 코드에 박혀 있어 스펙이 라벨·아이콘·순서를
392
- * 바꿀 수 없었고, `disableGrid`/`disableList`로 **끄는 것만** 가능했다.
393
- * 이제 `views`로 목록 자체를 정할 수 있고, 두 disable 프롭은 그 축약형으로 남는다.
394
- */
395
- interface SearchViewItem {
396
- value: 'grid' | 'list';
397
- /** 생략 시 기본 아이콘 — list=list, grid=layout-grid */
398
- icon?: string;
399
- /** 생략 시 기본 라벨 — list=목록, grid=카드 */
400
- label?: string;
340
+ declare function Select({ options, valueKey, labelKey, size, invalid, placeholder, className, ...rest }: SelectProps): React__default.JSX.Element;
341
+ interface CheckboxProps extends React__default.InputHTMLAttributes<HTMLInputElement> {
342
+ label?: React__default.ReactNode;
343
+ description?: React__default.ReactNode;
344
+ /**
345
+ * 부분 선택 — 전체선택 체크박스의 "일부만 골랐다" 상태.
346
+ *
347
+ * HTML 속성이 아니라 DOM 프로퍼티라 ref로 직접 넣어야 한다(마크업에 남지 않는다).
348
+ * 글리프도 체크가 아니라 빼기로 바뀐다.
349
+ */
350
+ indeterminate?: boolean;
351
+ disabled?: boolean;
401
352
  }
402
- interface GridCardRow<T> {
403
- icon?: React__default.ReactNode | ((item: T) => React__default.ReactNode);
404
- label?: React__default.ReactNode | ((item: T) => React__default.ReactNode);
405
- value?: React__default.ReactNode | ((item: T) => React__default.ReactNode);
406
- show?: (item: T) => boolean;
353
+ declare function Checkbox({ label, description, indeterminate, disabled, className, ...rest }: CheckboxProps): React__default.JSX.Element;
354
+ interface SwitchProps extends Omit<React__default.InputHTMLAttributes<HTMLInputElement>, 'size'> {
355
+ label?: React__default.ReactNode;
356
+ size?: 'sm' | 'md';
357
+ disabled?: boolean;
407
358
  }
408
- interface GridCardConfig<T> {
409
- /** 머리 위 표지 — 보통 대표 사진(cells.tsx의 imageRenderer)을 넘긴다 */
410
- cover?: (item: T) => React__default.ReactNode;
411
- title: (item: T) => React__default.ReactNode;
412
- subtitle?: (item: T) => React__default.ReactNode;
413
- badge?: (item: T) => {
414
- text: React__default.ReactNode;
415
- variant?: BadgeTone;
416
- } | null;
417
- actions?: (item: T) => React__default.ReactNode;
418
- rows?: GridCardRow<T>[];
419
- leftRows?: GridCardRow<T>[];
420
- rightRows?: GridCardRow<T>[];
421
- note?: (item: T) => React__default.ReactNode | null;
359
+ declare function Switch({ label, size, disabled, className, ...rest }: SwitchProps): React__default.JSX.Element;
360
+ interface FieldProps extends React__default.HTMLAttributes<HTMLDivElement> {
361
+ label?: React__default.ReactNode;
362
+ /** 에러가 없을 때 보여줄 안내 한 줄 */
363
+ hint?: React__default.ReactNode;
364
+ /** hint를 대체하고 빨간색으로 표시 */
365
+ error?: React__default.ReactNode;
366
+ required?: boolean;
367
+ htmlFor?: string;
368
+ /** stacked(기본) 또는 horizontal(132px 라벨 컬럼) */
369
+ layout?: 'stacked' | 'horizontal';
370
+ children?: React__default.ReactNode;
422
371
  }
423
- interface SearchResultsProps<T> {
424
- item: {
425
- data: T[];
426
- total: number;
427
- };
428
- columns: SearchColumn<T>[];
429
- gridConfig?: GridCardConfig<T>;
430
- leftFilters?: SearchFilter[];
431
- rightFilters?: SearchFilter[];
432
- initialView?: 'grid' | 'list';
372
+ declare function Field({ label, hint, error, required, htmlFor, layout, children, className, ...rest }: FieldProps): React__default.JSX.Element;
373
+ interface CardProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, 'title'> {
374
+ title?: React__default.ReactNode;
375
+ subtitle?: React__default.ReactNode;
376
+ /** 헤더 우측 — 버튼, Select 등 */
377
+ actions?: React__default.ReactNode;
378
+ /** 우측 정렬 푸터 행 */
379
+ footer?: React__default.ReactNode;
433
380
  /**
434
- * 노출할 보기 목록. 생략하면 목록/카드 두 칸이 기본값이고,
435
- * 그때만 `disableGrid`/`disableList`가 목록을 깎는다(둘 다 오면 views가 이긴다).
436
- * 한 칸만 남으면 전환 버튼 자체를 그리지 않는다.
381
+ * 머리 위에 꽉 차게 깔리는 표지 — 목록 카드의 대표 사진 자리다.
382
+ * 카드 안쪽 여백을 무시하고 위 두 모서리만 카드 곡률을 따른다.
437
383
  */
438
- views?: SearchViewItem[];
439
- /** 페이지당 건수 선택지. 생략 시 [10, 20, 50, 100]. 첫 값이 기본 페이지 크기다. */
440
- pageSizes?: number[];
441
- className?: string;
442
- /** 바깥에서 온 인라인 style — 자기 축(폭·여백)이 이 컴포넌트에도 닿게 하는 통로다. */
443
- style?: React__default.CSSProperties;
444
- /** 검색/필터/페이지 파라미터 (controlled) */
445
- searchParams: URLSearchParams;
446
- onParamsChange: (next: URLSearchParams) => void;
447
- /** 선택 액션 — 있으면 체크박스 선택이 활성화된다 */
448
- actions?: SearchAction<T>[];
449
- disableGrid?: boolean;
450
- disableList?: boolean;
451
- onRowClick?: (item: T) => void;
452
- /** 화면에는 카운트만 표시 — 접근성 레이블·빈 상태 문구에 사용 */
453
- title: string;
454
- headerActions?: React__default.ReactNode;
455
- isLoading?: boolean;
456
- hideHeader?: boolean;
457
- advancedSearchFields?: SearchField[];
458
- searchPlaceholder?: string;
384
+ cover?: React__default.ReactNode;
385
+ variant?: 'default' | 'raised' | 'flat' | 'sunken';
386
+ selected?: boolean;
387
+ interactive?: boolean;
388
+ /** default 20px · tight 14px · none(테이블, 리스트) */
389
+ padding?: 'default' | 'tight' | 'none';
390
+ children?: React__default.ReactNode;
391
+ }
392
+ declare function Card({ title, subtitle, actions, footer, cover, variant, selected, interactive, padding, children, className, ...rest }: CardProps): React__default.JSX.Element;
393
+ interface BadgeProps extends React__default.HTMLAttributes<HTMLSpanElement> {
394
+ tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger' | 'brand';
395
+ /** 선행 5px 점 — 라이브/진행 상태 */
396
+ dot?: boolean;
397
+ size?: 'md' | 'lg';
398
+ children?: React__default.ReactNode;
399
+ }
400
+ declare function Badge({ tone, dot, size, children, className, ...rest }: BadgeProps): React__default.JSX.Element;
401
+ interface TagProps extends React__default.HTMLAttributes<HTMLSpanElement> {
402
+ /** 선행 점의 CSS 색상 */
403
+ color?: string;
404
+ filled?: boolean;
405
+ onRemove?: (e: React__default.MouseEvent) => void;
406
+ children?: React__default.ReactNode;
407
+ }
408
+ declare function Tag({ color, filled, onRemove, children, className, ...rest }: TagProps): React__default.JSX.Element;
409
+ interface AlertProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, 'title'> {
410
+ tone?: 'info' | 'success' | 'warning' | 'danger' | 'neutral';
411
+ title?: React__default.ReactNode;
412
+ children?: React__default.ReactNode;
413
+ actions?: React__default.ReactNode;
414
+ onClose?: () => void;
459
415
  /**
460
- * 가상 스크롤(대용량 목록) — true면 기본 임계값 150행, 숫자면 그 값이 임계값.
461
- * 목록 뷰에서 행 수가 임계값을 넘으면 페이지 슬라이스 대신 전체 목록을 창 렌더한다
462
- * (서버 페이징 모드에서는 현재 응답 분량만 가상화).
416
+ * 테두리를 왼쪽 색 막대 하나로 바꾼다(모서리도 각지게 편다).
417
+ *
418
+ * 화면 맨 위에 놓여 먼저 읽혀야 하는 공지 — 개인정보 보호 안내, 업로드 절차 안내처럼
419
+ * 카드 안의 알림이 아니라 화면 폭을 가르는 배너로 보여야 하는 자리에 쓴다.
463
420
  */
464
- virtual?: boolean | number;
421
+ bar?: boolean;
465
422
  }
466
- /**
467
- * 스펙이 준 페이지 크기 목록을 다듬는다 — 양의 정수만, 중복 제거, 오름차순.
468
- * 쓸 값이 하나도 없으면 undefined를 돌려 기본 선택지를 쓰게 한다.
469
- */
470
- declare function normalizePageSizes(sizes: number[] | undefined): number[] | undefined;
471
- declare function SearchResults<T extends {
472
- id: string;
473
- }>({ item, columns, gridConfig, leftFilters, rightFilters, initialView, views, pageSizes, className, style, searchParams, onParamsChange, actions, disableGrid, disableList, onRowClick, title, headerActions, isLoading, hideHeader, advancedSearchFields: rawAdvancedSearchFields, searchPlaceholder, virtual, }: SearchResultsProps<T>): React__default.JSX.Element;
474
-
475
- /**
476
- * 셀 표현 어휘 — 목록 컬럼의 셀과 카드 행의 값이 **같은 규칙**을 쓴다.
477
- *
478
- * 예전에는 스펙이 고를 수 있는 표현이 `badge` 하나뿐이었고 나머지는 `row[key]` 원문
479
- * 출력이었다. 밑에 깔린 `SearchColumn.render`는 임의의 노드를 그릴 수 있는데 스펙에는
480
- * 그 역량이 두 종류(원문/배지)로만 열려 있었다는 뜻이다. 여기서 그 구멍을 메운다.
481
- *
482
- * 규약 두 가지:
483
- * - **모르는 type은 원문으로 강등한다.** 스펙은 신뢰할 수 없는 입력이고, 컬럼 하나 때문에
484
- * 목록 전체가 비면 안 된다. 판별 유니언이라 새 type을 더해도 옛 스펙은 그대로 돈다.
485
- * - **값을 해석하지 못하면 원문을 그대로 낸다.** ERP는 이미 서식된 문자열(`"12,400,000"`,
486
- * `"2026-07-31 09:00"`)을 내려주는 자리가 많아, 파싱 실패를 빈칸으로 만들면 퇴행이다.
487
- */
488
-
489
- /** ProgressBar가 받는 톤 */
490
- type MeterTone = 'brand' | 'success' | 'warning' | 'danger' | 'info';
491
- /** 값을 그대로 출력한다. cell을 생략한 것과 같으며, 명시하고 싶을 때 쓴다. */
492
- interface SduiTextCell {
493
- type: 'text';
423
+ declare function Alert({ tone, title, children, actions, onClose, bar, className, ...rest }: AlertProps): React__default.JSX.Element;
424
+ interface ToastProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, 'title'> {
425
+ tone?: 'info' | 'success' | 'warning' | 'danger';
426
+ title?: React__default.ReactNode;
427
+ children?: React__default.ReactNode;
428
+ /** 인라인 액션 하나 — 되돌리기 같은 것. Button variant="link"를 넣는다. */
429
+ action?: React__default.ReactNode;
430
+ onClose?: () => void;
494
431
  }
495
- /** 값을 배지로. 톤은 값별 매핑(없는 값은 neutral). */
496
- interface SduiBadgeCell {
497
- type: 'badge';
498
- toneMap?: Record<string, BadgeTone>;
432
+ /** 우하단 일시 알림. .wp-toast-region 안에 렌더링된다. */
433
+ declare function Toast({ tone, title, children, action, onClose, className, ...rest }: ToastProps): React__default.JSX.Element;
434
+ interface TabItem {
435
+ value: string;
436
+ label: React__default.ReactNode;
437
+ icon?: string;
438
+ count?: number;
439
+ }
440
+ interface TabsProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, 'onChange'> {
441
+ items: TabItem[];
442
+ value: string;
443
+ onChange?: (value: string) => void;
444
+ size?: 'md' | 'lg';
445
+ /** 네이비 탑바 위 — 밝은 글자, 빨간 인디케이터. */
446
+ onChrome?: boolean;
447
+ }
448
+ declare function Tabs({ items, value, onChange, size, onChrome, className, ...rest }: TabsProps): React__default.JSX.Element;
449
+ interface TableColumn {
450
+ key: string;
451
+ header: React__default.ReactNode;
452
+ /** CSS 폭 — "180px", "22%" */
453
+ width?: string;
454
+ align?: 'left' | 'right';
455
+ mono?: boolean;
456
+ strong?: boolean;
457
+ /** 헤더를 정렬 버튼으로 만든다. 실제 정렬은 `sort`·`onSort`가 담당한다. */
458
+ sortable?: boolean;
499
459
  /**
500
- * 값 → 표시 텍스트(생략 시 값 그대로) — Table 컬럼 badge와 같은 의미론.
501
- * 톤 조회 키는 여전히 **원래 값**이다. labelMap이 조회 키까지 바꾸면 기존 스펙의
502
- * toneMap(enum 키)이 전부 neutral로 떨어진다.
460
+ * 셀 값을 Badge로 렌더링 — JSON 스펙에서 쓸 수 있는 선언적 지정.
461
+ * toneMap: 값 → tone, labelMap: 값 → 표시 텍스트(생략 시 값 그대로)
503
462
  */
504
- labelMap?: Record<string, string>;
463
+ badge?: {
464
+ toneMap?: Record<string, string>;
465
+ labelMap?: Record<string, string>;
466
+ };
467
+ /** 프로그래매틱 사용 전용 — JSON 스펙에서는 쓸 수 없다 */
468
+ render?: (row: any, index: number) => React__default.ReactNode;
505
469
  }
506
- /** 값을 태그로. 선행 점 색은 값별 매핑(CSS 색상 문자열). */
507
- interface SduiTagCell {
508
- type: 'tag';
509
- colorMap?: Record<string, string>;
510
- filled?: boolean;
470
+ interface TableProps extends React__default.TableHTMLAttributes<HTMLTableElement> {
471
+ columns: TableColumn[];
472
+ rows: any[];
473
+ /** 40px 대신 32px 행 */
474
+ dense?: boolean;
475
+ hover?: boolean;
476
+ /** 스크롤 본문용 고정 헤더 */
477
+ sticky?: boolean;
478
+ /** 현재 정렬 상태 — 해당 컬럼 헤더의 화살표 방향을 정한다 */
479
+ sort?: {
480
+ key: string;
481
+ dir: 'asc' | 'desc';
482
+ };
483
+ /** 정렬 헤더를 눌렀을 때. 주지 않으면 sortable 컬럼도 버튼이 되지 않는다 */
484
+ onSort?: (key: string) => void;
485
+ /** 선택된 행의 rowKey 값 — 해당 행에 aria-selected가 붙고 하이라이트된다 */
486
+ selectedIds?: any[];
487
+ /** 행 key와 선택에 쓸 필드. 기본 "id" */
488
+ rowKey?: string;
489
+ onRowClick?: (row: any) => void;
490
+ /** rows가 비었을 때 대신 렌더링 — EmptyState 권장 */
491
+ empty?: React__default.ReactNode;
511
492
  }
512
- /**
513
- * 값을 링크로. `to`는 `/assets/{id}` 처럼 행의 필드를 `{키}`로 끼워 넣는 경로 템플릿이고,
514
- * 클릭하면 navigate 액션이 나간다(렌더러가 대상 안전성을 다시 검사한다).
515
- */
516
- interface SduiLinkCell {
517
- type: 'link';
518
- to: string;
519
- /** 링크 문구로 쓸 필드 — 생략하면 컬럼 자신의 값 */
520
- labelKey?: string;
493
+ declare function Table({ columns, rows, dense, hover, sticky, sort, onSort, selectedIds, rowKey, onRowClick, empty, className, ...rest }: TableProps): React__default.JSX.Element;
494
+ interface StatCardProps extends React__default.HTMLAttributes<HTMLDivElement> {
495
+ label: React__default.ReactNode;
496
+ value: React__default.ReactNode;
497
+ /** 단위 접미사 — "GB", "건", "%" */
498
+ unit?: string;
499
+ /** 비교 텍스트 — "+12.4%" */
500
+ delta?: React__default.ReactNode;
501
+ trend?: 'up' | 'down' | 'flat';
521
502
  icon?: string;
503
+ footer?: React__default.ReactNode;
504
+ size?: 'md' | 'sm';
522
505
  }
523
- /** 값을 진행 막대로. 분모는 maxKey(행의 필드) → max → 100 순으로 정한다. */
524
- interface SduiProgressCell {
525
- type: 'progress';
506
+ declare function StatCard({ label, value, unit, delta, trend, icon, footer, size, className, ...rest }: StatCardProps): React__default.JSX.Element;
507
+ interface EmptyStateProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, 'title'> {
508
+ icon?: string;
509
+ title?: React__default.ReactNode;
510
+ children?: React__default.ReactNode;
511
+ actions?: React__default.ReactNode;
512
+ tight?: boolean;
513
+ }
514
+ declare function EmptyState({ icon, title, children, actions, tight, className, ...rest }: EmptyStateProps): React__default.JSX.Element;
515
+ interface SpinnerProps extends React__default.HTMLAttributes<HTMLSpanElement> {
516
+ size?: 'sm' | 'md' | 'lg';
517
+ /** 네이비/뷰어 표면 위 — 밝은 테두리로 뒤집는다. */
518
+ inverse?: boolean;
519
+ label?: string;
520
+ }
521
+ declare function Spinner({ size, inverse, label, className, ...rest }: SpinnerProps): React__default.JSX.Element;
522
+ interface ProgressBarProps extends React__default.HTMLAttributes<HTMLDivElement> {
523
+ /** 0~`max` */
524
+ value: number;
525
+ /** 기본 100. 용량 게이지처럼 분모가 100이 아닌 자리에 쓴다 */
526
526
  max?: number;
527
- maxKey?: string;
528
- tone?: MeterTone;
527
+ /** danger 임계 강조 등 */
528
+ tone?: 'brand' | 'success' | 'warning' | 'danger' | 'info';
529
+ /** sm 4 · md 6(기본) · lg 8 px */
530
+ size?: 'sm' | 'md' | 'lg';
531
+ /** 사선 해치 애니메이션 — 진행 중인 작업에만 */
532
+ striped?: boolean;
533
+ label?: React__default.ReactNode;
534
+ /**
535
+ * 현재 값을 퍼센트로 적는다.
536
+ *
537
+ * 막대만으로는 82%와 91%를 구분할 수 없어서, 여러 개를 세로로 쌓아 비교하는
538
+ * 자리(공정별 진척 목록 등)에서 필요하다. 라벨이 있으면 라벨 줄 오른쪽 끝에,
539
+ * 라벨이 없으면 막대 오른쪽 옆(.wp-meter-row)에 붙는다.
540
+ */
529
541
  showValue?: boolean;
530
542
  }
531
- /** 값을 아이콘으로. iconMap에 없는 값은 icon(공통 기본)으로, 그것도 없으면 원문으로. */
532
- interface SduiIconCell {
533
- type: 'icon';
534
- iconMap?: Record<string, string>;
543
+ declare function ProgressBar({ value, max, tone, size, striped, showValue, label, className, ...rest }: ProgressBarProps): React__default.JSX.Element;
544
+ interface PaginationProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, 'onChange'> {
545
+ page: number;
546
+ pageCount: number;
547
+ total?: number;
548
+ pageSize?: number;
549
+ onChange?: (page: number) => void;
550
+ }
551
+ declare function Pagination({ page, pageCount, total, pageSize, onChange, className, ...rest }: PaginationProps): React__default.JSX.Element;
552
+ interface DialogProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, 'title'> {
553
+ title?: React__default.ReactNode;
554
+ subtitle?: React__default.ReactNode;
555
+ /** 타이틀 좌측 마크 안의 아이콘 이름 */
556
+ icon?: string;
557
+ tone?: 'brand' | 'danger';
558
+ /** sm 380 · md 480(기본) · lg 680 · xl 880 px */
559
+ size?: 'sm' | 'md' | 'lg' | 'xl';
560
+ /** 우측 정렬 액션 행 — 보조 버튼 다음 주 버튼 */
561
+ footer?: React__default.ReactNode;
562
+ onClose?: () => void;
563
+ children?: React__default.ReactNode;
564
+ }
565
+ /** 모달 본체. 오버레이가 가장 가까운 포지셔닝 조상을 채운다 — SDUI에서는 SduiDialog 래퍼가 뷰포트에 고정한다. */
566
+ declare function Dialog({ title, subtitle, icon, tone, size, footer, onClose, children, className, ...rest }: DialogProps): React__default.JSX.Element;
567
+ /** 배타 뷰 전환 스위치. 항목 2~4개까지, 그보다 많으면 Select다. */
568
+ interface SegmentedItem {
569
+ value: string;
570
+ label?: string;
571
+ /** 앞에 붙일 아이콘 이름 */
535
572
  icon?: string;
536
- /** 아이콘 뒤에 값도 적는다 */
537
- withLabel?: boolean;
538
573
  }
539
- /**
540
- * 숫자 서식(ko-KR 천단위) + 접두/접미. 통화는 `{ type:'number', suffix:'원' }`이다 —
541
- * `currency` 타입을 따로 두지 않은 이유는 ERP가 원화 단일이고, 그 자리에 필요한 것이
542
- * 통화 기호가 아니라 "단위 문자열"이기 때문이다(원·건·개·%·kg).
543
- */
544
- interface SduiNumberCell {
545
- type: 'number';
546
- /** 소수 자릿수 (기본 0) */
547
- digits?: number;
548
- prefix?: string;
549
- suffix?: string;
574
+ interface SegmentedControlProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, 'onChange'> {
575
+ items: SegmentedItem[];
576
+ value: string;
577
+ onChange?: (value: string) => void;
578
+ /** md(24px 항목, 기본) · lg(30px) */
579
+ size?: 'md' | 'lg';
580
+ className?: string;
550
581
  }
551
582
  /**
552
- * 값(이미지 URL)을 사진으로. 목록 행·카드 표지가 같은 규칙을 쓴다.
553
- *
554
- * **사진 값은 언제나 눌러서 크게 볼 수 있다.** 확대를 옵션으로 두면 스펙마다 켜고 끄는
555
- * 자리가 생기고, 켜지 않은 화면에서는 목록의 작은 썸네일이 사실상 못 보는 사진이 된다.
556
- * 원본 보기는 사진이라는 값 자체의 성질로 본다. 뷰어는 코어의 `ImageViewer` 하나뿐이라
557
- * (휠 줌·드래그 패닝·툴바) 목록·카드 표지·상세 첨부가 전부 같게 동작한다.
558
- *
559
- * **크게 볼 수 있는 것은 실제로 뜬 사진뿐이다.** URL이 없으면, 그리고 URL이 있어도
560
- * 로딩이 실패하면(끊긴 링크·삭제된 객체) 자리표시자 상자로 떨어지고 눌리지 않는다 —
561
- * 빈 화면이 뜨는 다이얼로그를 열어 주는 것보다 낫다.
583
+ * 목록/카드, 2D/3D, 일/주/월처럼 하나만 고르는 뷰 전환.
562
584
  *
563
- * **값이 비어도 상자 크기는 같다.** 칸을 비우면 사진 있는 행만 높이가 달라져 표가
564
- * 흔들린다. 아이콘 이름은 도메인마다 다르므로(장비는 building-2, 사람은 user)
565
- * `fallbackIcon`으로 받고 기본값만 중립적인 `image`로 둔다.
566
- */
567
- interface SduiImageCell {
568
- type: 'image';
569
- /** 기본 72 — 카드 커버로 쓰일 때는 컨테이너가 '100%'로 덮어쓴다 */
570
- width?: number;
571
- /** 기본 52 */
572
- height?: number;
573
- /** object-fit (기본 cover) */
574
- fit?: 'cover' | 'contain';
575
- /** 사진이 없을 때 그릴 아이콘 이름 (기본 image) */
576
- fallbackIcon?: string;
577
- /** 대체 텍스트를 담은 행의 필드 — 없으면 빈 alt(장식으로 취급) */
578
- altKey?: string;
579
- }
580
- /** 날짜 서식(ko-KR). 파싱되지 않는 값은 원문 그대로. */
581
- interface SduiDateCell {
582
- type: 'date';
583
- format?: 'date' | 'datetime' | 'time';
584
- }
585
- type SduiCell = SduiTextCell | SduiImageCell | SduiBadgeCell | SduiTagCell | SduiLinkCell | SduiProgressCell | SduiIconCell | SduiNumberCell | SduiDateCell;
586
- /**
587
- * 사진 셀 — 카드 커버도 이 함수를 쓴다(`width: '100%'` 을 넘겨 폭만 늘린다).
588
- * 상자 크기를 값이 있든 없든 똑같이 잡아 두는 것이 요점이다.
589
- */
590
- declare function imageRenderer(cell: SduiImageCell, key: string, width?: number | string): (row: SearchRow) => React__default.ReactNode;
591
- /**
592
- * 셀 스펙 → 렌더 함수. `undefined`를 돌려주면 호출자가 원문을 그대로 출력한다
593
- * (표에서는 `<td>{row[key]}</td>`, 카드에서는 값 문자열).
594
- */
595
- declare function cellRenderer(cell: SduiCell | undefined, key: string, dispatch: SduiDispatch): ((row: SearchRow) => React__default.ReactNode) | undefined;
596
-
597
- /** JSON 컬럼 정의 — render 함수 대신 선언적 cell 스펙을 쓴다 */
598
- interface SduiSearchColumn {
599
- key: string;
600
- title: string;
601
- sortable?: boolean;
602
- /** DS Table은 left/right만 지원 */
603
- textAlign?: 'left' | 'right';
604
- ratio?: number;
605
- mono?: boolean;
606
- strong?: boolean;
607
- /** 셀 표현. 생략하면 row[key]를 그대로 출력한다 — 어휘는 `cells.tsx` 참고 */
608
- cell?: SduiCell;
609
- }
610
- /**
611
- * 카드 행 표시 조건.
612
- * `key`만 주면 "그 값이 비어 있지 않을 때"다. 값이 없는 행을 감춰 카드 높이가
613
- * 들쭉날쭉해지지 않게 하는 것이 주 용도라 그것이 기본 의미다.
585
+ * `search/SearchResults.tsx`가 "라이브러리에 SegmentedControl이 없다"는 이유로
586
+ * 안에 `ViewToggle` 사본을 들고 있었다. 이 컴포넌트가 그 사본의 원본이다.
614
587
  */
615
- interface SduiRowCondition {
616
- key: string;
617
- equals?: string | number | boolean;
618
- in?: Array<string | number>;
619
- notEmpty?: boolean;
588
+ declare function SegmentedControl({ items, value, onChange, size, className, ...rest }: SegmentedControlProps): React__default.JSX.Element;
589
+ /** 사람·팀·자산의 신원 표식. */
590
+ interface AvatarProps extends React__default.HTMLAttributes<HTMLSpanElement> {
591
+ /** 이니셜과 title에 쓰인다 */
592
+ name?: string;
593
+ /** 이미지 URL — 있으면 이니셜을 대신한다 */
594
+ src?: string;
595
+ /** hex(기본, 심볼 실루엣) · round(사람) · square(자산) */
596
+ shape?: 'hex' | 'round' | 'square';
597
+ /** xs 20 · sm 24 · md 28(기본) · lg 36 · xl 48 px */
598
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
599
+ /** brand 네이비(기본) · muted 회색 · accent 레드 */
600
+ tone?: 'brand' | 'muted' | 'accent';
601
+ className?: string;
620
602
  }
621
- interface SduiGridCardRow {
622
- label?: string;
623
- valueKey: string;
624
- /** 라벨 앞 아이콘 */
603
+ /** 사람/자산 표식. 기본은 육각형 — 심볼의 실루엣이다. */
604
+ declare function Avatar({ name, src, shape, size, tone, className, ...rest }: AvatarProps): React__default.JSX.Element;
605
+ /** 프로젝트에서 개별 객체까지의 위치. 4단계를 넘으면 가운데를 접는다. */
606
+ interface BreadcrumbItem {
607
+ label: React__default.ReactNode;
608
+ href?: string;
625
609
  icon?: string;
626
- /** 값 표현 — 컬럼 셀과 같은 어휘 */
627
- cell?: SduiCell;
628
- /** 조건부 행 */
629
- showWhen?: SduiRowCondition;
610
+ onClick?: () => void;
630
611
  }
631
- /** 카드 우상단 버튼 — 이벤트 값으로 행 객체 전체가 전달된다 */
632
- interface SduiGridCardAction {
633
- label: string;
634
- icon?: string;
635
- variant?: 'default' | 'destructive' | 'outline';
636
- run: SduiAction | SduiAction[];
612
+ interface BreadcrumbProps extends React__default.HTMLAttributes<HTMLElement> {
613
+ items: BreadcrumbItem[];
614
+ className?: string;
637
615
  }
638
- interface SduiGridCard {
639
- /**
640
- * 카드 표지로 쓸 사진 URL이 담긴 행의 필드. 주면 머리 위에 꽉 차게 깔린다.
641
- * 사진이 없는 행은 자리표시자 상자가 대신 서서 카드 높이가 들쭉날쭉해지지 않는다.
642
- */
643
- imageKey?: string;
644
- /** 표지 높이 (기본 150) */
645
- imageHeight?: number;
646
- /** 표지에 사진이 없을 때 그릴 아이콘 (기본 image) */
647
- imageFallbackIcon?: string;
648
- titleKey: string;
649
- subtitleKey?: string;
650
- /** 카드 우상단 배지 — labelMap은 표시 텍스트만 바꾸고 톤 조회 키는 원래 값이다(셀 badge와 같은 규칙) */
651
- badge?: {
652
- key: string;
653
- toneMap?: Record<string, BadgeTone>;
654
- labelMap?: Record<string, string>;
655
- };
656
- /** 한 열로 쌓는 행 */
657
- rows?: SduiGridCardRow[];
658
- /** 두 열 배치 — 좌/우 각각의 행 목록. rows와 함께 쓰면 두 열이 위, rows가 아래다 */
659
- leftRows?: SduiGridCardRow[];
660
- rightRows?: SduiGridCardRow[];
661
- /** 카드 맨 아래 보조 문구로 쓸 필드 */
662
- noteKey?: string;
663
- actions?: SduiGridCardAction[];
616
+ /** 계보 표시. 마지막 항목은 현재 객체이므로 링크가 아니다. */
617
+ declare function Breadcrumb({ items, className, ...rest }: BreadcrumbProps): React__default.JSX.Element;
618
+ /** 후보가 전부 보이는 배타 선택. 5개 이상이면 Select다. */
619
+ interface RadioProps extends React__default.InputHTMLAttributes<HTMLInputElement> {
620
+ label?: React__default.ReactNode;
621
+ description?: React__default.ReactNode;
622
+ disabled?: boolean;
664
623
  }
665
- interface SduiSearchAction {
666
- label: string;
624
+ /** 2~4개 후보를 다 보여 주는 라디오. Checkbox와 마크업을 공유한다(.wp-check). */
625
+ declare function Radio({ label, description, disabled, className, ...rest }: RadioProps): React__default.JSX.Element;
626
+ /** 주 내비게이션 목록. 활성 항목에 빨간 에지 마커가 붙는다. */
627
+ interface SidebarNavItem {
628
+ value: string;
629
+ label: React__default.ReactNode;
667
630
  icon?: string;
668
- variant?: 'default' | 'destructive' | 'outline';
669
- /** 실행할 선언적 액션 — 이벤트 값으로 선택된 행 id 배열이 전달된다 */
670
- run: SduiAction | SduiAction[];
631
+ meta?: React__default.ReactNode;
632
+ href?: string;
671
633
  }
672
- /** 두 어댑터가 공유하는 선언적 프롭 */
673
- interface SduiSearchCommonProps {
674
- title: string;
675
- columns: SduiSearchColumn[];
676
- fields?: SearchField[];
677
- leftFilters?: SearchFilter[];
678
- rightFilters?: SearchFilter[];
679
- gridCard?: SduiGridCard;
680
- actions?: SduiSearchAction[];
681
- initialView?: 'grid' | 'list';
682
- /**
683
- * 노출할 보기 목록(라벨·아이콘·순서). 생략하면 목록/카드 두 칸이 기본이다.
684
- * gridCard 없이 카드 보기를 넣으면 빈 카드가 나오므로 여기서 걸러낸다.
685
- */
686
- views?: SearchViewItem[];
687
- /** 페이지당 건수 선택지. 생략 시 [10, 20, 50, 100] */
688
- pageSizes?: number[];
689
- disableGrid?: boolean;
690
- disableList?: boolean;
691
- searchPlaceholder?: string;
692
- /** 행 클릭 시 실행할 액션 — 이벤트 값으로 행 객체 전체가 전달된다 */
693
- rowClick?: SduiAction | SduiAction[];
694
- /** 렌더러의 slots.headerActions가 이 프롭으로 들어온다 */
695
- headerActions?: React__default.ReactNode;
696
- /** 가상 스크롤 — true면 기본 임계값 150행, 숫자면 그 값이 임계값 (JSON 직렬화 가능) */
697
- virtual?: boolean | number;
634
+ interface SidebarNavSection {
635
+ label?: React__default.ReactNode;
636
+ items: SidebarNavItem[];
698
637
  }
699
- interface SduiSearchResultsProps extends SduiSearchCommonProps {
700
- data: SearchRow[];
638
+ interface SidebarNavProps extends Omit<React__default.HTMLAttributes<HTMLElement>, 'onSelect'> {
639
+ sections: SidebarNavSection[];
640
+ value?: string;
641
+ onSelect?: (value: string) => void;
642
+ /** 아이콘만 남기는 56px 레일 */
643
+ collapsed?: boolean;
644
+ /** 흰 표면(설정 하위 내비) 위에 쓰는 밝은 변형 */
645
+ light?: boolean;
646
+ className?: string;
701
647
  }
702
- interface RemoteSearchResultsProps extends SduiSearchCommonProps {
703
- /**
704
- * 목록 endpoint — page/limit/search/필드키 쿼리를 붙여 조회하고
705
- * `{ data: Row[]; total: number }`를 기대한다. 조회 자체는 어댑터(SearchFetcher)가 한다.
706
- */
707
- endpoint: string;
708
- /** 페이지당 행 수 (기본 10) — limit 파라미터의 초기값 */
709
- pageSize?: number;
648
+ /** 네이비 사이드바의 주 내비게이션. 섹션 + 항목. */
649
+ declare function SidebarNav({ sections, value, onSelect, collapsed, light, className, ...rest }: SidebarNavProps): React__default.JSX.Element;
650
+ /** 아이콘 전용 컨트롤·잘린 값에 붙이는 hover/focus 힌트. */
651
+ interface TooltipProps extends React__default.HTMLAttributes<HTMLSpanElement> {
652
+ label: React__default.ReactNode;
653
+ /** 단축키 칩, 예: "⌘K" */
654
+ shortcut?: string;
655
+ placement?: 'top' | 'bottom' | 'left' | 'right';
656
+ /** 여러 줄 허용(최대 240px) */
657
+ wrap?: boolean;
658
+ children?: React__default.ReactNode;
659
+ className?: string;
710
660
  }
711
- /** 바깥에서 온 `style`·`className`을 뿌리에 얹어 내보낸다. 까닭은 {@link ../boxed.tsx}에 있다. */
712
- declare const SduiSearchResults: React__default.FC<SduiSearchResultsProps & SduiBoxProps>;
713
- /** 바깥에서 온 `style`·`className`을 뿌리에 얹어 내보낸다. 까닭은 {@link ../boxed.tsx}에 있다. */
714
- declare const RemoteSearchResults: React__default.FC<RemoteSearchResultsProps & SduiBoxProps>;
661
+ /** hover 힌트. 트리거를 감싸며 위치는 CSS가 잡는다 — 포털을 쓰지 않는다. */
662
+ declare function Tooltip({ label, shortcut, placement, wrap, children, className, ...rest }: TooltipProps): React__default.JSX.Element;
715
663
 
716
664
  /**
717
- * 목록 표 — SearchResults 전용. 내장 `Table`은 정렬 헤더·선택 표시·가상 스크롤을
718
- * 다루지 않으므로, 같은 wp-table 마크업/클래스를 유지한 채 그 세 가지를 더한 변형이다.
665
+ * 목록 원격 조회 어댑터.
719
666
  *
720
- * 가상 스크롤은 **의존성 없이** 직접 구현한다(core는 무거운 외부 의존성을 갖지 않는다).
721
- * ERP는 @tanstack/react-virtual을 썼지만, 여기서 필요한 건 "고정 행 높이 목록의 창"뿐이라
722
- * scrollTop → 인덱스 범위 계산으로 충분하다. 렌더 범위 밖은 상/하단 스페이서 행으로 대체한다
723
- * (<table> 마크업에서 transform 대신 안전하게 동작하는 방식).
667
+ * core는 특정 HTTP 클라이언트에 묶이지 않는다 — ERP는 SWR(useErpQuery)로 조회했지만
668
+ * 라이브러리는 "endpoint + 파라미터 → { data, total }" 한 줄짜리 계약만 요구하고,
669
+ * 호스트가 자기 클라이언트(TanStack Query, SWR, 사내 fetch 래퍼…)를 끼워 넣는다.
670
+ *
671
+ * 조회 함수를 고르는 순서는 셋이다:
672
+ *
673
+ * 1. `SduiSearchDataProvider`로 **명시 주입**한 fetcher — 가장 세다. 조회를 통째로 소유하는
674
+ * 소비자(ERP RemoteSearchResults 등)의 뜻을 다른 무엇도 덮지 않는다.
675
+ * 2. 렌더러 어댑터의 `fetch` — 호스트가 자기 인증·에러 처리를 꽂아 둔 자리다. 여기를 거치면
676
+ * 소비 앱은 세션 규약을 그대로 태우고, 편집기 캔버스처럼 어댑터로 바깥을 막아 둔
677
+ * 자리에서는 목록 조회도 **함께 막힌다**. 예전에는 이 경로만 잠금 밖이라, 캔버스가
678
+ * 뜨는 것만으로 저장하지도 않은 스펙이 운영 API를 세션 쿠키와 함께 때렸다.
679
+ * 3. 둘 다 없으면 브라우저 fetch — 감싸지 않은 드롭인 소비자를 위한 폴백이다.
680
+ * 쿠키는 same-origin으로 좁힌다(1.0): endpoint가 런타임 JSON에서 오는 자리라,
681
+ * 스펙이 외부 origin을 가리켜도 세션 쿠키가 따라가면 안 된다.
724
682
  */
725
683
 
726
- interface ResultTableColumn<T> {
727
- key: string;
728
- header: React__default.ReactNode;
729
- sortable?: boolean;
730
- /** right → 우측 정렬 + tabular numerals (wp-table__num) */
731
- align?: 'left' | 'right';
732
- /** 고정폭 글꼴 셀 (wp-table__mono) */
733
- mono?: boolean;
734
- /** 강조 셀 (wp-table__strong) */
735
- strong?: boolean;
736
- /** CSS width — "180px", "22%" */
737
- width?: string;
738
- render?: (row: T, index: number) => React__default.ReactNode;
739
- }
740
- type SortDir = 'asc' | 'desc';
741
- interface ResultTableProps<T extends {
684
+ interface SearchRow {
742
685
  id: string;
743
- }> {
744
- columns: ResultTableColumn<T>[];
745
- rows: T[];
746
- sort?: {
747
- key: string;
748
- dir: SortDir;
749
- };
750
- onSort?: (key: string) => void;
751
- onRowClick?: (row: T) => void;
752
- selectedIds?: string[];
753
- /** rows가 비었을 때 표 대신 렌더 */
754
- empty?: React__default.ReactNode;
755
- /** true면 스크롤 창 안에서 보이는 행만 렌더한다 */
756
- virtual?: boolean;
757
- /** 가상 스크롤 컨테이너 높이(px). 기본 560 */
758
- height?: number;
759
- /** 가상 스크롤 행 높이(px) 초기 추정치. 첫 렌더 후 실측값으로 대체된다 — 기본 41(wp-table 기본 행) */
760
- rowHeight?: number;
686
+ [key: string]: unknown;
687
+ }
688
+ interface SearchPage {
689
+ data: SearchRow[];
690
+ total: number;
761
691
  }
762
- declare function ResultTable<T extends {
763
- id: string;
764
- }>({ columns, rows, sort, onSort, onRowClick, selectedIds, empty, virtual, height, rowHeight, }: ResultTableProps<T>): React__default.JSX.Element;
765
-
766
692
  /**
767
- * 권한 게이트 — 통과하면 children, 실패하면 fallback 슬롯(없으면 아무 것도)을 렌더한다.
768
- * 판정 자체는 하지 않는다. 호스트가 주입한 콜백(permission.tsx)에 질의만 위임한다.
769
- *
770
- * 스펙 예 (ERP 85개 페이지 중 56개가 쓰는 형태 그대로):
771
- * { "component": "Can",
772
- * "props": { "entity": "asset", "action": "create" },
773
- * "children": [ { "component": "Button", ... } ] }
693
+ * 목록 한 페이지를 가져온다. 실패하면 Error(사용자에게 보여줄 메시지)를 던진다.
694
+ * signal은 파라미터가 바뀌거나 언마운트될 때 중단 신호로 넘어온다.
774
695
  */
775
-
776
- interface CanProps {
777
- /** 대상 엔티티 식별자 */
778
- entity: string;
779
- /** 수행 동작 — 생략 시 'read'(ERP Can과 같은 기본값) */
780
- action?: string;
781
- /** 최소 적용 범위 — 생략 시 호스트 판정기의 기본값을 따른다 */
782
- scope?: string;
783
- /** 차단 시 대신 보여줄 내용 (JSON에서는 slots.fallback) */
784
- fallback?: React__default.ReactNode;
696
+ type SearchFetcher = (endpoint: string, params: URLSearchParams, signal: AbortSignal) => Promise<SearchPage>;
697
+ declare function SduiSearchDataProvider({ fetcher, children, }: {
698
+ fetcher: SearchFetcher;
699
+ children?: React__default.ReactNode;
700
+ }): React__default.JSX.Element;
701
+ /**
702
+ * 원격 목록 재조회 신호 통로.
703
+ *
704
+ * `useRemoteSearchPage`는 컴포넌트 로컬 상태라 바깥(SDUI invalidate 액션·SSE 재조회)이
705
+ * 재조회를 촉발할 길이 없었고, `SduiSearchDataProvider`는 조회 **함수**만 갈아 끼우는
706
+ * 자리라 이 문제를 풀지 못한다 — ERP가 RemoteSearchResults 전체를 포크한 유일한 이유다.
707
+ * 여기서 신호만 따로 연다: 호스트가 `createSearchInvalidator()` 하나를 만들어 Provider에
708
+ * 걸고, 어댑터의 `invalidate`(또는 subscribe 이벤트)에서 `invalidate(prefix)`를 부르면
709
+ * endpoint가 일치하는 목록이 다시 조회된다. TanStack Query 같은 호스트 캐시가 없어도
710
+ * 액션이 라이브러리 원격 목록에 닿고, 캐시를 쓰는 소비자는 fetcher 주입과 함께 쓰면 된다.
711
+ *
712
+ * prefix 판정은 렌더러 page.data의 invalidate(page-data.ts invalidateHits)와 같은 규칙이다 —
713
+ * 빈 prefix는 전부, exact면 완전 일치, 아니면 startsWith.
714
+ */
715
+ interface SearchInvalidator {
716
+ /** prefix와 일치하는 endpoint의 목록을 재조회시킨다. 생략(또는 '')하면 전부. */
717
+ invalidate(prefix?: string, exact?: boolean): void;
718
+ /** 목록 훅이 자기 endpoint로 등록한다 — 해제 함수를 돌려준다. */
719
+ subscribe(endpoint: string, reload: () => void): () => void;
720
+ }
721
+ declare function createSearchInvalidator(): SearchInvalidator;
722
+ declare function SduiSearchInvalidationProvider({ invalidator, children, }: {
723
+ invalidator: SearchInvalidator;
785
724
  children?: React__default.ReactNode;
725
+ }): React__default.JSX.Element;
726
+ declare function useSearchFetcher(): SearchFetcher;
727
+ interface RemotePageState {
728
+ page: SearchPage;
729
+ isLoading: boolean;
730
+ error: Error | null;
731
+ reload: () => void;
786
732
  }
787
- declare function Can({ entity, action, scope, fallback, children }: CanProps): React__default.JSX.Element;
788
-
789
733
  /**
790
- * 내장 기본 컴포넌트 세트 — WIPCO 디자인 시스템(wp-* 토큰)을 따르는
791
- * 독립 구현. ERP 디자인 시스템과 같은 프롭 규약을 쓰므로 스펙이 호환된다.
792
- * 스타일은 styles.css 한 파일이 담당한다.
734
+ * endpoint+params 조회 훅. 라이브러리 안에 캐시를 두지 않는다(호스트 클라이언트의 몫) —
735
+ * 대신 직전 페이지를 유지해(keepPreviousData 상당) 페이지 이동 중 목록이 깜빡이지 않게 한다.
736
+ *
737
+ * 중단 신호는 fetcher에 넘기지만 호스트가 무시할 수도 있다(어댑터 계약상 선택 사항) —
738
+ * 그래서 늦게 도착한 응답은 아래 `alive` 검사로 한 번 더 버린다.
793
739
  */
740
+ declare function useRemoteSearchPage(endpoint: string, params: URLSearchParams): RemotePageState;
794
741
 
795
- interface IconProps extends React__default.SVGAttributes<SVGSVGElement> {
796
- /** kebab-case Lucide 이름 — "scan-line", "layers", "circle-alert". */
797
- name: string;
798
- /** 픽셀 박스. 기본 18. */
799
- size?: number;
800
- /** 기본 1.5. 32px 위에서만 2를 쓴다. */
801
- strokeWidth?: number;
802
- /** 주면 role="img" + aria-label. 장식 아이콘은 생략한다. */
803
- title?: string;
804
- }
805
742
  /**
806
- * 스트로크 아이콘. 130종을 24px 그리드 위에 그린다.
743
+ * 검색 툴바 — ERP `app/components/search/SearchToolbar.tsx`의 계약(타입·헬퍼·동작)을
744
+ * 그대로 옮긴 것. 스펙 호환이 목적이므로 값 규칙(쉼표 다중값, 느슨한 검색, 범위 키 이름)은
745
+ * 한 글자도 바꾸지 않았다.
807
746
  *
808
- * **등록되지 않은 이름은 `null`이다** — 예전엔 점(●)으로 대체했는데, 그러면 스펙 오타나
809
- * 아직 안 넣은 이름이 "없던 자국"으로 화면에 남는다. 빠진 아이콘은 비어 보이는 편이
810
- * 낫고, 어차피 잘못된 이름은 check-meta가 팔레트 쪽에서 잡는다.
747
+ * - 검색 버튼을 입력창과 상세검색 토글 사이에 둔다(Enter 도 그대로 동작) —
748
+ * 원래 시안은 Enter 전용이었는데 실사용 지적으로 명시 버튼을 되돌렸다
749
+ * - 상세검색은 아이콘 버튼 → 드롭다운 패널(초기화/적용)
750
+ * - 적용된 조건은 칩(Tag)으로 나열되고, 다중 선택 칩은 값 하나씩 제거된다
811
751
  */
812
- declare function Icon({ name, size, strokeWidth, className, style, title, ...rest }: IconProps): React__default.JSX.Element | null;
813
- interface ButtonProps extends React__default.ButtonHTMLAttributes<HTMLButtonElement> {
814
- /** primary = WIPCO Blue, secondary = 외곽선(기본), ghost = 투명, danger = WIPCO Red, link = 인라인 */
815
- variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'link';
816
- /** xs 24 · sm 28 · md 32(기본) · lg 40 · xl 48 px */
817
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
818
- /** 선행 아이콘 이름 */
819
- icon?: string;
820
- /** 후행 아이콘 이름 */
821
- iconEnd?: string;
822
- /**
823
- * 라벨을 감추고 스피너를 돌린다.
824
- *
825
- * 스피너는 **CSS가 그린다**(`.wp-btn--loading::after`) — 엘리먼트를 따로 넣지 않는다.
826
- * 예전엔 `<span class="wp-spinner">`를 렌더했는데, 같은 클래스에 ::after 규칙을 가진
827
- * 디자인 시스템 스타일시트와 겹치면 원이 두 개 돈다. 그릴 주체를 CSS 한쪽으로 모은다.
828
- */
829
- loading?: boolean;
830
- block?: boolean;
831
- disabled?: boolean;
832
- /**
833
- * 다른 태그로 렌더한다. 링크로 보내야 하는 자리(SSO 이동 등)는 `as="a"` + `href`다 —
834
- * `<button>`에 href를 달면 아무 데도 가지 않는다.
835
- *
836
- * button이 아닐 때는 `disabled` 속성이 없으므로 비활성은 `aria-disabled`로만 알린다
837
- * (그래서 button일 때도 둘 다 붙인다 — 표기가 태그에 따라 달라지지 않게).
838
- */
839
- as?: 'button' | 'a' | 'span';
840
- /**
841
- * 버튼 **안쪽** 내용(아이콘·글자)의 가로 정렬. 기본은 가운데다.
842
- *
843
- * `block`으로 폭을 늘리거나 `width`를 준 버튼에서만 뜻이 있다 — 그전에는 버튼이
844
- * 내용만큼만 넓어 정렬할 여백이 없다. 실측: 스펙 85개가 `style.justifyContent:
845
- * 'flex-start'`로 4번 손으로 쓰고 있었고, 전부 폭을 늘린 버튼이었다.
846
- */
847
- justify?: 'start' | 'center' | 'end' | 'between';
848
- children?: React__default.ReactNode;
752
+
753
+ interface SearchOption {
754
+ label: string;
755
+ value: string;
849
756
  }
850
- declare function Button({ variant, size, icon, iconEnd, loading, block, disabled, as, justify, children, className, style, ...rest }: ButtonProps): React__default.JSX.Element;
851
- interface IconButtonProps extends React__default.ButtonHTMLAttributes<HTMLButtonElement> {
852
- icon: string;
853
- /** 접근성 라벨(필수 권장) — title이자 aria-label이 된다. */
854
- label?: string;
855
- /** xs 24 · sm 28 · md 32(기본) · lg 40 px 정사각 */
856
- size?: 'xs' | 'sm' | 'md' | 'lg';
757
+ interface CascadingSearchOption extends SearchOption {
758
+ children?: CascadingSearchOption[];
759
+ }
760
+ type SearchField = {
761
+ type?: 'text';
762
+ key: string;
763
+ label: string;
764
+ placeholder?: string;
765
+ } | {
857
766
  /**
858
- * 예전 이름. `variant`를 주지 않았을 때만 쓰인다 — `variant` 주석 참고.
859
- * @deprecated `variant`로 옮겨라. `tone="danger"`는 `variant="danger"`와 같다.
767
+ * 다중 선택 — 값은 쉼표로 합쳐 저장한다.
768
+ *
769
+ * `valueKey`/`labelKey` 를 주면 options 를 도메인 객체 배열로 받는다
770
+ * (`Select` 0.7.0 과 같은 계약). 스펙에서는 options 에
771
+ * `{"$state":"rt.departments","default":[]}` 를 바인딩하면 page.data 가 채운
772
+ * 실데이터가 그대로 선택지가 된다 — 정적 {label,value}만 받던 것이
773
+ * 예시값 필터가 화면마다 남던 근본 원인이었다(실사용 지적).
860
774
  */
861
- tone?: 'default' | 'danger';
775
+ type: 'select';
776
+ key: string;
777
+ label: string;
778
+ options: Array<SearchOption | Record<string, unknown>>;
779
+ valueKey?: string;
780
+ labelKey?: string;
781
+ allLabel?: string;
782
+ } | {
783
+ type: 'dateRange';
784
+ key: string;
785
+ label: string;
786
+ fromKey?: string;
787
+ toKey?: string;
788
+ } | {
789
+ type: 'numberRange';
790
+ key: string;
791
+ label: string;
792
+ fromKey?: string;
793
+ toKey?: string;
794
+ minPlaceholder?: string;
795
+ maxPlaceholder?: string;
796
+ } | {
862
797
  /**
863
- * 디자인 시스템 어휘. ghost = 투명(기본) · outlined = 하드라인 테두리 ·
864
- * solid = 채움 · danger = 빨강 · onchrome = 네이비 사이드바/탑바 위.
798
+ * 계층 다중 선택 — 단계별 체크박스 목록 체인. 선택된 값들을 쉼표로 합쳐 저장하며,
799
+ * 부모를 선택하면 그 자손 선택은 제거되고 자손을 선택하면 그 조상 선택이 제거된다.
865
800
  *
866
- * 같은 자리를 가리키는 프롭이 `tone`과 둘로 갈라져 있는데 **variant가 이긴다** —
867
- * 스펙과 디자인 시스템 문서가 쓰는 이름이 이쪽이고, 값 어휘도 넓다(outlined는 tone에 없다).
868
- * variant를 주지 않으면 tone이 그대로 먹으므로 tone만 쓰던 코드는 그대로 돈다.
801
+ * `tree` 를 주면 options 를 **평면 행 배열**로 받아 여기서 계층을 세운다 —
802
+ * `/categories` 처럼 {id, parent_id, name} 평면 목록을 주는 API 를 스펙이
803
+ * 트리로 재조립할 방법이 없어서다($calc 에 그런 연산이 없다).
869
804
  */
870
- variant?: 'ghost' | 'outlined' | 'solid' | 'danger' | 'onchrome';
871
- /** 토글 상태 — aria-pressed로 나가고 선택 틴트를 입는다. */
872
- pressed?: boolean;
873
- disabled?: boolean;
805
+ type: 'cascading';
806
+ key: string;
807
+ label: string;
808
+ options: Array<CascadingSearchOption | Record<string, unknown>>;
809
+ tree?: {
810
+ idKey: string;
811
+ parentIdKey: string;
812
+ labelKey: string;
813
+ };
814
+ allLabel?: string;
815
+ };
816
+ type SearchValues = Record<string, string>;
817
+ declare function splitMultiValue(value: string): string[];
818
+ declare function joinMultiValue(values: string[]): string;
819
+ declare function normalizeSearchText(value: string): string;
820
+ declare function includesLooseSearch(text: string, keyword: string): boolean;
821
+ /** 필드가 점유하는 파라미터 키 전체 — 범위 필드는 From/To(Min/Max) 두 개다 */
822
+ declare function searchFieldKeys(fields: SearchField[]): string[];
823
+ declare function searchValuesFromParams(fields: SearchField[], params: URLSearchParams): SearchValues;
824
+ declare function cleanSearchValues(fields: SearchField[], values: SearchValues): SearchValues;
825
+ /**
826
+ * 값 → 자기 자신을 포함한 조상 값 목록(루트→자신 순).
827
+ * SearchResults의 클라이언트 필터링에서 "부모 선택 = 자손 값 매칭"을 구현할 때 쓴다.
828
+ */
829
+ declare function cascadingValuePaths(options: CascadingSearchOption[]): Map<string, string[]>;
830
+ interface SearchToolbarProps {
831
+ search: string;
832
+ onSearchChange: (value: string) => void;
833
+ onSearch: (search: string, advancedValues: SearchValues) => void;
834
+ advancedFields?: SearchField[];
835
+ advancedValues?: SearchValues;
836
+ placeholder?: string;
837
+ className?: string;
838
+ /** 필터 전용 모드: 검색 입력창 없이 상세검색 버튼+칩만 보여준다 */
839
+ hideSearchInput?: boolean;
874
840
  }
875
- declare function IconButton({ icon, label, size, tone, variant, pressed, disabled, className, type, ...rest }: IconButtonProps): React__default.JSX.Element;
876
- interface InputProps extends Omit<React__default.InputHTMLAttributes<HTMLInputElement>, 'size'> {
877
- /** sm 28 · md 32(기본) · lg 40 px */
878
- size?: 'sm' | 'md' | 'lg';
879
- /** 선행 아이콘 이름 (예: "search") */
841
+ declare function SearchToolbar({ search, onSearchChange, onSearch, advancedFields: rawAdvancedFields, advancedValues, placeholder, className, hideSearchInput, }: SearchToolbarProps): React__default.JSX.Element;
842
+
843
+ /**
844
+ * 검색 결과 목록 — ERP `app/components/search/SearchResults.tsx`의 계약을 그대로 가져온 포팅.
845
+ * - 목록(Table)/카드 그리드(Card) 전환, 정렬, 좌/우 필터, 선택 후 일괄 액션, 페이지네이션
846
+ * - 데이터 가공(검색·필터·정렬·페이지 슬라이스)은 로컬 데이터 기준 클라이언트에서 수행하며,
847
+ * 서버 페이징으로 넘어가면 params를 그대로 백엔드 질의로 옮기면 된다
848
+ *
849
+ * ERP와 다른 점은 하나뿐이다: 검색/필터/페이지 파라미터를 URL에 직접 쓰지 않고
850
+ * `searchParams` + `onParamsChange`로만 다룬다(controlled). core는 라우터를 알지 않으며,
851
+ * 한 페이지에 목록이 여러 개 있어도 서로의 URL을 오염시키지 않는다.
852
+ */
853
+
854
+ /** DS Badge 톤 — 컴포넌트 프롭에서 파생한다. 재선언하면 Badge에 톤이 늘어도 여기만 좁게 남는다. */
855
+ type BadgeTone = NonNullable<BadgeProps['tone']>;
856
+ interface SearchAction<T> {
857
+ label: string;
858
+ onClick: (selectedItems: T[]) => void | Promise<void>;
880
859
  icon?: string;
881
- /** 빨간 테두리 — Field error와 함께 사용 */
882
- invalid?: boolean;
883
- /** 고정폭 글꼴 — ID, 좌표, 해시 */
884
- mono?: boolean;
885
- /**
886
- * 입력 오른쪽 끝에 겹쳐 그리는 후행 표시.
887
- *
888
- * **문자열은 아이콘 이름으로 읽는다** — `trailing="eye"`(비밀번호 보기)가 원래 용례다.
889
- * 등록되지 않은 이름이면 Icon이 null을 내므로 자리만 남는다. 단위 글자("원", "mm")를
890
- * 넣고 싶으면 문자열이 아니라 노드로 줘라: `trailing={<span>원</span>}`.
891
- */
892
- trailing?: React__default.ReactNode;
860
+ variant?: 'default' | 'destructive' | 'outline';
893
861
  }
894
- declare function Input({ size, icon, invalid, mono, trailing, className, style, ...rest }: InputProps): React__default.JSX.Element;
895
- interface TextareaProps extends React__default.TextareaHTMLAttributes<HTMLTextAreaElement> {
896
- invalid?: boolean;
897
- rows?: number;
862
+ interface SearchColumn<T> {
863
+ key: string;
864
+ title: string;
865
+ sortable?: boolean;
866
+ /** DS Table은 left/right만 지원한다 */
867
+ textAlign?: 'left' | 'right';
868
+ /** 상대 폭 — 전체 ratio 합 대비 % 폭으로 환산된다 */
869
+ ratio?: number;
870
+ mono?: boolean;
871
+ strong?: boolean;
872
+ accessor?: (item: T) => string | number | Date | null | undefined;
873
+ render?: (item: T) => React__default.ReactNode;
898
874
  }
899
- declare function Textarea({ invalid, rows, className, ...rest }: TextareaProps): React__default.JSX.Element;
900
- interface SelectOption {
901
- value: string;
875
+ interface SearchFilter {
876
+ key: string;
902
877
  label: string;
903
- disabled?: boolean;
878
+ options: {
879
+ key: string;
880
+ label: string;
881
+ value: string;
882
+ }[];
904
883
  }
905
884
  /**
906
- * 자유 형식 옵션 — `valueKey`/`labelKey`가 가리키는 키로 읽는다.
885
+ * 목록/카드 전환 버튼 한 칸.
907
886
  *
908
- * `AutocompleteOption`과 같은 모양(`Record<string, unknown>`)으로 맞췄다. 두 어휘가
909
- * 같은 자리에서 같은 프롭 이름을 쓰는데 옵션 타입만 다르면 스펙 작성자가 둘 사이를
910
- * 옮길 때마다 다시 배워야 한다.
887
+ * 예전에는 이 두 칸(`목록`/`카드`)이 코드에 박혀 있어 스펙이 라벨·아이콘·순서를
888
+ * 바꿀 수 없었고, `disableGrid`/`disableList`로 **끄는 것만** 가능했다.
889
+ * 이제 `views`로 목록 자체를 정할 수 있고, 두 disable 프롭은 그 축약형으로 남는다.
911
890
  */
912
- type SelectOptionRecord = Record<string, unknown>;
913
- interface SelectProps extends Omit<React__default.SelectHTMLAttributes<HTMLSelectElement>, 'size'> {
914
- /**
915
- * 옵션 목록. 기본은 `{value, label}`이고, 도메인 객체(`{id, title, …}`)를 그대로
916
- * 넘길 거면 `valueKey`/`labelKey`로 읽을 키를 알려 준다. 값과 라벨이 같은 목록은
917
- * 문자열·숫자 배열(`["S","A","B","C"]`)을 그대로 줘도 된다.
918
- */
919
- options: (SelectOption | SelectOptionRecord | string | number)[];
920
- /** 옵션에서 값을 읽을 키. 기본 'value'. */
921
- valueKey?: string;
922
- /** 옵션에서 표시 라벨을 읽을 키. 기본 'label'. */
923
- labelKey?: string;
924
- size?: 'sm' | 'md' | 'lg';
925
- invalid?: boolean;
926
- /** 비어 있는 첫 옵션 */
927
- placeholder?: string;
928
- }
929
- declare function Select({ options, valueKey, labelKey, size, invalid, placeholder, className, ...rest }: SelectProps): React__default.JSX.Element;
930
- interface CheckboxProps extends React__default.InputHTMLAttributes<HTMLInputElement> {
931
- label?: React__default.ReactNode;
932
- description?: React__default.ReactNode;
933
- /**
934
- * 부분 선택 — 전체선택 체크박스의 "일부만 골랐다" 상태.
935
- *
936
- * HTML 속성이 아니라 DOM 프로퍼티라 ref로 직접 넣어야 한다(마크업에 남지 않는다).
937
- * 글리프도 체크가 아니라 빼기로 바뀐다.
938
- */
939
- indeterminate?: boolean;
940
- disabled?: boolean;
891
+ interface SearchViewItem {
892
+ value: 'grid' | 'list';
893
+ /** 생략 시 기본 아이콘 — list=list, grid=layout-grid */
894
+ icon?: string;
895
+ /** 생략 시 기본 라벨 — list=목록, grid=카드 */
896
+ label?: string;
941
897
  }
942
- declare function Checkbox({ label, description, indeterminate, disabled, className, ...rest }: CheckboxProps): React__default.JSX.Element;
943
- interface SwitchProps extends Omit<React__default.InputHTMLAttributes<HTMLInputElement>, 'size'> {
944
- label?: React__default.ReactNode;
945
- size?: 'sm' | 'md';
946
- disabled?: boolean;
898
+ interface GridCardRow<T> {
899
+ icon?: React__default.ReactNode | ((item: T) => React__default.ReactNode);
900
+ label?: React__default.ReactNode | ((item: T) => React__default.ReactNode);
901
+ value?: React__default.ReactNode | ((item: T) => React__default.ReactNode);
902
+ show?: (item: T) => boolean;
947
903
  }
948
- declare function Switch({ label, size, disabled, className, ...rest }: SwitchProps): React__default.JSX.Element;
949
- interface FieldProps extends React__default.HTMLAttributes<HTMLDivElement> {
950
- label?: React__default.ReactNode;
951
- /** 에러가 없을 때 보여줄 안내 한 줄 */
952
- hint?: React__default.ReactNode;
953
- /** hint를 대체하고 빨간색으로 표시 */
954
- error?: React__default.ReactNode;
955
- required?: boolean;
956
- htmlFor?: string;
957
- /** stacked(기본) 또는 horizontal(132px 라벨 컬럼) */
958
- layout?: 'stacked' | 'horizontal';
959
- children?: React__default.ReactNode;
904
+ interface GridCardConfig<T> {
905
+ /** 머리 위 표지 — 보통 대표 사진(cells.tsx의 imageRenderer)을 넘긴다 */
906
+ cover?: (item: T) => React__default.ReactNode;
907
+ title: (item: T) => React__default.ReactNode;
908
+ subtitle?: (item: T) => React__default.ReactNode;
909
+ badge?: (item: T) => {
910
+ text: React__default.ReactNode;
911
+ variant?: BadgeTone;
912
+ } | null;
913
+ actions?: (item: T) => React__default.ReactNode;
914
+ rows?: GridCardRow<T>[];
915
+ leftRows?: GridCardRow<T>[];
916
+ rightRows?: GridCardRow<T>[];
917
+ note?: (item: T) => React__default.ReactNode | null;
960
918
  }
961
- declare function Field({ label, hint, error, required, htmlFor, layout, children, className, ...rest }: FieldProps): React__default.JSX.Element;
962
- interface CardProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, 'title'> {
963
- title?: React__default.ReactNode;
964
- subtitle?: React__default.ReactNode;
965
- /** 헤더 우측 — 버튼, Select 등 */
966
- actions?: React__default.ReactNode;
967
- /** 우측 정렬 푸터 행 */
968
- footer?: React__default.ReactNode;
919
+ interface SearchResultsProps<T> {
920
+ item: {
921
+ data: T[];
922
+ total: number;
923
+ };
924
+ columns: SearchColumn<T>[];
925
+ gridConfig?: GridCardConfig<T>;
926
+ leftFilters?: SearchFilter[];
927
+ rightFilters?: SearchFilter[];
928
+ initialView?: 'grid' | 'list';
969
929
  /**
970
- * 머리 위에 꽉 차게 깔리는 표지 — 목록 카드의 대표 사진 자리다.
971
- * 카드 안쪽 여백을 무시하고 위 두 모서리만 카드 곡률을 따른다.
930
+ * 노출할 보기 목록. 생략하면 목록/카드 두 칸이 기본값이고,
931
+ * 그때만 `disableGrid`/`disableList`가 목록을 깎는다(둘 다 오면 views가 이긴다).
932
+ * 한 칸만 남으면 전환 버튼 자체를 그리지 않는다.
972
933
  */
973
- cover?: React__default.ReactNode;
974
- variant?: 'default' | 'raised' | 'flat' | 'sunken';
975
- selected?: boolean;
976
- interactive?: boolean;
977
- /** default 20px · tight 14px · none(테이블, 리스트) */
978
- padding?: 'default' | 'tight' | 'none';
979
- children?: React__default.ReactNode;
980
- }
981
- declare function Card({ title, subtitle, actions, footer, cover, variant, selected, interactive, padding, children, className, ...rest }: CardProps): React__default.JSX.Element;
982
- interface BadgeProps extends React__default.HTMLAttributes<HTMLSpanElement> {
983
- tone?: 'neutral' | 'info' | 'success' | 'warning' | 'danger' | 'brand';
984
- /** 선행 5px 점 — 라이브/진행 상태 */
985
- dot?: boolean;
986
- size?: 'md' | 'lg';
987
- children?: React__default.ReactNode;
988
- }
989
- declare function Badge({ tone, dot, size, children, className, ...rest }: BadgeProps): React__default.JSX.Element;
990
- interface TagProps extends React__default.HTMLAttributes<HTMLSpanElement> {
991
- /** 선행 점의 CSS 색상 */
992
- color?: string;
993
- filled?: boolean;
994
- onRemove?: (e: React__default.MouseEvent) => void;
995
- children?: React__default.ReactNode;
934
+ views?: SearchViewItem[];
935
+ /** 페이지당 건수 선택지. 생략 시 [10, 20, 50, 100]. 첫 값이 기본 페이지 크기다. */
936
+ pageSizes?: number[];
937
+ className?: string;
938
+ /** 바깥에서 온 인라인 style — 자기 축(폭·여백)이 이 컴포넌트에도 닿게 하는 통로다. */
939
+ style?: React__default.CSSProperties;
940
+ /** 검색/필터/페이지 파라미터 (controlled) */
941
+ searchParams: URLSearchParams;
942
+ onParamsChange: (next: URLSearchParams) => void;
943
+ /** 선택 액션 — 있으면 체크박스 선택이 활성화된다 */
944
+ actions?: SearchAction<T>[];
945
+ disableGrid?: boolean;
946
+ disableList?: boolean;
947
+ onRowClick?: (item: T) => void;
948
+ /** 화면에는 카운트만 표시 — 접근성 레이블·빈 상태 문구에 사용 */
949
+ title: string;
950
+ headerActions?: React__default.ReactNode;
951
+ isLoading?: boolean;
952
+ hideHeader?: boolean;
953
+ advancedSearchFields?: SearchField[];
954
+ searchPlaceholder?: string;
955
+ /**
956
+ * 가상 스크롤(대용량 목록) — true면 기본 임계값 150행, 숫자면 그 값이 임계값.
957
+ * 목록 뷰에서 행 수가 임계값을 넘으면 페이지 슬라이스 대신 전체 목록을 창 렌더한다
958
+ * (서버 페이징 모드에서는 현재 응답 분량만 가상화).
959
+ */
960
+ virtual?: boolean | number;
996
961
  }
997
- declare function Tag({ color, filled, onRemove, children, className, ...rest }: TagProps): React__default.JSX.Element;
998
- interface AlertProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, 'title'> {
999
- tone?: 'info' | 'success' | 'warning' | 'danger' | 'neutral';
1000
- title?: React__default.ReactNode;
1001
- children?: React__default.ReactNode;
1002
- actions?: React__default.ReactNode;
1003
- onClose?: () => void;
962
+ /**
963
+ * 스펙이 준 페이지 크기 목록을 다듬는다 — 양의 정수만, 중복 제거, 오름차순.
964
+ * 쓸 값이 하나도 없으면 undefined를 돌려 기본 선택지를 쓰게 한다.
965
+ */
966
+ declare function normalizePageSizes(sizes: number[] | undefined): number[] | undefined;
967
+ declare function SearchResults<T extends {
968
+ id: string;
969
+ }>({ item, columns, gridConfig, leftFilters, rightFilters, initialView, views, pageSizes, className, style, searchParams, onParamsChange, actions, disableGrid, disableList, onRowClick, title, headerActions, isLoading, hideHeader, advancedSearchFields: rawAdvancedSearchFields, searchPlaceholder, virtual, }: SearchResultsProps<T>): React__default.JSX.Element;
970
+
971
+ /**
972
+ * 셀 표현 어휘 — 목록 컬럼의 셀과 카드 행의 값이 **같은 규칙**을 쓴다.
973
+ *
974
+ * 예전에는 스펙이 고를 수 있는 표현이 `badge` 하나뿐이었고 나머지는 `row[key]` 원문
975
+ * 출력이었다. 밑에 깔린 `SearchColumn.render`는 임의의 노드를 그릴 수 있는데 스펙에는
976
+ * 그 역량이 두 종류(원문/배지)로만 열려 있었다는 뜻이다. 여기서 그 구멍을 메운다.
977
+ *
978
+ * 규약 두 가지:
979
+ * - **모르는 type은 원문으로 강등한다.** 스펙은 신뢰할 수 없는 입력이고, 컬럼 하나 때문에
980
+ * 목록 전체가 비면 안 된다. 판별 유니언이라 새 type을 더해도 옛 스펙은 그대로 돈다.
981
+ * - **값을 해석하지 못하면 원문을 그대로 낸다.** ERP는 이미 서식된 문자열(`"12,400,000"`,
982
+ * `"2026-07-31 09:00"`)을 내려주는 자리가 많아, 파싱 실패를 빈칸으로 만들면 퇴행이다.
983
+ */
984
+
985
+ /** ProgressBar가 받는 톤 — 컴포넌트 프롭에서 파생한다. 재선언하면 한쪽만 바뀌었을 때 조용히 어긋난다. */
986
+ type MeterTone = NonNullable<ProgressBarProps['tone']>;
987
+ /** 값을 그대로 출력한다. cell을 생략한 것과 같으며, 명시하고 싶을 때 쓴다. */
988
+ interface SduiTextCell {
989
+ type: 'text';
990
+ }
991
+ /** 값을 배지로. 톤은 값별 매핑(없는 값은 neutral). */
992
+ interface SduiBadgeCell {
993
+ type: 'badge';
994
+ toneMap?: Record<string, BadgeTone>;
1004
995
  /**
1005
- * 테두리를 왼쪽 색 막대 하나로 바꾼다(모서리도 각지게 편다).
1006
- *
1007
- * 화면 맨 위에 놓여 먼저 읽혀야 하는 공지 — 개인정보 보호 안내, 업로드 절차 안내처럼
1008
- * 카드 안의 알림이 아니라 화면 폭을 가르는 배너로 보여야 하는 자리에 쓴다.
996
+ * 값 → 표시 텍스트(생략 시 값 그대로) — Table 컬럼 badge와 같은 의미론.
997
+ * 톤 조회 키는 여전히 **원래 값**이다. labelMap이 조회 키까지 바꾸면 기존 스펙의
998
+ * toneMap(enum 키)이 전부 neutral로 떨어진다.
1009
999
  */
1010
- bar?: boolean;
1000
+ labelMap?: Record<string, string>;
1011
1001
  }
1012
- declare function Alert({ tone, title, children, actions, onClose, bar, className, ...rest }: AlertProps): React__default.JSX.Element;
1013
- interface ToastProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, 'title'> {
1014
- tone?: 'info' | 'success' | 'warning' | 'danger';
1015
- title?: React__default.ReactNode;
1016
- children?: React__default.ReactNode;
1017
- /** 인라인 액션 하나 — 되돌리기 같은 것. Button variant="link"를 넣는다. */
1018
- action?: React__default.ReactNode;
1019
- onClose?: () => void;
1002
+ /** 값을 태그로. 선행 점 색은 값별 매핑(CSS 색상 문자열). */
1003
+ interface SduiTagCell {
1004
+ type: 'tag';
1005
+ colorMap?: Record<string, string>;
1006
+ filled?: boolean;
1020
1007
  }
1021
- /** 우하단 일시 알림. .wp-toast-region 안에 렌더링된다. */
1022
- declare function Toast({ tone, title, children, action, onClose, className, ...rest }: ToastProps): React__default.JSX.Element;
1023
- interface TabItem {
1024
- value: string;
1025
- label: React__default.ReactNode;
1008
+ /**
1009
+ * 값을 링크로. `to`는 `/assets/{id}` 처럼 행의 필드를 `{키}`로 끼워 넣는 경로 템플릿이고,
1010
+ * 클릭하면 navigate 액션이 나간다(렌더러가 대상 안전성을 다시 검사한다).
1011
+ */
1012
+ interface SduiLinkCell {
1013
+ type: 'link';
1014
+ to: string;
1015
+ /** 링크 문구로 쓸 필드 — 생략하면 컬럼 자신의 값 */
1016
+ labelKey?: string;
1026
1017
  icon?: string;
1027
- count?: number;
1028
1018
  }
1029
- interface TabsProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, 'onChange'> {
1030
- items: TabItem[];
1031
- value: string;
1032
- onChange?: (value: string) => void;
1033
- size?: 'md' | 'lg';
1034
- /** 네이비 탑바 위 — 밝은 글자, 빨간 인디케이터. */
1035
- onChrome?: boolean;
1019
+ /** 값을 진행 막대로. 분모는 maxKey(행의 필드) → max → 100 순으로 정한다. */
1020
+ interface SduiProgressCell {
1021
+ type: 'progress';
1022
+ max?: number;
1023
+ maxKey?: string;
1024
+ tone?: MeterTone;
1025
+ showValue?: boolean;
1036
1026
  }
1037
- declare function Tabs({ items, value, onChange, size, onChrome, className, ...rest }: TabsProps): React__default.JSX.Element;
1038
- interface TableColumn {
1027
+ /** 값을 아이콘으로. iconMap에 없는 값은 icon(공통 기본)으로, 그것도 없으면 원문으로. */
1028
+ interface SduiIconCell {
1029
+ type: 'icon';
1030
+ iconMap?: Record<string, string>;
1031
+ icon?: string;
1032
+ /** 아이콘 뒤에 값도 적는다 */
1033
+ withLabel?: boolean;
1034
+ }
1035
+ /**
1036
+ * 숫자 서식(ko-KR 천단위) + 접두/접미. 통화는 `{ type:'number', suffix:'원' }`이다 —
1037
+ * `currency` 타입을 따로 두지 않은 이유는 ERP가 원화 단일이고, 그 자리에 필요한 것이
1038
+ * 통화 기호가 아니라 "단위 문자열"이기 때문이다(원·건·개·%·kg).
1039
+ */
1040
+ interface SduiNumberCell {
1041
+ type: 'number';
1042
+ /** 소수 자릿수 (기본 0) */
1043
+ digits?: number;
1044
+ prefix?: string;
1045
+ suffix?: string;
1046
+ }
1047
+ /**
1048
+ * 값(이미지 URL)을 사진으로. 목록 행·카드 표지가 같은 규칙을 쓴다.
1049
+ *
1050
+ * **사진 값은 언제나 눌러서 크게 볼 수 있다.** 확대를 옵션으로 두면 스펙마다 켜고 끄는
1051
+ * 자리가 생기고, 켜지 않은 화면에서는 목록의 작은 썸네일이 사실상 못 보는 사진이 된다.
1052
+ * 원본 보기는 사진이라는 값 자체의 성질로 본다. 뷰어는 코어의 `ImageViewer` 하나뿐이라
1053
+ * (휠 줌·드래그 패닝·툴바) 목록·카드 표지·상세 첨부가 전부 같게 동작한다.
1054
+ *
1055
+ * **크게 볼 수 있는 것은 실제로 뜬 사진뿐이다.** URL이 없으면, 그리고 URL이 있어도
1056
+ * 로딩이 실패하면(끊긴 링크·삭제된 객체) 자리표시자 상자로 떨어지고 눌리지 않는다 —
1057
+ * 빈 화면이 뜨는 다이얼로그를 열어 주는 것보다 낫다.
1058
+ *
1059
+ * **값이 비어도 상자 크기는 같다.** 칸을 비우면 사진 있는 행만 높이가 달라져 표가
1060
+ * 흔들린다. 아이콘 이름은 도메인마다 다르므로(장비는 building-2, 사람은 user)
1061
+ * `fallbackIcon`으로 받고 기본값만 중립적인 `image`로 둔다.
1062
+ */
1063
+ interface SduiImageCell {
1064
+ type: 'image';
1065
+ /** 기본 72 — 카드 커버로 쓰일 때는 컨테이너가 '100%'로 덮어쓴다 */
1066
+ width?: number;
1067
+ /** 기본 52 */
1068
+ height?: number;
1069
+ /** object-fit (기본 cover) */
1070
+ fit?: 'cover' | 'contain';
1071
+ /** 사진이 없을 때 그릴 아이콘 이름 (기본 image) */
1072
+ fallbackIcon?: string;
1073
+ /** 대체 텍스트를 담은 행의 필드 — 없으면 빈 alt(장식으로 취급) */
1074
+ altKey?: string;
1075
+ }
1076
+ /** 날짜 서식(ko-KR). 파싱되지 않는 값은 원문 그대로. */
1077
+ interface SduiDateCell {
1078
+ type: 'date';
1079
+ format?: 'date' | 'datetime' | 'time';
1080
+ }
1081
+ type SduiCell = SduiTextCell | SduiImageCell | SduiBadgeCell | SduiTagCell | SduiLinkCell | SduiProgressCell | SduiIconCell | SduiNumberCell | SduiDateCell;
1082
+ /**
1083
+ * 사진 셀 — 카드 커버도 이 함수를 쓴다(`width: '100%'` 을 넘겨 폭만 늘린다).
1084
+ * 상자 크기를 값이 있든 없든 똑같이 잡아 두는 것이 요점이다.
1085
+ */
1086
+ declare function imageRenderer(cell: SduiImageCell, key: string, width?: number | string): (row: SearchRow) => React__default.ReactNode;
1087
+ /**
1088
+ * 셀 스펙 → 렌더 함수. `undefined`를 돌려주면 호출자가 원문을 그대로 출력한다
1089
+ * (표에서는 `<td>{값}</td>`, 카드에서는 값 문자열).
1090
+ *
1091
+ * 값 조회는 전부 calc.ts의 `fieldOf`다 — 평면 키가 먼저고, 없으면 점 경로(`"department.name"`)로
1092
+ * 판다. 조인 객체를 담은 행을 소비자가 미리 한 겹 펴지 않아도 되고(ERP flattenRow 글루의
1093
+ * 존재 이유), 이름에 점이 든 평면 키를 쓰던 기존 스펙은 그대로 산다.
1094
+ */
1095
+ declare function cellRenderer(cell: SduiCell | undefined, key: string, dispatch: SduiDispatch): ((row: SearchRow) => React__default.ReactNode) | undefined;
1096
+
1097
+ /** JSON 컬럼 정의 — render 함수 대신 선언적 cell 스펙을 쓴다 */
1098
+ interface SduiSearchColumn {
1039
1099
  key: string;
1040
- header: React__default.ReactNode;
1041
- /** CSS 폭 — "180px", "22%" */
1042
- width?: string;
1043
- align?: 'left' | 'right';
1100
+ title: string;
1101
+ sortable?: boolean;
1102
+ /** DS Table은 left/right만 지원 */
1103
+ textAlign?: 'left' | 'right';
1104
+ ratio?: number;
1044
1105
  mono?: boolean;
1045
1106
  strong?: boolean;
1046
- /** 헤더를 정렬 버튼으로 만든다. 실제 정렬은 `sort`·`onSort`가 담당한다. */
1047
- sortable?: boolean;
1107
+ /** 셀 표현. 생략하면 row[key]를 그대로 출력한다 — 어휘는 `cells.tsx` 참고 */
1108
+ cell?: SduiCell;
1109
+ }
1110
+ /**
1111
+ * 카드 행 표시 조건.
1112
+ * `key`만 주면 "그 값이 비어 있지 않을 때"다. 값이 없는 행을 감춰 카드 높이가
1113
+ * 들쭉날쭉해지지 않게 하는 것이 주 용도라 그것이 기본 의미다.
1114
+ */
1115
+ interface SduiRowCondition {
1116
+ key: string;
1117
+ equals?: string | number | boolean;
1118
+ in?: Array<string | number>;
1119
+ notEmpty?: boolean;
1120
+ }
1121
+ interface SduiGridCardRow {
1122
+ label?: string;
1123
+ valueKey: string;
1124
+ /** 라벨 앞 아이콘 */
1125
+ icon?: string;
1126
+ /** 값 표현 — 컬럼 셀과 같은 어휘 */
1127
+ cell?: SduiCell;
1128
+ /** 조건부 행 */
1129
+ showWhen?: SduiRowCondition;
1130
+ }
1131
+ /** 카드 우상단 버튼 — 이벤트 값으로 행 객체 전체가 전달된다 */
1132
+ interface SduiGridCardAction {
1133
+ label: string;
1134
+ icon?: string;
1135
+ variant?: 'default' | 'destructive' | 'outline';
1136
+ run: SduiAction | SduiAction[];
1137
+ }
1138
+ interface SduiGridCard {
1048
1139
  /**
1049
- * 셀 값을 Badge로 렌더링 — JSON 스펙에서 쓸 수 있는 선언적 지정.
1050
- * toneMap: 값 → tone, labelMap: 값 → 표시 텍스트(생략 시 값 그대로)
1140
+ * 카드 표지로 쓸 사진 URL이 담긴 행의 필드. 주면 머리 위에 꽉 차게 깔린다.
1141
+ * 사진이 없는 행은 자리표시자 상자가 대신 서서 카드 높이가 들쭉날쭉해지지 않는다.
1051
1142
  */
1143
+ imageKey?: string;
1144
+ /** 표지 높이 (기본 150) */
1145
+ imageHeight?: number;
1146
+ /** 표지에 사진이 없을 때 그릴 아이콘 (기본 image) */
1147
+ imageFallbackIcon?: string;
1148
+ titleKey: string;
1149
+ subtitleKey?: string;
1150
+ /** 카드 우상단 배지 — labelMap은 표시 텍스트만 바꾸고 톤 조회 키는 원래 값이다(셀 badge와 같은 규칙) */
1052
1151
  badge?: {
1053
- toneMap?: Record<string, string>;
1054
- labelMap?: Record<string, string>;
1055
- };
1056
- /** 프로그래매틱 사용 전용 — JSON 스펙에서는 쓸 수 없다 */
1057
- render?: (row: any, index: number) => React__default.ReactNode;
1058
- }
1059
- interface TableProps extends React__default.TableHTMLAttributes<HTMLTableElement> {
1060
- columns: TableColumn[];
1061
- rows: any[];
1062
- /** 40px 대신 32px 행 */
1063
- dense?: boolean;
1064
- hover?: boolean;
1065
- /** 스크롤 본문용 고정 헤더 */
1066
- sticky?: boolean;
1067
- /** 현재 정렬 상태 — 해당 컬럼 헤더의 화살표 방향을 정한다 */
1068
- sort?: {
1069
1152
  key: string;
1070
- dir: 'asc' | 'desc';
1153
+ toneMap?: Record<string, BadgeTone>;
1154
+ labelMap?: Record<string, string>;
1071
1155
  };
1072
- /** 정렬 헤더를 눌렀을 때. 주지 않으면 sortable 컬럼도 버튼이 되지 않는다 */
1073
- onSort?: (key: string) => void;
1074
- /** 선택된 행의 rowKey 값 — 해당 행에 aria-selected가 붙고 하이라이트된다 */
1075
- selectedIds?: any[];
1076
- /** 행 key와 선택에 쓸 필드. 기본 "id" */
1077
- rowKey?: string;
1078
- onRowClick?: (row: any) => void;
1079
- /** rows가 비었을 때 대신 렌더링 — EmptyState 권장 */
1080
- empty?: React__default.ReactNode;
1156
+ /** 한 열로 쌓는 행 */
1157
+ rows?: SduiGridCardRow[];
1158
+ /** 두 열 배치 — 좌/우 각각의 행 목록. rows와 함께 쓰면 두 열이 위, rows가 아래다 */
1159
+ leftRows?: SduiGridCardRow[];
1160
+ rightRows?: SduiGridCardRow[];
1161
+ /** 카드 맨 아래 보조 문구로 쓸 필드 */
1162
+ noteKey?: string;
1163
+ actions?: SduiGridCardAction[];
1081
1164
  }
1082
- declare function Table({ columns, rows, dense, hover, sticky, sort, onSort, selectedIds, rowKey, onRowClick, empty, className, ...rest }: TableProps): React__default.JSX.Element;
1083
- interface StatCardProps extends React__default.HTMLAttributes<HTMLDivElement> {
1084
- label: React__default.ReactNode;
1085
- value: React__default.ReactNode;
1086
- /** 단위 접미사 — "GB", "건", "%" */
1087
- unit?: string;
1088
- /** 비교 텍스트 — "+12.4%" */
1089
- delta?: React__default.ReactNode;
1090
- trend?: 'up' | 'down' | 'flat';
1165
+ interface SduiSearchAction {
1166
+ label: string;
1091
1167
  icon?: string;
1092
- footer?: React__default.ReactNode;
1093
- size?: 'md' | 'sm';
1168
+ variant?: 'default' | 'destructive' | 'outline';
1169
+ /** 실행할 선언적 액션 — 이벤트 값으로 선택된 행 id 배열이 전달된다 */
1170
+ run: SduiAction | SduiAction[];
1094
1171
  }
1095
- declare function StatCard({ label, value, unit, delta, trend, icon, footer, size, className, ...rest }: StatCardProps): React__default.JSX.Element;
1096
- interface EmptyStateProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, 'title'> {
1097
- icon?: string;
1098
- title?: React__default.ReactNode;
1099
- children?: React__default.ReactNode;
1100
- actions?: React__default.ReactNode;
1101
- tight?: boolean;
1172
+ /** 두 어댑터가 공유하는 선언적 프롭 */
1173
+ interface SduiSearchCommonProps {
1174
+ title: string;
1175
+ columns: SduiSearchColumn[];
1176
+ fields?: SearchField[];
1177
+ leftFilters?: SearchFilter[];
1178
+ rightFilters?: SearchFilter[];
1179
+ gridCard?: SduiGridCard;
1180
+ actions?: SduiSearchAction[];
1181
+ initialView?: 'grid' | 'list';
1182
+ /**
1183
+ * 노출할 보기 목록(라벨·아이콘·순서). 생략하면 목록/카드 두 칸이 기본이다.
1184
+ * gridCard 없이 카드 보기를 넣으면 빈 카드가 나오므로 여기서 걸러낸다.
1185
+ */
1186
+ views?: SearchViewItem[];
1187
+ /** 페이지당 건수 선택지. 생략 시 [10, 20, 50, 100] */
1188
+ pageSizes?: number[];
1189
+ disableGrid?: boolean;
1190
+ disableList?: boolean;
1191
+ searchPlaceholder?: string;
1192
+ /** 행 클릭 시 실행할 액션 — 이벤트 값으로 행 객체 전체가 전달된다 */
1193
+ rowClick?: SduiAction | SduiAction[];
1194
+ /** 렌더러의 slots.headerActions가 이 프롭으로 들어온다 */
1195
+ headerActions?: React__default.ReactNode;
1196
+ /** 가상 스크롤 — true면 기본 임계값 150행, 숫자면 그 값이 임계값 (JSON 직렬화 가능) */
1197
+ virtual?: boolean | number;
1102
1198
  }
1103
- declare function EmptyState({ icon, title, children, actions, tight, className, ...rest }: EmptyStateProps): React__default.JSX.Element;
1104
- interface SpinnerProps extends React__default.HTMLAttributes<HTMLSpanElement> {
1105
- size?: 'sm' | 'md' | 'lg';
1106
- /** 네이비/뷰어 표면 위 — 밝은 테두리로 뒤집는다. */
1107
- inverse?: boolean;
1108
- label?: string;
1199
+ interface SduiSearchResultsProps extends SduiSearchCommonProps {
1200
+ data: SearchRow[];
1109
1201
  }
1110
- declare function Spinner({ size, inverse, label, className, ...rest }: SpinnerProps): React__default.JSX.Element;
1111
- interface ProgressBarProps extends React__default.HTMLAttributes<HTMLDivElement> {
1112
- /** 0~`max` */
1113
- value: number;
1114
- /** 기본 100. 용량 게이지처럼 분모가 100이 아닌 자리에 쓴다 */
1115
- max?: number;
1116
- /** danger 임계 강조 등 */
1117
- tone?: 'brand' | 'success' | 'warning' | 'danger' | 'info';
1118
- /** sm 4 · md 6(기본) · lg 8 px */
1119
- size?: 'sm' | 'md' | 'lg';
1120
- /** 사선 해치 애니메이션 — 진행 중인 작업에만 */
1121
- striped?: boolean;
1122
- label?: React__default.ReactNode;
1202
+ /** JSON 컬럼 정의 → SearchResults 컬럼. 셀 해석은 cellRenderer 한 벌이다. */
1203
+ declare function mapColumns(columns: SduiSearchColumn[], dispatch: SduiDispatch): SearchColumn<SearchRow>[];
1204
+ /** 카드 행 표시 조건 판정 — 조건이 아예 없으면 항상 표시한다 */
1205
+ declare function matchesCondition(condition: SduiRowCondition | undefined, row: SearchRow): boolean;
1206
+ /** 카드 행 목록 → GridCardRow. 값 조회는 컬럼 셀과 같은 규칙(평면 키 우선, 점 경로)이다. */
1207
+ declare function mapCardRows(rows: SduiGridCardRow[] | undefined, dispatch: SduiDispatch): GridCardRow<SearchRow>[] | undefined;
1208
+ /** 선언적 gridCard 스펙 → GridCardConfig. */
1209
+ declare function mapGridCard(gridCard: SduiGridCard | undefined, dispatch: SduiDispatch): GridCardConfig<SearchRow> | undefined;
1210
+ /**
1211
+ * gridCard 없이 카드 보기를 켜면 빈 카드만 나온다 — `disableGrid ?? !gridCard`와 같은
1212
+ * 제약을 views에도 건다. 전부 걸러지면 undefined를 돌려 기본 규칙에 맡긴다.
1213
+ */
1214
+ declare function mapViews(views: SearchViewItem[] | undefined, gridCard: SduiGridCard | undefined, disableGrid: boolean | undefined, disableList: boolean | undefined): SearchViewItem[] | undefined;
1215
+ /** 선언적 액션 목록 → 콜백 액션 목록. 이벤트 값으로 선택된 행 id 배열을 넘긴다 */
1216
+ declare function mapActions(actions: SduiSearchAction[] | undefined, dispatch: SduiDispatch): SearchAction<SearchRow>[];
1217
+ interface RemoteSearchResultsProps extends SduiSearchCommonProps {
1123
1218
  /**
1124
- * 현재 값을 퍼센트로 적는다.
1125
- *
1126
- * 막대만으로는 82%와 91%를 구분할 수 없어서, 여러 개를 세로로 쌓아 비교하는
1127
- * 자리(공정별 진척 목록 등)에서 필요하다. 라벨이 있으면 라벨 줄 오른쪽 끝에,
1128
- * 라벨이 없으면 막대 오른쪽 옆(.wp-meter-row)에 붙는다.
1219
+ * 목록 endpoint — page/limit/search/필드키 쿼리를 붙여 조회하고
1220
+ * `{ data: Row[]; total: number }`를 기대한다. 조회 자체는 어댑터(SearchFetcher)가 한다.
1129
1221
  */
1130
- showValue?: boolean;
1131
- }
1132
- declare function ProgressBar({ value, max, tone, size, striped, showValue, label, className, ...rest }: ProgressBarProps): React__default.JSX.Element;
1133
- interface PaginationProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, 'onChange'> {
1134
- page: number;
1135
- pageCount: number;
1136
- total?: number;
1222
+ endpoint: string;
1223
+ /** limit 파라미터의 초기값 — 생략하면 pageSizes 첫 값(그것도 없으면 10) */
1137
1224
  pageSize?: number;
1138
- onChange?: (page: number) => void;
1139
- }
1140
- declare function Pagination({ page, pageCount, total, pageSize, onChange, className, ...rest }: PaginationProps): React__default.JSX.Element;
1141
- interface DialogProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, 'title'> {
1142
- title?: React__default.ReactNode;
1143
- subtitle?: React__default.ReactNode;
1144
- /** 타이틀 좌측 마크 안의 아이콘 이름 */
1145
- icon?: string;
1146
- tone?: 'brand' | 'danger';
1147
- /** sm 380 · md 480(기본) · lg 680 · xl 880 px */
1148
- size?: 'sm' | 'md' | 'lg' | 'xl';
1149
- /** 우측 정렬 액션 행 — 보조 버튼 다음 주 버튼 */
1150
- footer?: React__default.ReactNode;
1151
- onClose?: () => void;
1152
- children?: React__default.ReactNode;
1153
- }
1154
- /** 모달 본체. 오버레이가 가장 가까운 포지셔닝 조상을 채운다 — SDUI에서는 SduiDialog 래퍼가 뷰포트에 고정한다. */
1155
- declare function Dialog({ title, subtitle, icon, tone, size, footer, onClose, children, className, ...rest }: DialogProps): React__default.JSX.Element;
1156
- /** 배타 뷰 전환 스위치. 항목 2~4개까지, 그보다 많으면 Select다. */
1157
- interface SegmentedItem {
1158
- value: string;
1159
- label?: string;
1160
- /** 앞에 붙일 아이콘 이름 */
1161
- icon?: string;
1162
- }
1163
- interface SegmentedControlProps extends Omit<React__default.HTMLAttributes<HTMLDivElement>, 'onChange'> {
1164
- items: SegmentedItem[];
1165
- value: string;
1166
- onChange?: (value: string) => void;
1167
- /** md(24px 항목, 기본) · lg(30px) */
1168
- size?: 'md' | 'lg';
1169
- className?: string;
1225
+ /**
1226
+ * 검색/필터/페이지 파라미터를 바깥에서 소유할 때(controlled) — 딥링크·새로고침 복원처럼
1227
+ * URL과 동기화하려는 호스트가 라우터 값을 그대로 꽂는다. JSON 스펙에서는 표현할 수 없는
1228
+ * 값이라 스펙 계약 밖이고, 코드에서 직접 쓰는 자리다. 생략하면 종전처럼 내부 상태다.
1229
+ */
1230
+ searchParams?: URLSearchParams;
1231
+ /** 파라미터 변경 통지 — searchParams 없이 쓰면 내부 상태를 유지한 채 관찰만 한다 */
1232
+ onParamsChange?: (next: URLSearchParams) => void;
1170
1233
  }
1234
+ /** 바깥에서 온 `style`·`className`을 뿌리에 얹어 내보낸다. 까닭은 {@link ../boxed.tsx}에 있다. */
1235
+ declare const SduiSearchResults: React__default.FC<SduiSearchResultsProps & SduiBoxProps>;
1236
+ /** 바깥에서 온 `style`·`className`을 뿌리에 얹어 내보낸다. 까닭은 {@link ../boxed.tsx}에 있다. */
1237
+ declare const RemoteSearchResults: React__default.FC<RemoteSearchResultsProps & SduiBoxProps>;
1238
+
1171
1239
  /**
1172
- * 목록/카드, 2D/3D, 일/주/월처럼 하나만 고르는 뷰 전환.
1240
+ * 목록 표 — SearchResults 전용. 내장 `Table`은 정렬 헤더·선택 표시·가상 스크롤을
1241
+ * 다루지 않으므로, 같은 wp-table 마크업/클래스를 유지한 채 그 세 가지를 더한 변형이다.
1173
1242
  *
1174
- * `search/SearchResults.tsx`가 "라이브러리에 SegmentedControl이 없다"는 이유로
1175
- * 안에 `ViewToggle` 사본을 들고 있었다. 이 컴포넌트가 그 사본의 원본이다.
1243
+ * 가상 스크롤은 **의존성 없이** 직접 구현한다(core는 무거운 외부 의존성을 갖지 않는다).
1244
+ * ERP는 @tanstack/react-virtual을 썼지만, 여기서 필요한 건 "고정 행 높이 목록의 창"뿐이라
1245
+ * scrollTop → 인덱스 범위 계산으로 충분하다. 렌더 범위 밖은 상/하단 스페이서 행으로 대체한다
1246
+ * (<table> 마크업에서 transform 대신 안전하게 동작하는 방식).
1176
1247
  */
1177
- declare function SegmentedControl({ items, value, onChange, size, className, ...rest }: SegmentedControlProps): React__default.JSX.Element;
1178
- /** 사람·팀·자산의 신원 표식. */
1179
- interface AvatarProps extends React__default.HTMLAttributes<HTMLSpanElement> {
1180
- /** 이니셜과 title에 쓰인다 */
1181
- name?: string;
1182
- /** 이미지 URL — 있으면 이니셜을 대신한다 */
1183
- src?: string;
1184
- /** hex(기본, 심볼 실루엣) · round(사람) · square(자산) */
1185
- shape?: 'hex' | 'round' | 'square';
1186
- /** xs 20 · sm 24 · md 28(기본) · lg 36 · xl 48 px */
1187
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
1188
- /** brand 네이비(기본) · muted 회색 · accent 레드 */
1189
- tone?: 'brand' | 'muted' | 'accent';
1190
- className?: string;
1191
- }
1192
- /** 사람/자산 표식. 기본은 육각형 — 심볼의 실루엣이다. */
1193
- declare function Avatar({ name, src, shape, size, tone, className, ...rest }: AvatarProps): React__default.JSX.Element;
1194
- /** 프로젝트에서 개별 객체까지의 위치. 4단계를 넘으면 가운데를 접는다. */
1195
- interface BreadcrumbItem {
1196
- label: React__default.ReactNode;
1197
- href?: string;
1198
- icon?: string;
1199
- onClick?: () => void;
1200
- }
1201
- interface BreadcrumbProps extends React__default.HTMLAttributes<HTMLElement> {
1202
- items: BreadcrumbItem[];
1203
- className?: string;
1204
- }
1205
- /** 계보 표시. 마지막 항목은 현재 객체이므로 링크가 아니다. */
1206
- declare function Breadcrumb({ items, className, ...rest }: BreadcrumbProps): React__default.JSX.Element;
1207
- /** 후보가 전부 보이는 배타 선택. 5개 이상이면 Select다. */
1208
- interface RadioProps extends React__default.InputHTMLAttributes<HTMLInputElement> {
1209
- label?: React__default.ReactNode;
1210
- description?: React__default.ReactNode;
1211
- disabled?: boolean;
1212
- }
1213
- /** 2~4개 후보를 다 보여 주는 라디오. Checkbox와 마크업을 공유한다(.wp-check). */
1214
- declare function Radio({ label, description, disabled, className, ...rest }: RadioProps): React__default.JSX.Element;
1215
- /** 주 내비게이션 목록. 활성 항목에 빨간 에지 마커가 붙는다. */
1216
- interface SidebarNavItem {
1217
- value: string;
1218
- label: React__default.ReactNode;
1219
- icon?: string;
1220
- meta?: React__default.ReactNode;
1221
- href?: string;
1222
- }
1223
- interface SidebarNavSection {
1224
- label?: React__default.ReactNode;
1225
- items: SidebarNavItem[];
1248
+
1249
+ interface ResultTableColumn<T> {
1250
+ key: string;
1251
+ header: React__default.ReactNode;
1252
+ sortable?: boolean;
1253
+ /** right → 우측 정렬 + tabular numerals (wp-table__num) */
1254
+ align?: 'left' | 'right';
1255
+ /** 고정폭 글꼴 셀 (wp-table__mono) */
1256
+ mono?: boolean;
1257
+ /** 강조 셀 (wp-table__strong) */
1258
+ strong?: boolean;
1259
+ /** CSS width — "180px", "22%" */
1260
+ width?: string;
1261
+ render?: (row: T, index: number) => React__default.ReactNode;
1226
1262
  }
1227
- interface SidebarNavProps extends Omit<React__default.HTMLAttributes<HTMLElement>, 'onSelect'> {
1228
- sections: SidebarNavSection[];
1229
- value?: string;
1230
- onSelect?: (value: string) => void;
1231
- /** 아이콘만 남기는 56px 레일 */
1232
- collapsed?: boolean;
1233
- /** 흰 표면(설정 하위 내비) 위에 쓰는 밝은 변형 */
1234
- light?: boolean;
1235
- className?: string;
1263
+ type SortDir = 'asc' | 'desc';
1264
+ interface ResultTableProps<T extends {
1265
+ id: string;
1266
+ }> {
1267
+ columns: ResultTableColumn<T>[];
1268
+ rows: T[];
1269
+ sort?: {
1270
+ key: string;
1271
+ dir: SortDir;
1272
+ };
1273
+ onSort?: (key: string) => void;
1274
+ onRowClick?: (row: T) => void;
1275
+ selectedIds?: string[];
1276
+ /** rows가 비었을 때 표 대신 렌더 */
1277
+ empty?: React__default.ReactNode;
1278
+ /** true면 스크롤 창 안에서 보이는 행만 렌더한다 */
1279
+ virtual?: boolean;
1280
+ /** 가상 스크롤 컨테이너 높이(px). 기본 560 */
1281
+ height?: number;
1282
+ /** 가상 스크롤 행 높이(px) 초기 추정치. 첫 렌더 후 실측값으로 대체된다 — 기본 41(wp-table 기본 행) */
1283
+ rowHeight?: number;
1236
1284
  }
1237
- /** 네이비 사이드바의 주 내비게이션. 섹션 + 항목. */
1238
- declare function SidebarNav({ sections, value, onSelect, collapsed, light, className, ...rest }: SidebarNavProps): React__default.JSX.Element;
1239
- /** 아이콘 전용 컨트롤·잘린 값에 붙이는 hover/focus 힌트. */
1240
- interface TooltipProps extends React__default.HTMLAttributes<HTMLSpanElement> {
1241
- label: React__default.ReactNode;
1242
- /** 단축키 칩, 예: "⌘K" */
1243
- shortcut?: string;
1244
- placement?: 'top' | 'bottom' | 'left' | 'right';
1245
- /** 여러 줄 허용(최대 240px) */
1246
- wrap?: boolean;
1285
+ declare function ResultTable<T extends {
1286
+ id: string;
1287
+ }>({ columns, rows, sort, onSort, onRowClick, selectedIds, empty, virtual, height, rowHeight, }: ResultTableProps<T>): React__default.JSX.Element;
1288
+
1289
+ /**
1290
+ * 권한 게이트 — 통과하면 children, 실패하면 fallback 슬롯(없으면 아무 것도)을 렌더한다.
1291
+ * 판정 자체는 하지 않는다. 호스트가 주입한 콜백(permission.tsx)에 질의만 위임한다.
1292
+ *
1293
+ * 스펙 예 (ERP 85개 페이지 중 56개가 쓰는 형태 그대로):
1294
+ * { "component": "Can",
1295
+ * "props": { "entity": "asset", "action": "create" },
1296
+ * "children": [ { "component": "Button", ... } ] }
1297
+ */
1298
+
1299
+ interface CanProps {
1300
+ /** 대상 엔티티 식별자 */
1301
+ entity: string;
1302
+ /** 수행 동작 — 생략 시 'read'(ERP Can과 같은 기본값) */
1303
+ action?: string;
1304
+ /** 최소 적용 범위 — 생략 시 호스트 판정기의 기본값을 따른다 */
1305
+ scope?: string;
1306
+ /** 차단 시 대신 보여줄 내용 (JSON에서는 slots.fallback) */
1307
+ fallback?: React__default.ReactNode;
1247
1308
  children?: React__default.ReactNode;
1248
- className?: string;
1249
1309
  }
1250
- /** hover 힌트. 트리거를 감싸며 위치는 CSS가 잡는다 — 포털을 쓰지 않는다. */
1251
- declare function Tooltip({ label, shortcut, placement, wrap, children, className, ...rest }: TooltipProps): React__default.JSX.Element;
1310
+ declare function Can({ entity, action, scope, fallback, children }: CanProps): React__default.JSX.Element;
1252
1311
 
1253
1312
  interface GridProps extends SduiContainerLayoutProps {
1254
1313
  /** wp-grid--N 프리셋 (1~6) */
@@ -1423,6 +1482,7 @@ interface SduiFormProps {
1423
1482
  /**
1424
1483
  * URL 쿼리에서 프리필할 필드 — { 필드명: 쿼리파라미터명 }.
1425
1484
  * initial보다 우선하며, 파라미터가 없거나 이미 값이 있으면 건드리지 않는다.
1485
+ * 해시 라우터(`/#/page?x=1`)의 해시 안 쿼리도 읽는다 — search와 겹치면 search가 이긴다.
1426
1486
  */
1427
1487
  initialFromUrl?: Record<string, string>;
1428
1488
  /** 필드별 검증 규칙 */
@@ -1647,6 +1707,24 @@ declare const defaultRegistry: {
1647
1707
  * 빈칸이지 컴포넌트가 필요하다는 뜻이 아니다.
1648
1708
  */
1649
1709
  declare function createRegistry(extensions: SduiRegistry): SduiRegistry;
1710
+ /**
1711
+ * 기본 프롭을 얹은 컴포넌트 — 레지스트리 등록 한 줄용.
1712
+ *
1713
+ * 도메인 라벨·엔드포인트는 소비자가 소유하는 것이 맞다(라이브러리는 어느 회사의
1714
+ * 직제도, API 오리진도 모른다). 그 올바른 구조의 부산물이 "프롭 기본값만 얹는 래퍼
1715
+ * 함수 + eslint-disable any" 블록의 반복이라, 패턴 자체를 여기서 지원한다:
1716
+ *
1717
+ * ```tsx
1718
+ * const registry = createRegistry({
1719
+ * ApprovalStepper: withProps(ApprovalStepper, { typeLabels: MY_LABELS }),
1720
+ * });
1721
+ * ```
1722
+ *
1723
+ * 스프레드 순서는 기본값이 먼저다 — **스펙이 준 프롭이 이긴다.** 기본값은 "스펙이
1724
+ * 말하지 않았을 때"의 값이지 강제값이 아니다(강제하고 싶다면 그것은 기본값이 아니라
1725
+ * 소비자 구현이므로 직접 래핑할 것).
1726
+ */
1727
+ declare function withProps<P extends object, K extends keyof P>(Component: ComponentType<P>, defaults: Pick<P, K>): ComponentType<Omit<P, K> & Partial<Pick<P, K>>>;
1650
1728
 
1651
1729
  /**
1652
1730
  * 권한 판정 주입 지점.
@@ -1739,12 +1817,83 @@ interface SduiRendererProps {
1739
1817
  */
1740
1818
  declare function SduiRenderer({ page, registry, adapter, syncUrlState, initialState, can, onCustomAction, customValidators, }: SduiRendererProps): React__default.JSX.Element;
1741
1819
 
1820
+ /**
1821
+ * 패턴 스펙 id 해석 — `{name}` 세그먼트 문법의 클라이언트 절반.
1822
+ *
1823
+ * sdui-server 는 `{name}` 을 **패턴 세그먼트**로 정식 지원한다(crates/sdui-server/
1824
+ * src/spec.rs 의 `valid_page_id`): 동적 상세 라우트(`asset/asset/{id}`)의 자리다.
1825
+ * 저장소는 패턴을 그대로 한 행으로 저장할 뿐이고, **실제 경로를 어느 패턴에 맞춰
1826
+ * 볼지는 클라이언트가 정한다** — 그 후보 생성과 우선순위가 프로토콜의 나머지 절반이다.
1827
+ *
1828
+ * 이 절반을 소비 프로젝트마다 재발명하면 같은 스펙 DB 에서 다른 화면이 열리고,
1829
+ * 빠뜨린 조합의 대가는 조용한 NotFound 다(소비 프로젝트 실측: "한 칸 건너" 후보가
1830
+ * 없던 동안 `asset/asset/{id}/unit/{sub}` 류 유닛 상세가 전부 NotFound 로 떨어졌다).
1831
+ * 그래서 순서의 정본을 여기에 두고, 서버 문법과는 테스트로 짝을 맞춘다.
1832
+ *
1833
+ * ## 패턴 이름은 `{id}`·`{sub}` 관례로 고정이다
1834
+ *
1835
+ * 서버 문법 자체는 `{unitId}` 같은 임의 이름을 허용한다. 하지만 실제 경로만 보고는
1836
+ * 어느 세그먼트가 패턴 자리였는지 알 수 없으므로, 이름까지 자유로우면 후보가 무한해진다.
1837
+ * 그래서 해석기는 첫 파라미터를 `{id}`, 둘째를 `{sub}` 로 **이름 붙여 찾는다** —
1838
+ * 패턴 스펙을 저장할 때 이 이름을 써야 이 해석기가 닿는다. 매칭된 값은 같은 이름으로
1839
+ * `params` 에 담기므로, 호스트는 그대로 `route.<이름>` 초기 상태로 심으면 된다.
1840
+ */
1841
+ /** 후보 스펙 id 하나 — 패턴 세그먼트가 삼킨 실제 값이 `params` 에 담긴다. */
1842
+ interface SpecIdCandidate {
1843
+ specId: string;
1844
+ params: Record<string, string>;
1845
+ }
1846
+ /** [`resolvePatternSpec`] 의 결과 — 정확 일치면 `specId` 는 요청 id, `params` 는 빈 객체다. */
1847
+ interface ResolvedPatternSpec<T> {
1848
+ spec: T;
1849
+ specId: string;
1850
+ params: Record<string, string>;
1851
+ }
1852
+ /**
1853
+ * 스펙 id 문법 검사 — 서버 `valid_page_id`(spec.rs)와 같은 판정이어야 한다.
1854
+ *
1855
+ * 세그먼트는 `[A-Za-z0-9_-]+`(리터럴) 또는 `{[A-Za-z0-9_]+}`(패턴)다. 중괄호는
1856
+ * 양끝을 감싼 형태만 — 반쪽 중괄호·빈 이름·이름 밖 문자는 구문 오류다. 클라이언트가
1857
+ * 이 검사를 자기 정규식으로 들면 서버와 어긋난 채 조용히 굴러가므로(서버는 400,
1858
+ * 클라이언트는 통과) 정본을 여기서 내보낸다.
1859
+ */
1860
+ declare function isValidSpecId(id: string): boolean;
1861
+ /**
1862
+ * 경로 id → 패턴 스펙 후보 목록. **정확 일치는 호출부의 첫 시도**이고, 여기는 그
1863
+ * 다음 순서만 만든다 — 구체적인 것부터: 마지막만 패턴 > 안쪽만 패턴 > 둘 다 패턴 >
1864
+ * 한 칸 건너 패턴.
1865
+ *
1866
+ * 후보는 뒤에서 세 세그먼트 안의 패턴 조합이다(`a/b/{id}` · `a/{id}/c` ·
1867
+ * `a/{id}/{sub}` · `a/{id}/c/{sub}`) — 동적 상세 라우트가 전부 이 안에 든다.
1868
+ * 마지막 조합은 파라미터 둘 사이에 리터럴 한 칸이 낀 `asset/asset/{id}/unit/{sub}`
1869
+ * 류의 자리다.
1870
+ *
1871
+ * 한 세그먼트짜리 id 는 후보가 없다 — 최상위 `{id}` 패턴은 존재하지 않는 모든
1872
+ * 경로를 삼키는 만능 폴백이 되므로 만들지 않는다.
1873
+ *
1874
+ * 요청 id 와 같은 후보·중복 후보는 걸러진다: id 에 이미 패턴 세그먼트가 있으면
1875
+ * (편집기 딥링크가 패턴 스펙을 직접 여는 경우) 치환이 제자리를 맴돌아 이미 실패한
1876
+ * 정확 일치를 다시 조회하게 되기 때문이다.
1877
+ */
1878
+ declare function patternSpecCandidates(id: string): SpecIdCandidate[];
1879
+ /**
1880
+ * 정확 일치 → 패턴 폴백 순서로 로더를 돌리는 해석 루프.
1881
+ *
1882
+ * 로더만 소비자의 것이다(캐시·네트워크·인증은 앱마다 다르다). 순서와 `params` 의
1883
+ * 모양은 프로토콜이므로 루프까지 여기서 내보낸다 — 소비자가 루프를 다시 짜면
1884
+ * "정확 일치의 params 는 빈 객체" 같은 세부가 갈라진다. 로더의 null/undefined 는
1885
+ * "그런 스펙 없음"으로 읽고 다음 후보로 넘어간다(오류는 로더가 던지는 대로 흐른다).
1886
+ */
1887
+ declare function resolvePatternSpec<T>(id: string, load: (specId: string) => Promise<T | null | undefined>): Promise<ResolvedPatternSpec<T> | null>;
1888
+
1742
1889
  /**
1743
1890
  * 어휘 전체. **렌더러와 편집기 검증기가 같은 이 집합을 본다** — 목록이 두 벌이면
1744
1891
  * 한쪽에만 연산을 더했을 때 "렌더는 되는데 저장이 거부되는" 스펙이 생긴다.
1745
1892
  */
1746
1893
  declare const CALC_OPS: ReadonlySet<string>;
1747
1894
 
1895
+ declare const ICON_NAMES: readonly string[];
1896
+
1748
1897
  /**
1749
1898
  * 선택적 peer 의존성(recharts · ol)을 런타임에 끌어오는 공용 장치.
1750
1899
  *
@@ -1864,6 +2013,12 @@ declare const OverlayContainerContext: React__default.Context<HTMLElement | null
1864
2013
  * - 배경(.wp-overlay) 클릭 시 닫기 — 내용 클릭은 무시한다
1865
2014
  * - Escape는 맨 위 겹에서만 먹는다
1866
2015
  * - 아래 겹은 `inert`로 잠근다 — 탭·클릭이 가려진 창으로 새지 않게
2016
+ *
2017
+ * **배경 클릭 닫기는 마크업 계약이다**: 자식이 `.wp-overlay` 요소(스크림)를 직접
2018
+ * 그려야 동작한다. 이 래퍼가 스크림을 대신 그리지 않는 이유 — Dialog가 이미 자기
2019
+ * 스크림을 갖고 있어 두 겹이 되고, 스크림이 필요 없는 겹(전체 화면 뷰어 등)에는
2020
+ * 억지로 배경이 생긴다. 라이브러리 밖에서 자기 오버레이를 얹을 때도 배경 요소에
2021
+ * `wp-overlay` 클래스를 줘야 배경 클릭 닫기가 걸린다(없으면 Escape로만 닫힌다).
1867
2022
  */
1868
2023
  declare function OverlayLayer({ onClose, children, }: {
1869
2024
  onClose?: () => void;
@@ -1919,4 +2074,4 @@ interface ImageViewerProps {
1919
2074
  */
1920
2075
  declare function ImageViewer({ src, alt, open, onClose }: ImageViewerProps): React__default.ReactPortal | null;
1921
2076
 
1922
- export { Actions, type ActionsProps, Alert, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, CALC_OPS, Can, type CanProps, Card, type CardProps, type CascadingSearchOption, Checkbox, type CheckboxProps, Col, type ColProps, type CustomActionHandler, type CustomValidator, DatePickerProps, DateRangePickerProps, Dialog, type DialogProps, Divider, EmptyState, type EmptyStateProps, Field, type FieldProps, Grid, type GridCardConfig, type GridCardRow, type GridProps, Icon, IconButton, type IconButtonProps, type IconProps, ImageViewer, type ImageViewerProps, Input, type InputProps, type Justify, type KeyValueItem, KeyValueList, type KeyValueListProps, type MeterTone, NodeErrorBoundary, OverlayContainerContext, OverlayLayer, PageHead, type PageHeadProps, Pagination, type PaginationProps, ProgressBar, type ProgressBarProps, Radio, type RadioProps, RemoteSearchResults, type RemoteSearchResultsProps, ResultTable, type ResultTableColumn, type ResultTableProps, Row, type RowProps, SduiAction, type SduiAdapter, type SduiAssetContext, type SduiAssetResolver, type SduiBadgeCell, SduiBoxProps, type SduiCanFn, type SduiCanQuery, type SduiCanSetting, type SduiCell, SduiColumnsScale, SduiContainerLayoutProps, type SduiContainerRoleProps, type SduiDateCell, SduiDialog, type SduiDispatch, SduiForm, type SduiFormConfig, type SduiFormProps, SduiGapScale, type SduiGridCard, type SduiGridCardAction, type SduiGridCardRow, type SduiIconCell, type SduiImageCell, type SduiLinkCell, type SduiNumberCell, SduiPage, SduiPermissionProvider, type SduiProgressCell, SduiPropValue, type SduiRegistry, SduiRenderer, type SduiRendererProps, type SduiRowCondition, SduiRule, type SduiRuntime, type SduiSearchAction, type SduiSearchColumn, SduiSearchDataProvider, SduiSearchResults, type SduiSearchResultsProps, type SduiTagCell, SduiTextAlignScale, type SduiTextCell, type SduiUseSpec, SduiWeightScale, type SearchAction, type SearchColumn, type SearchFetcher, type SearchField, type SearchFilter, type SearchOption, type SearchPage, SearchResults, type SearchResultsProps, type SearchRow, SearchToolbar, type SearchValues, type SearchViewItem, Section, type SectionProps, SegmentedControl, type SegmentedControlProps, type SegmentedItem, Select, type SelectOption, type SelectOptionRecord, type SelectProps, Show, type ShowProps, SidebarNav, type SidebarNavItem, type SidebarNavProps, type SidebarNavSection, type SortDir, SortHandle, type SortHandleProps, SortableList, type SortableListProps, Spacer, type SpacerProps, type SpecIssue, Spinner, type SpinnerProps, StatCard, type StatCardProps, Switch, type SwitchProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Tag, type TagProps, Text, type TextPreset, type TextProps, Textarea, type TextareaProps, TimePickerProps, Toast, type ToastProps, Tooltip, type TooltipProps, boxed, cascadingValuePaths, cellRenderer, cleanSearchValues, createRegistry, defaultAdapter, defaultRegistry, getOverlayRoot, imageRenderer, includesLooseSearch, joinMultiValue, loadOptionalModule, normalizePageSizes, normalizeSearchText, searchFieldKeys, searchValuesFromParams, sortableDropIndex, sortableMove, splitMultiValue, useOverlayLayer, useRemoteSearchPage, useSduiAssetResolver, useSduiCan, useSduiDispatch, useSduiRuntime, useSduiSandbox, useSearchFetcher, validateSpec };
2077
+ export { Actions, type ActionsProps, Alert, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, CALC_OPS, Can, type CanProps, Card, type CardProps, type CascadingSearchOption, Checkbox, type CheckboxProps, Col, type ColProps, type CustomActionHandler, type CustomValidator, DatePickerProps, DateRangePickerProps, Dialog, type DialogProps, Divider, EmptyState, type EmptyStateProps, Field, type FieldProps, Grid, type GridCardConfig, type GridCardRow, type GridProps, ICON_NAMES, Icon, IconButton, type IconButtonProps, type IconProps, ImageViewer, type ImageViewerProps, Input, type InputProps, type Justify, type KeyValueItem, KeyValueList, type KeyValueListProps, type MeterTone, NodeErrorBoundary, OverlayContainerContext, OverlayLayer, PageHead, type PageHeadProps, Pagination, type PaginationProps, ProgressBar, type ProgressBarProps, Radio, type RadioProps, type RemotePageState, RemoteSearchResults, type RemoteSearchResultsProps, type ResolvedPatternSpec, ResultTable, type ResultTableColumn, type ResultTableProps, Row, type RowProps, SduiAction, type SduiAdapter, type SduiAssetContext, type SduiAssetResolver, type SduiBadgeCell, SduiBoxProps, type SduiCanFn, type SduiCanQuery, type SduiCanSetting, type SduiCell, SduiColumnsScale, SduiContainerLayoutProps, type SduiContainerRoleProps, type SduiDateCell, SduiDialog, type SduiDispatch, SduiForm, type SduiFormConfig, type SduiFormProps, SduiGapScale, type SduiGridCard, type SduiGridCardAction, type SduiGridCardRow, type SduiIconCell, type SduiImageCell, type SduiLinkCell, type SduiNumberCell, SduiPage, SduiPermissionProvider, type SduiProgressCell, SduiPropValue, type SduiRegistry, SduiRenderer, type SduiRendererProps, type SduiRowCondition, SduiRule, type SduiRuntime, type SduiSearchAction, type SduiSearchColumn, SduiSearchDataProvider, SduiSearchInvalidationProvider, SduiSearchResults, type SduiSearchResultsProps, type SduiTagCell, SduiTextAlignScale, type SduiTextCell, type SduiUseSpec, SduiWeightScale, type SearchAction, type SearchColumn, type SearchFetcher, type SearchField, type SearchFilter, type SearchInvalidator, type SearchOption, type SearchPage, SearchResults, type SearchResultsProps, type SearchRow, SearchToolbar, type SearchValues, type SearchViewItem, Section, type SectionProps, SegmentedControl, type SegmentedControlProps, type SegmentedItem, Select, type SelectOption, type SelectOptionRecord, type SelectProps, Show, type ShowProps, SidebarNav, type SidebarNavItem, type SidebarNavProps, type SidebarNavSection, type SortDir, SortHandle, type SortHandleProps, SortableList, type SortableListProps, Spacer, type SpacerProps, type SpecIdCandidate, type SpecIssue, Spinner, type SpinnerProps, StatCard, type StatCardProps, Switch, type SwitchProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Tag, type TagProps, Text, type TextPreset, type TextProps, Textarea, type TextareaProps, TimePickerProps, Toast, type ToastProps, Tooltip, type TooltipProps, boxed, cascadingValuePaths, cellRenderer, cleanSearchValues, createRegistry, createSearchInvalidator, defaultAdapter, defaultRegistry, getOverlayRoot, imageRenderer, includesLooseSearch, isValidSpecId, joinMultiValue, loadOptionalModule, mapActions, mapCardRows, mapColumns, mapGridCard, mapViews, matchesCondition, normalizePageSizes, normalizeSearchText, patternSpecCandidates, resolvePatternSpec, searchFieldKeys, searchValuesFromParams, sortableDropIndex, sortableMove, splitMultiValue, useOverlayLayer, useRemoteSearchPage, useSduiAssetResolver, useSduiCan, useSduiDispatch, useSduiRuntime, useSduiSandbox, useSearchFetcher, validateSpec, withProps };