MOCA React API

업데이트 2026-08-22 · 컴포넌트 10 · 모듈 3 · 앱 규약 1 가이드 →

컴포넌트

화면(.jsx)에 JSX 로 배치하고 props 로 설정한다. 명령형 API 가 있는 것은 ref 로 얻어 호출한다.

MocaReactCalendar

import { MocaReactCalendar } from '@teammoca/react-input-calendar'

붙박이 달력 — 화면에 늘 펼쳐진 날짜 선택 달력 (MOCA calendar 이식, 데모 TPL044 대응). InputCalendar 가 "입력칸 + 눌러서 뜨는 달력"이라면 이쪽은 팝업이 아니다: 고른 날짜를 달력 위에서 눈으로 확인하는 것이 목적(예약일·근태·마감일 화면). 제목을 누르면 한 단계 위 뷰로 올라가고(일→월→연) 위 뷰에서 칸을 고르면 내려온다 — 먼 날짜로 빠르게 이동하는 통로. 공휴일·비영업일은 컴포넌트가 모르고 화면이 dayInfo 로 알려준다

사용 예

import { MocaReactCalendar, mdate } from '@teammoca/react-input-calendar'

// 공휴일·휴무는 화면이 조회해 넘긴다(달을 옮기면 onViewChange 에서 다시 조회)
const [dayInfo, setDayInfo] = useState({})

<MocaReactCalendar ref={cal} dateType="yyyyMMdd"
  dayInfo={dayInfo}        // { '20260815': { holi: '광복절', off: true, badge: '휴' } }
  weekendOff legend
  onSelect={(v) => console.log(v, cal.current.getDayInfo(v))}
  onViewChange={(ym, view) => { if (view === 'day') loadHoli(ym) }} />

// 예약 가능일처럼 구간이 런타임에 정해지는 화면
cal.current.setDateRange(mdate.getToday(), mdate.addDay(mdate.getToday(), 30))

Props

dateType
'yyyyMMdd'(기본·일자 격자) | 'yyyyMM'(연월 — 12개월) | 'yyyy'(연 — 한 연대 10년). 이 타입이 기본 뷰이면서 값 확정 단위다(그 뷰에서 칸을 고르면 확정, 위 뷰에서는 내려간다)
value
제어 모드 값(숫자문자열) — onChange 와 함께 쓴다
defaultValue
비제어 초기값 — 숫자문자열 또는 상대일 표현('오늘' · '7일전' …)
dayInfo
업무 표시 데이터 (MOCA setData({dayInfo}) 규약) — { yyyyMMdd: { holi, off, badge, class } }. holi=공휴일명(날짜가 빨개지고 칸에 이름 표시) · off=비영업일(빗금) · badge=칸 우측 위 표식 · class=그 칸에 덧붙일 클래스. 컴포넌트는 공휴일을 모른다 — 화면이 조회한 것만 그린다
dayInfo={{ '20260815': { holi: '광복절', off: true } }}
weekendOff
true 면 토·일을 비영업일(빗금)로 표시한다. dayInfo 의 off:false 가 이보다 우선하므로 "쉬는 토요일만 예외" 같은 지정이 가능하다(MOCA 동일)
legend
true 면 달력 아래 범례를 표시한다 — 오늘·선택은 항상, 공휴일·비영업일은 지금 달력에 실제로 있을 때만 설명한다(없는 규칙을 찾게 만들지 않으려는 MOCA 규약). 일자 뷰에서만 나온다
startYm
초기 표시 기준 연월(yyyyMM) — 미지정 시 값이 가리키는 달, 값도 없으면 이번 달
header
false 면 내부 헤더(이전·제목·오늘·다음)를 숨긴다 — 화면이 이동 UI 를 직접 만들 때(MOCA data-m-header)
todayButton
false 면 헤더의 [오늘] 버튼만 숨긴다(기본 표시). [오늘]은 보기를 옮기는 것이 아니라 오늘을 고른다 — 달력 3종이 같은 동작이다(MOCA 는 붙박이만 이동 전용이었는데 일관성을 택해 다르게 갔다). 읽기전용이거나 연·월 목록 뷰에서는 종전대로 이동만 한다
dateMin
선택가능 하한 — 범위 밖 칸·이동버튼이 잠긴다. 런타임 변경은 setDateRange
dateMax
선택가능 상한
readOnly
true 면 선택만 잠긴다 — 달 이동·둘러보기는 그대로(MOCA 동일)

이벤트 (콜백 props)

onChange
(value) => void — 값이 바뀔 때(제어 모드 필수)
onSelect
(value, view) => void — 값이 확정될 때 (MOCA data-m-onselect). setValue 는 발화하지 않는다(초기 바인딩·취소 원복용)
onSelect={(v) => say(v)}
onViewChange
(ym, view) => void — 표시 기준이 바뀔 때(이전/다음·오늘·뷰 전환). **공휴일 재조회를 여기에 건다**. 초기화 시엔 발화하지 않으므로 첫 조회는 화면이 getViewYm() 으로 직접 한다(MOCA 와 같은 규약)
onViewChange={(ym, view) => { if (view === "day") loadHoli(ym) }}

메서드 (ref API)

getValue()
선택값(숫자문자열 — 타입 자릿수, 선택 없으면 "")
setValue(v)
선택값 설정 + 그 값이 보이는 달로 이동(이벤트 미발화)
cal.current.setValue("20260815")
clear()
선택 해제(이벤트 미발화)
setData({dayInfo})
업무 표시 데이터를 명령형으로 넣는다 — dayInfo prop 대신 ref 로 넘기고 싶을 때(MOCA setData 대응). 넘긴 즉시 다시 그린다
getDayInfo(ymd)
그 날의 표시 데이터 반환(없으면 빈 객체) — 비영업일 판정 같은 업무 로직은 화면 몫이다
if (cal.current.getDayInfo(v).off) alert("휴무일입니다")
setDateRange(min, max)
선택가능 범위를 런타임에 지정 — 경계가 다른 값에서 오는 화면용(예약 가능일이 "오늘~30일", 입고일 하한이 발주일인 경우). 고정 구간이면 props 로 충분하다
cal.current.setDateRange(today, mdate.addDay(today, 30))
getDateRange()
지금 적용 중인 범위 { min, max } — props 와 setDateRange 가 합쳐진 최종값
getViewYm()
지금 보고 있는 기준 연월(yyyyMM) — 공휴일 조회 파라미터. 첫 조회에 쓴다
setViewYm(ym)
표시 기준 이동(선택값은 그대로). 기준이 바뀌면 onViewChange 발화
today()
오늘이 속한 달로 이동 — 선택값은 안 바뀐다(이동 전용). 헤더의 [오늘] 버튼과 다르다: 버튼은 오늘을 고르고, 이 메서드는 화면이 명시적으로 부르는 것이라 값을 건드리지 않는다
next()
다음으로 이동(단위는 현재 뷰 — 일=1개월·월=1년·연=10년)
prev()
이전으로 이동
setReadOnly(bool)
선택 잠금 토글(달 이동은 그대로)

MocaReactEchart

import { MocaReactEchart } from '@teammoca/react-echart'

Apache ECharts 를 감싼 차트 컴포넌트 (MOCA plugins/echart.js 이식). option 문법은 ECharts 제품 API 그대로 — 래퍼는 내용을 해석하지 않고 인스턴스 관리(생성·리사이즈·해제)만 담당한다. ResizeObserver 로 크기 변화를 자동 추종(레이아웃 setRatio·MDI 분할·창 크기·모바일 스택 전환에 화면 코드 불필요). 언마운트 시 자동 dispose

사용 예

import { MocaReactEchart } from '@teammoca/react-echart'

const option = { xAxis: {...}, yAxis: {...}, series: [{ type: 'bar', data: [...] }] }
<MocaReactEchart option={option} />   {/* 부모(레이아웃 pane)를 100% 채운다 */}

// 제품 API 직접 사용 — ref 로 실제 echarts 인스턴스를 받는다
chartRef.current?.getChart((c) => c.dispatchAction({ type: 'highlight', dataIndex: 0 }))

Props

option
ECharts option 객체 — 바뀔 때마다 전체 교체(notMerge) 적용 (MOCA setValue 대응). null/undefined 면 차트를 비운다(clear). option 문법은 ECharts 제품 문서를 그대로 따른다
merge
true 면 전체 교체 대신 병합(setOption 기본 동작) — 시리즈 데이터만 갈아끼우는 부분 갱신용 (기본 false)
theme
"auto"(기본 — OS 다크면 echarts 내장 "dark" 테마, MOCA 앱 기조 대응) | echarts 테마명 | null(기본 테마). 테마가 바뀌면 인스턴스를 재생성한다(echarts 테마는 init 시점 고정)
height
차트 높이(px). 생략하면 부모를 100% 채운다 — 레이아웃 pane 안에서는 생략이 기본. 높이가 0 이면 조용히 안 보이니 부모 높이를 확인할 것(MOCA 동일 주의)

메서드 (ref API)

getChart(cb)
echarts 인스턴스를 콜백으로 넘긴다 — 제품 API 직접 사용 진입점 (MOCA 대응)
ref.current?.getChart((c) => c.on("click", fn))
getChartObj()
echarts 인스턴스 즉시 반환 (마운트 전이면 null)
resize()
수동 리사이즈 — 크기 변화는 ResizeObserver 가 자동 추종하므로 즉시 반영이 필요할 때만
clear()
차트 비우기 (option prop 을 null 로 줘도 동일)

MocaReactFloatingButton

import { MocaReactFloatingButton } from '@teammoca/react-floating-button'

화면 위에 떠 있는 원형/알약형 액션 버튼(FAB) — MOCA floatingButton 이식. 기본 우하단(fixed), 꾹 누르면(350ms) 드래그 모드로 자유롭게 이동(마우스·터치 공통, 뷰포트/부모 클램프, 드래그 직후 click 무시). 레이아웃의 골격 미리보기 버튼도 이 컴포넌트다

사용 예

import { MocaReactFloatingButton } from '@teammoca/react-floating-button'

<MocaReactFloatingButton onClick={fnNew} title="신규" />              {/* plus 원형 */}
<MocaReactFloatingButton label="저장" icon="💾" onClick={fnSave} />   {/* 알약형 */}
<MocaReactFloatingButton absolute position="rt" icon="✏️" bgColor="#b4493e" onClick={fnEdit} />

Props

label
버튼 텍스트 (MOCA data-m-label 대응) — 주면 아이콘 옆에 텍스트가 붙는 알약형, 생략하면 아이콘만 원형. aria-label 로도 들어간다
icon
아이콘 (기본 'plus' — MOCA data-m-icon 대응). 'plus'=내장 SVG(색 추종) · '/' 나 '.' 포함=이미지 경로 <img> · 그 외 문자열=글리프 텍스트(이모지) · ReactNode=그대로 렌더(React 판 확장)
icon="✏️"
iconMode
"img"(기본) | "mask" — 경로 아이콘을 CSS mask 로 currentColor 를 채워 그린다(파일 아이콘도 색·테마 자동 추종, MOCA data-m-iconmode 대응)
bgColor
배경색 (MOCA data-m-bgcolor 대응). 기본은 테마 토큰(primary)이라 라이트/다크 자동 추종 — 화면 고유색을 줄 때만 지정
color
아이콘·글자색 (MOCA data-m-color 대응)
position
모서리 위치 — "rb"(기본·우하단) | "lb" | "rt" | "lt" (MOCA data-m-position 대응)
absolute
true 면 fixed 대신 부모(positioned ancestor) 기준 절대배치 — 레이아웃/컨테이너 안 버튼용. 드래그 클램프도 부모 기준이 된다
draggable
꾹 눌러 드래그 이동 (기본 true — MOCA data-m-draggable 대응). 짧은 클릭은 onClick, 350ms 홀드 후 이동, 대기 중 8px 이상 움직이면 취소. 이동 위치는 마운트 동안만 유지(영속화 없음)
className
외형 확장 — 쓰는 쪽 CSS 가 기본 외형(.mrf)을 덮을 수 있다
레이아웃의 mrl-debug-fab

이벤트 (콜백 props)

onClick
클릭 콜백 (MOCA data-m-onclick 대응). Enter/Space 키보드도 발화(접근성). 드래그로 이동한 직후의 클릭은 무시된다

MocaReactGrid

import { MocaReactGrid } from '@teammoca/react-grid'

가상 스크롤 그리드 — 전용 스크롤바 아키텍처(MOCA .moca_scrollY_type1 이식)로 10만 행에서도 스크롤 플래시가 구조적으로 없다. 컬럼 리사이징 · 특수컬럼(행상태/행번호/삭제체크) · 행 선택 · 툴바(제목/총건수/전체화면/행높이/찾기) 내장

사용 예

import { MocaReactGrid } from '@teammoca/react-grid'

<MocaReactGrid
  label="사원 목록"
  columns={[{ key: 'name', title: '이름', width: 120 }, { key: 'dept', title: '부서' }]}
  data={rows}
  onRowSelected={(row, idx) => setSelected(row)}
  search rowHeightSelect
/>

Props

columns
컬럼 정의 [{ key, title, width?, align?, format?, calc?, merge?, cellType? }]. width(px) 없으면 남은 폭을 균등 분배하고, **그리드가 넓어지면 그 컬럼들이 다시 흡수한다**(팝업이 열리며 커질 때·레이아웃 setRatio·MDI 분할·창 크기 — 오른쪽에 빈 띠가 남지 않는다). 좁아질 때는 줄이지 않고 가로 스크롤이 생기며, 사용자가 헤더를 드래그해 정한 폭은 이후 재배분에서 제외된다. 셀 값은 row[key] 로 읽는다. cellType: "tree" 면 트리 컬럼(아래 treeColumn 절), calc/merge 는 집계·병합(아래 aggColumn 절)
columns={[{ key: "name", title: "이름", width: 120 }]}
columns[].align
셀 정렬 — "left"(기본)|"center"|"right". 헤더는 항상 중앙
{ key: "SALE_AMT", title: "판매금액", align: "right" }
columns[].format
표시 가공 함수 (value, row) => 표시값 (MOCA data-m-displayfunction 대응). 집계행 값에도 적용된다
{ key: "SALE_AMT", format: (v) => Number(v).toLocaleString() }
data
행 객체 배열. 참조가 바뀌면(재조회) 선택·찾기 캐시가 초기화된다 — MOCA drawGrid 와 동일
data={rows}
rowHeight
행 높이(px, 기본 26 — MOCA data-m-defaultcellheight 기본값). 가상화 계산 기준이라 모든 행이 같은 높이
height
그리드 전체 높이(px). 생략하면 부모를 100% 채우는 반응형 — MDI 탭 안에서는 생략이 기본
overscan
보이는 구간 위아래 여유 렌더 행 수(기본 3) — 경계의 부분 행 대비
statusBar
true(기본) 면 하단 푸터 표시. 왼쪽(좌측 정렬) = 지금 보고 있는 것 — 검색했으면 "검색 N건 · M번째", 선택한 행이 있으면 "K행". 오른쪽(우측 정렬) = 전체 규모 — 총 건수, 트리 접힘 시 표시 건수, 삭제체크 시 선택 건수. 헤더에는 건수를 두지 않는다(좁은 폭에서 제목·찾기와 자리를 다툰다)
rowStatus
true(기본) 면 특수컬럼 [0] 행상태(22px) 표시 — row._status 값(C:신규 U:수정 D:삭제)을 색으로
rowNum
true(기본) 면 특수컬럼 [1] 행번호(28px) 표시 — 전체 데이터 기준 절대 번호, 좁은 폭에는 scaleX 가로압축
delCheck
true(기본) 면 특수컬럼 [2] 삭제체크(26px) 표시 — 행별 X 체크 + 헤더 전체선택
label
그리드 제목. 지정하면 상단 툴바(제목 + 찾기·행높이·전체화면 버튼)가 나타난다. 건수는 툴바가 아니라 푸터(statusBar)에 있다
label="사원 목록"
fullScreen
true(기본) 면 툴바에 전체화면 버튼 — label 지정 시에만 의미. MOCA _fullScreenGrid 의 overlayer 방식
rowHeightSelect
true 면 툴바에 행높이 셀렉트(기본 false — React 판 자체 기능). 배열을 주면 그 값들이 선택지(기본 [22,26,32,40]). 선택값이 rowHeight prop 보다 우선
rowHeightSelect={[20, 26, 36]}
search
true 면 툴바에 찾기(검색어 + 다음 버튼, 기본 false — 자체 기능). 검색 결과(총 검색건수·몇 번째)는 툴바가 아니라 푸터 왼쪽에 표시된다. 모든 데이터 컬럼 부분일치(대소문자 무시), Enter/다음마다 다음 매치 행 선택 + 가운데 스크롤, 끝나면 순환. 트리에서 접혀 숨은 행·집계행은 대상에서 제외
sortable
true 면 모든 데이터 컬럼 헤더에 정렬 버튼 (기본 false — MOCA data-m-sortable 대응). 누를 때마다 오름차순 → 내림차순 → 원래대로 3단 순환이고, 한 번에 한 컬럼만 걸린다. 같은 값이면 원본 순서를 지키고(안정 정렬), 빈 값은 방향과 무관하게 뒤로 보낸다. 양쪽 다 숫자면 숫자 비교라 **콤마를 찍어 문자열로 만든 값은 정렬이 틀어진다** — 숫자로 두고 columns[].format 으로 표시만 가공할 것. 트리 컬럼이 있으면 무시된다(PATH 정렬이 전제)
sortable
columns[].sortable
그 컬럼만 정렬 버튼 켜기/끄기 — 그리드의 sortable 보다 우선
{ key: "MEMO", title: "비고", sortable: false }
filterable
true 면 모든 데이터 컬럼 헤더에 필터 버튼 (기본 false — MOCA data-m-filterable 대응). 그 컬럼의 고유값을 건수와 함께 늘어놓은 드롭다운에서 골라 켠다(값 검색 · 가나다순/건수순 · 전체선택). 여러 컬럼에 걸면 AND 이고, 전체를 고른 채 적용하면 그 컬럼 필터는 해제된 것으로 본다. 걸려 있는 동안 푸터 건수가 "필터 N/전체"로 바뀌고 툴바에 [필터 해제] 가 나타난다. 트리 컬럼이 있으면 무시된다
filterable
columns[].filterable
그 컬럼만 필터 버튼 켜기/끄기 — 그리드의 filterable 보다 우선. 고유값이 많은 키 컬럼(사번·일련번호)은 꺼 두는 편이 좋다
{ key: "EMP_NO", title: "사번", filterable: false }
filterDistinctLimit
필터 목록을 만들 고유값 상한 (기본 1000 — MOCA data-m-filterdistinctlimit 대응). 넘는 컬럼은 드롭다운을 열어도 목록 대신 안내만 나온다 — 체크박스를 수십만 개 만들면 브라우저가 멈추기 때문
filterDistinctLimit={3000}
subtotal
소계 그룹 컬럼 key (MOCA data-m-subtotal 대응 — 1단만). 그룹값의 인접 런(연속 구간) 단위로 소계행이 삽입되므로 데이터는 그 컬럼으로 정렬되어 내려와야 한다(SQL ORDER BY). 런 수 > 고유값 수(그룹 흩어짐)면 틀린 소계를 보여주느니 생략한다
subtotal="REGION_NM"
total
합계행 위치 — true/"bottom"(기본, 맨 아래 고정) | "top"(헤더 아래 고정) | false(없음) (MOCA data-m-total 대응). 합계행은 스크롤과 무관하게 항상 제자리(pin). calc 컬럼이 하나도 없으면 집계 자체가 꺼진 것
total="top"
subtotalLabel
소계행 라벨 (기본 "소계" — MOCA data-m-subtotallabel 대응). 그룹값 뒤에 붙어 "서울 소계" 처럼 표시된다
subtotalLabel="평균/최저/최대"
totalLabel
합계행 라벨 (기본 "합 계" — MOCA data-m-totallabel 대응)
MocaReactGrid · aggColumn
calc: 'sum'|'count'|'avg'|'min'|'max'
이 컬럼을 집계 대상으로 (MOCA data-m-calc 대응). avg 는 소수 2자리 반올림 · 전체 합÷전체 건수(소계 평균들의 평균이 아님). 빈 값은 어떤 연산에도 안 센다. min/max 는 양쪽 다 숫자면 숫자, 아니면 문자열 비교(날짜 문자열 동작). 트리와 동시 사용 불가
{ key: "SALE_QTY", title: "수량", calc: "sum", align: "right", format: comma }
merge: true
연속된 같은 값을 세로 병합해 표시 (MOCA data-m-colmerge 대응) — 값은 병합 구간의 첫 표시 행에만 그려진다(colmergealign="top" 방식이라 스크롤해도 구간 첫 행이 항상 라벨을 가진다). 집계행은 병합 런을 끊는다. 병합 셀은 배경이 불투명해 지브라/hover/선택 강조가 비치지 않는다(rowspan 병합과 같은 규칙)
{ key: "REGION_NM", title: "지역", merge: true }
MocaReactGrid · treeColumn
cellType: 'tree'
이 컬럼을 트리로 그린다 (MOCA data-m-celltype="tree" 대응, title 이 data-m-name 대응). 부모 노드의 +/− 로 접고 펼친다 — 접으면 모든 자손이 숨지만 자손의 접힘 상태는 보존되어, 부모를 다시 펼치면 이전에 펼쳐져 있던 모습 그대로 복원된다(MOCA 는 접을 때 하위 부모를 강제로 접어 상태가 사라진다 — 의도된 개선). 가상 스크롤은 보이는 행 목록 위에서 동작하므로 대용량 트리도 그대로 빠르다. data 가 바뀌면 전체 펼침으로 초기화
{ key: "TREE", title: "메뉴", cellType: "tree", levelKey: "DEPTH", labelKey: "MENU_NM", treeKey: "MENU_ID", width: 240 }
levelKey
깊이 컬럼명 (MOCA data-m-levelid 대응). 깊이는 1부터 — 1은 들여쓰기 없음, 2부터 연결선(│├└)이 그려진다
labelKey
트리에 함께 표시할 라벨 컬럼명 (MOCA data-m-labelid 대응). 라벨이 빈 행은 트리 셀을 그리지 않는다
treeKey
트리 계산용 노드 고유키 컬럼명 (MOCA data-m-treeid 대응). 현재 표시에는 쓰이지 않고 행 추가류 기능을 위해 예약

이벤트 (콜백 props)

onRowSelected
행 클릭 시 (row, rowIndex) 로 호출 — rowIndex 는 data 기준 절대 인덱스. 같은 행을 다시 클릭해도 매번 호출된다(MOCA data-m-onrowselected 동일). 찾기로 행이 선택될 때도 발화
onRowSelected={(row, idx) => setSelected(row)}

MocaReactInputCalendar

import { MocaReactInputCalendar } from '@teammoca/react-input-calendar'

날짜 선택 — 입력칸 + 달력 팝업 (MOCA inputCalendar 이식, 데모 TPL042 대응). dateType 하나가 "무엇을 고르는 달력인가"를 정하고 값 자릿수·팝업 모드(연 목록/월 목록/일자 격자+시각)·기본 표시형식이 전부 거기서 파생된다. 값은 언제나 숫자만 있는 문자열(서버·SQL 과 그대로 주고받는다)이고 표시는 displayFormat 의 # 마스크가 만든다. 팝업은 body 로 portal + fixed — 폼·패널처럼 overflow:auto 인 스크롤 영역 안에서도 잘리지 않는다

사용 예

import { MocaReactInputCalendar, mdate } from '@teammoca/react-input-calendar'

// 기본(일자) — 오늘로 시작, 값 확정 시 콜백
<MocaReactInputCalendar displayFormat="####-##-##" defaultValue="오늘"
  onDateSelected={(v) => console.log(v)} />   {/* v = '20260815' */}

// 연월 · 연 · 일시
<MocaReactInputCalendar dateType="yyyyMM" defaultValue="오늘" />        {/* 값 6자리 */}
<MocaReactInputCalendar dateType="yyyy" defaultValue="3년전" />          {/* 값 4자리 */}
<MocaReactInputCalendar dateType="yyyyMMdd hh:mm:ss" defaultValue="오늘" />

// 범위 제한 + ref API
const cal = useRef(null)
<MocaReactInputCalendar ref={cal} dateMin="20260710" dateMax="20260820" />
cal.current.setValue(mdate.addDay(cal.current.getValue(), 1))

Props

dateType
무엇을 고르는 달력인가 — 값 자릿수·팝업 모드·기본 표시형식이 여기서 파생된다(기본 yyyyMMdd). 대소문자·구분자 무시("yyyyMMdd hh:mm:ss" = yyyyMMddHHmmss), 별칭 year/month/date/day/datetime (MOCA data-m-datetype 대응). 일시 타입의 팝업은 **시·분만** 고르게 한다 — 값의 초 자리는 들어온 값을 그대로 유지한다(기간이면 시작 00·종료 59)
value
제어 모드 값(숫자문자열) — onChange 와 함께 쓴다. 폼 상태를 화면이 들고 있을 때
defaultValue
비제어 초기값 — 숫자문자열 또는 상대일 표현: "오늘" · "전일"/"어제" · "내일" · "N일전/후" · "N개월전/후" · "N년전/후" · "당월"(그 달 1일) · "전월" (MOCA data-m-defaultvalue 대응)
defaultValue="7일전"
displayFormat
표시형식 — "#" 마스크(기본은 dateType 의 기본형식). 값은 그대로 숫자문자열이고 표시만 바뀐다 (MOCA data-m-displayformat 대응)
displayFormat="####/##/##"
dateMin
선택가능 하한(yyyyMMdd) — 범위 밖 칸·이동버튼·[오늘]이 잠기고 직접 타이핑도 확정 시 거부된다. 연/연월 타입은 "그 값이 덮는 기간이 범위와 겹치는가"로 판정한다(MOCA data-m-datemin 대응)
dateMax
선택가능 상한(yyyyMMdd) — MOCA data-m-datemax 대응
readOnly
true 면 잠금 — 입력·팝업 모두 막힌다 (MOCA data-m-readonly 대응)
width
입력칸 너비(px 또는 CSS 값, 기본 160). 연 선택처럼 짧은 값은 110 정도가 적당
placeholder
안내문(기본은 표시형식을 0 으로 바꾼 모양)

이벤트 (콜백 props)

onChange
(value) => void — 값이 바뀔 때(제어 모드 필수). 값은 타입 자릿수로 정규화된 숫자문자열
onDateSelected
(value) => void — 값이 확정될 때(팝업 선택·타이핑 확정). ref.setValue 로 넣을 때는 발화하지 않는다(MOCA data-m-ondateselected 와 같은 규약)
onDateSelected={(v) => fnSearch(v)}

메서드 (ref API)

getValue()
현재 값(숫자문자열, 없으면 "")
const v = cal.current.getValue()
setValue(v)
값 설정 — 타입 자릿수로 맞춘다(넘치면 자르고, 모자라면 월·일 01 / 시각 00 으로 채움). 이벤트 미발화
cal.current.setValue("20260815")
setReadOnly(bool)
잠금 토글 (MOCA setReadOnly 대응)
clear()
값 비우기(이벤트 미발화)
focus()
입력칸 포커스
import { mdate } from '@teammoca/react-input-calendar'
날짜 유틸(MOCA moca.$g.date 대응) — getToday/getNow · addDay/addMonth/addYear · getFirstDayOfMonth/getLastDayOfMonth · shiftYm · inRange · resolveRelative. 전부 숫자문자열 기반
mdate.addDay(mdate.getToday(), -7)

MocaReactLayout

import { MocaReactLayout } from '@teammoca/react-layout'

반응형 분할 레이아웃 — layout 속성 하나로 자식들을 방향(v/h) + 비율 토큰으로 분할 배치한다(MOCA layout.js 이식). 모든 화면 골격의 시작점. 데스크톱은 부모를 스크롤 없이 꽉 채우고, 모바일(≤768px)은 방향 무관 세로 스택으로 전환된다. 중첩 가능, Alt+L 로 골격 미리보기(와이어프레임)

사용 예

import { MocaReactLayout } from '@teammoca/react-layout'

// 가장 흔한 골격 — 검색영역(fit) + 본문(auto), 본문은 h3:7 중첩
<MocaReactLayout layout="vfit:auto">
  <div>검색영역</div>
  <MocaReactLayout layout="h3:7" ref={bodyRef}>
    <MocaReactGrid ... />   {/* 목록 3 */}
    <MocaReactGrid ... />   {/* 상세 7 */}
  </MocaReactLayout>
</MocaReactLayout>

bodyRef.current?.setRatio('h0:10')  // 런타임 변경 — 좌측 접기

Props

layout
분할 스펙 "{방향}{토큰:토큰[:…]}" (기본 "v"). 방향 v=세로 스택·h=가로 분할. 토큰은 자식 개수만큼 ":" 나열(개수 제한 없음, 생략 시 균등). 통짜(분할 없음)의 표준 표기는 v1. 토큰 종류는 아래 tokens 절
layout="vfit:auto:fit"
width
루트 고정 폭(px 숫자 또는 CSS 값 — MOCA data-m-width 대응). 주면 꽉채움 대신 이 크기 기준으로 분할하고 넘치면 부모가 스크롤
height
루트 고정 높이 (MOCA data-m-height 대응)
gap
칸 사이 간격(px 숫자 또는 CSS 값, 기본 15 — MOCA --m-layout-gap 대응)
mobileHeights
칸별 모바일(≤768px) 고정 높이 배열 — 해당 없는 칸은 null (MOCA data-m-mobileheight 마커 대응, PC 무영향). h(가로) 레이아웃은 토큰이 "폭"이라 모바일 높이를 명시할 방법이 없어 생긴 장치
mobileHeights={[null, 360]}
debug
true 면 골격 미리보기를 켠 채 시작(기본 false). 런타임 토글은 Alt+L 또는 우하단 FAB — 루트 레이아웃이 트리 전체에 칸 구조·토큰 라벨을 그린다
wireframeButton
루트 레이아웃 우하단의 골격 미리보기 토글 FAB 표시 (기본 true — MOCA _injectDebugFab/mconfig.wireframeBtn 대응). 실체는 MocaReactFloatingButton 이고 켜짐 상태는 primary 글로우로 표시. 운영 화면에서 숨기려면 false. 중첩 레이아웃에는 붙지 않는다(루트당 1개)
MocaReactLayout · tokens
숫자
상대 비율(flex-grow) — 합이 10일 필요 없다(2:8 = 1:4 = 20:80). 0 을 주면 그 칸이 접힌다(setRatio("h0:10") 사이드 토글)
layout="h3:7"
숫자px
고정 크기 칸(주축 기준 — h 는 폭, v 는 높이). 모바일 세로스택에서는 내용 높이로 풀린다(그리드가 든 칸은 예외 — 높이 유지)
layout="h240px:auto"
fit
내용 크기만큼(hug) — 검색영역/버튼줄 용. vfit:auto 가 가장 흔한 화면 골격, vfit:auto:fit 이 팝업 표준 골격
auto
남은 공간 전부(내부적으로 grow 1 — 2:auto 는 2:1 과 동일). px/fit 고정 칸과 짝지어 "고정+나머지" 패턴으로 쓰는 것이 의도
MocaReactLayout · markers
class="mrl-fill"
모바일(≤768px) 세로스택에서 그 칸이 남은 높이를 채우게 하는 표식. 모바일은 칸을 내용 높이로 푸는데, 내용이 전부 absolute 이거나(플로팅 버튼 상자) 캔버스처럼 내재 높이가 없는 박스는 0 으로 주저앉아 화면 아래가 빈 채로 남는다. 그리드가 든 칸은 자동으로 채워지므로 표식이 필요 없다
&lt;div className="myBox mrl-fill"&gt;

메서드 (ref API)

setRatio(spec)
런타임 비율(+방향) 변경 (MOCA $p.get("lay").setRatio 대응). 방향 문자를 생략하면 현재 방향 유지. layout prop 이 바뀌면 런타임 변경분은 리셋된다
ref.current?.setRatio("7:3") — 방향 유지 / setRatio("v5:5") — 세로 전환

MocaReactMdi

import { MocaReactMdi } from '@teammoca/react-mdi'

MDI 탭 셸 — 좌우 화면분할 + 탭 드래그 이동(MOCA mdi.js 이식). 탭 목록(tabs)은 부모가 소유하고 MDI 는 분할/이동/활성화를 담당. 탭별 고정 홀더 + createPortal 로 패널 이동 시에도 화면 상태(그리드 스크롤·선택 등)가 보존된다

사용 예

import { MocaReactMdi } from '@teammoca/react-mdi'

const mdiRef = useRef(null)
<MocaReactMdi
  ref={mdiRef}
  tabs={tabs}
  onTabClose={(key) => setTabs((ts) => ts.filter((t) => t.key !== key))}
  emptyHint="좌측 메뉴에서 화면을 선택하세요"
/>
mdiRef.current?.activate('userlist')  // 이미 열린 탭 활성화

Props

tabs
열린 탭 목록 [{ key, title, content, pinned? }] — 부모가 소유. title 은 ReactNode 가능, content 는 렌더할 화면 ReactNode, pinned 는 닫기 버튼 없음(MOCA 고정탭 컨셉). 새로 추가된 탭은 자동 활성화
setTabs((ts) => [...ts, { key, title, content: <Screen /> }])
emptyHint
분할 아님 + 탭 0개일 때 메인 패널에 보여줄 안내 문구

이벤트 (콜백 props)

onTabClose
닫기/전체닫기 요청 시 (key) 로 호출. 실제 제거(tabs 에서 빼기)는 부모 몫
onTabClose={(key) => setTabs((ts) => ts.filter((t) => t.key !== key))}
onActiveChange
지금 보이는 탭 key 배열로 호출 — 분할 중이면 두 개(메인, 서브). 좌측 메뉴에서 "열려 있음"과 "지금 보고 있음"을 구분해 칠하는 용도. 값이 실제로 바뀔 때만 호출되고, 탭이 닫히거나 패널을 옮겨 활성 탭이 자동 보정된 경우도 반영된다
onActiveChange={setActiveKeys}

메서드 (ref API)

activate(key)
이미 열린 탭을 활성화 — 메뉴에서 같은 화면을 다시 클릭했을 때 사용(새 탭은 자동 활성화라 부를 필요 없다)
mdiRef.current?.activate("userlist")

MocaReactMultiCalendar

import { MocaReactMultiCalendar } from '@teammoca/react-input-calendar'

기간 선택(from~to) — 한 컴포넌트가 입력칸 두 개와 달력 두 판(좌=시작, 우=종료)을 관리한다 (MOCA inputMultiCalendar 이식, 데모 TPL043 대응). dateType 계약은 단일 달력과 같고(값 자릿수·팝업 모드·기본 표시형식 파생, calCore 공유), 기간이라서 더해지는 것은 셋: 빠른선택 버튼(quickItems) · 최대 기간(maxTermBy*) · from>to 자동 보정. 종료값은 시각을 23:59:59 로 채워 그 날 하루가 빠지지 않게 한다(MOCA 규약)

사용 예

import { MocaReactMultiCalendar, mdate } from '@teammoca/react-input-calendar'

// 기본 — 당월(1일~말일)로 시작
<MocaReactMultiCalendar displayFormat="####-##-##" defaultValue="당월"
  onTermSelected={({ from, to }) => fnSearch(from, to)} />

// 빠른선택 + 최대 31일
<MocaReactMultiCalendar defaultValue="금주" quickItems="오늘,금주,당월,전월,당분기,당년"
  maxTermByDay={31} />

// 연월·연·일시 기간 (단일 달력과 같은 dateType)
<MocaReactMultiCalendar dateType="yyyyMM" defaultValue="당년" />

// ref API
term.current.setMultiCalendar({ from: '20260801', to: '20260831' })
term.current.getTerm()   // { from, to }

Props

dateType
단일 달력과 같은 5종(기본 yyyyMMdd) — 값 자릿수·팝업 모드(연 목록/월 목록/일자 격자)·기본 표시형식이 파생된다
from
제어 모드 시작값(숫자문자열) — onChange 와 함께 쓴다
to
제어 모드 종료값
defaultValue
비제어 초기 기간 — 프리셋 문구 또는 { from, to } 객체. 프리셋: "오늘" · "전일" · "금주"(일~토) · "전주" · "당월"(1일~말일) · "전월" · "당분기" · "전분기" · "당년" · "전년" · "N일전/개월전/년전"(그만큼 전 ~ 오늘) (MOCA data-m-defaultvalue 대응)
defaultValue="당월"
quickItems
팝업 왼쪽 빠른선택 버튼 목록 — 문자열("오늘,금주,당월") 또는 배열. 기본은 MOCA 와 같은 10종(오늘,전일,금주,전주,당월,전월,당분기,전분기,당년,전년). false/빈값이면 버튼 영역을 숨긴다 (MOCA data-m-selecteritem 대응)
maxTermByDay
최대 기간(일) — 넘기면 선택이 거부된다(양 끝 포함. 31 이면 8/1~8/31 까지 허용). MOCA data-m-maxtermbyday 대응
maxTermByMonth
최대 기간(개월) — MOCA data-m-maxtermbymonth
maxTermByYear
최대 기간(년) — MOCA data-m-maxtermbyyear
dateMin
선택가능 하한 — 좌·우 달력 모두 범위 밖 칸·이동버튼이 잠기고 타이핑도 거부된다
dateMax
선택가능 상한
readOnly
true 면 두 칸 모두 잠금(팝업도 안 열린다)

이벤트 (콜백 props)

onChange
({ from, to }) => void — 값이 바뀔 때(제어 모드 필수)
onTermSelected
({ from, to }) => void — 기간이 확정될 때. 팝업에서는 **[확인] 을 눌렀을 때만** 발화한다(2026-08-19 — 기간은 두 번 골라야 완성되므로 고를 때마다 쏘면 반쪽 기간으로 조회가 돈다). 빠른선택은 기간만 채우고 팝업을 닫지 않는다. 타이핑 확정(blur)은 종전대로 즉시. set* API 는 발화하지 않는다
onTermSelected={({from,to}) => fnSearch(from,to)}

메서드 (ref API)

getValue()
시작값(from) — MOCA getValue() 가 from 을 주는 규약 그대로
getTerm()
{ from, to } 반환 — React 판 편의 API
const { from, to } = ref.current.getTerm()
setFrom(v)
시작값 설정(이벤트 미발화). to 보다 뒤면 to 가 따라온다
setTo(v)
종료값 설정(이벤트 미발화). from 보다 앞이면 from 이 따라온다
setMultiCalendar({from, to})
기간 일괄 설정(이벤트 미발화)
ref.current.setMultiCalendar({ from: mdate.getFirstDayOfMonth(t), to: mdate.getLastDayOfMonth(t) })
setReadOnly(bool)
잠금 토글
clear()
기간 비우기

MocaReactScheduleCalendar

import { MocaReactScheduleCalendar } from '@teammoca/react-schedule-calendar'

월간 일정 달력 (MOCA scheduleCalendar 이식, 데모 TPL041 대응). 앞의 달력 3종이 "날짜를 고르는" 컴포넌트라면 이쪽은 "일정을 보여주는" 컴포넌트다. 화면이 하는 일은 셋뿐: ① 월 변경 시 그 달 조회 ② 아이템으로 매핑해 넘기기 ③ 일자 클릭 시 getDayItems. 연속일정 일자 전개(pos one/s/m/e)·정렬(완료 뒤·연속 우선·시작일·시작시간·seq)·바 마크업·공휴일/날씨/뱃지 표기·좌우 스와이프는 컴포넌트가 한다

사용 예

import { MocaReactScheduleCalendar } from '@teammoca/react-schedule-calendar'

<MocaReactScheduleCalendar ref={scc}
  schedules={schedules}      // [{ start, end, title, ico, done, crucial, bg, fg, mark, time, seq, data }]
  dayInfo={dayInfo}          // { '20260815': { holi: '광복절', wthr: '☀️', badges: ['휴'] } }
  hideOutside todayFlag menuButton
  onMonthChange={(ym) => loadMonth(ym)}   // 초기 조회는 화면이 getYm() 으로 직접
  onDayClick={(day) => setPicked({ ymd: day.ymd, items: scc.current.getDayItems(day.ymd) })}
  dayRenderer={(day) => day.dow === 0 ? <span>휴일</span> : null} />

Props

schedules
일정 아이템 배열 — { start:"yyyyMMdd"(필수), end(생략=단일), title, ico, done, crucial, span(생략 시 end>start 로 판정), bg, fg, mark(마커색 — 생략 시 fg), time:"HHmm"(시작시간 정렬키), endTime(상세용), seq(동순위 최후 정렬키 — 최근 우선), data(원본 행 — 컴포넌트는 보관만 하고 getDayItems 로 되돌려준다) }. ref.setData 로 넘겨도 된다
schedules={rows.map(toItem)}
dayInfo
{ yyyyMMdd: { holi, wthr, badges[] } } — 공휴일명(날짜 위에 표시·날짜 빨강) · 날씨 · 뱃지. 컴포넌트는 공휴일을 모른다(화면이 조회해 넘긴 것만 그린다)
hideOutside
true 면 표시 월 밖 칸(전월 말·익월 초)에는 일정·메타를 그리지 않고 날짜만 남긴다(MOCA data-m-hideoutside 대응)
startYm
초기 표시 월(yyyyMM, 기본 이번 달)
header
false 면 내부 헤더(메뉴·이전·연월·다음·오늘)를 숨긴다 — 화면이 이동 UI 를 직접 만들 때
menuButton
true 면 헤더 맨 앞에 햄버거 버튼 — 화면이 옵션·드로어를 여는 진입점(컴포넌트는 훅만 준다. onMenuClick 과 함께)
todayFlag
true 면 오늘 칸에 TODAY 깃발을 꽂는다(칸 위로 살짝 올라간 형태. 첫 주는 위 공간이 없어 칸 안쪽에 세운다. 클릭은 아래 칸이 받는다). 모션 최소화 설정에서는 흔들림만 멈춘다
dayRenderer
(day) => ReactNode — 날짜 배지 아래에 넣을 내용. day={ymd,dd,dow,inMonth,today}. MOCA 는 HTML 문자열을 반환했지만 React 판은 노드를 반환한다
dayRenderer={(day) => day.dow === 0 ? <span>휴일</span> : null}

이벤트 (콜백 props)

onMonthChange
(ym) => void — 표시 월 변경 시(헤더 버튼·오늘·setYm·스와이프). **초기화 시엔 미발화** — 첫 조회는 화면이 getYm() 으로 직접 한다(MOCA 와 같은 규약)
onMonthChange={(ym) => loadMonth(ym)}
onDayClick
(day) => void — 일 칸 클릭. day={ymd,dd,dow,inMonth,today}. 여기서 getDayItems 로 그 날 목록을 꺼낸다(서버 재조회 없음)
onMenuClick
() => void — 헤더 햄버거 클릭(menuButton 과 함께)

메서드 (ref API)

setData({schedules, dayInfo, hideOutside})
일정 표시 데이터를 명령형으로 넣는다(MOCA 표준 API). props 로 넘기는 방식과 같은 결과 — 기존 MOCA 화면을 옮길 때 이 형태가 편하다
getDayItems(ymd)
그 날의 일정 목록을 **화면 순서 그대로** 반환 — 이미 인덱싱해 둔 것이라 서버 재조회가 필요 없다. 각 항목 = 아이템 사본 + pos("one"|"s"|"m"|"e" — 그 날이 단일/시작/중간/종료 중 무엇인지) + ymd. data(원본 행)를 담아 넘겼다면 그대로 실려 온다
const items = scc.current.getDayItems(day.ymd)
getDayInfo(ymd)
그 날의 일자메타({holi,wthr,badges}) 반환 — getDayItems 와 대칭. 일자 목록에 공휴일·날씨까지 재조회 없이 함께 보여줄 때
getYm()
현재 표시 월(yyyyMM) — 초기 조회에 쓴다
loadMonth(scc.current.getYm())
setYm(ym)
그 월로 이동 — 월이 바뀌면 onMonthChange 발화
today()
오늘이 속한 월로 이동
next()
다음 달
prev()
이전 달
refresh()
강제 재렌더 — React 는 데이터가 바뀌면 자동으로 다시 그리므로 보통 필요 없다(dayRenderer 가 외부 값을 읽는 화면에서 MOCA 호환용)

MocaReactSearchCombo

import { MocaReactSearchCombo } from '@teammoca/react-search-combo'

타이핑으로 목록을 좁혀 고르는 검색형 콤보 — MOCA searchCombo 이식. 항목이 수십·수백 건이라 select 로는 훑기 힘든 자리에 쓴다. **필터는 화면에 보이는 표시문구 기준**(대소문자 무시)이라, 코드로도 검색시키려면 displayFormat 으로 코드를 문구에 넣는다. ↓↑ 로 이동하고 Enter/클릭으로 확정하며, 목록은 뷰포트 기준(fixed)으로 띄워 레이아웃 칸이나 헤더 안에서 잘리지 않고 아래 자리가 모자라면 위로 뒤집힌다. 데모 앱 헤더의 메뉴 검색이 이 컴포넌트다

사용 예

import { MocaReactSearchCombo } from '@teammoca/react-search-combo'

<MocaReactSearchCombo list={areas} value={area} onChange={(v) => setArea(v)} />

{/* 코드까지 검색되게 — 필터는 표시문구 기준이다 */}
<MocaReactSearchCombo list={codes} displayFormat="[value] · [label]" width={220} />

{/* 검색창 용법 — 고르고 나면 입력칸을 비운다(헤더 메뉴 검색) */}
<MocaReactSearchCombo list={menuList} clearOnSelect placeholder="메뉴 검색" onChange={openScreen} />

Props

list
목록 [{ cd, nm }] (필수). null 항목은 걸러낸다. 참조가 바뀌면 그 자리에서 목록이 갈린다 — 서버 조회 후 setState 로 넘기면 된다
list={areas}
cdKey
코드 필드명 (기본 'cd' — MOCA data-m-cdkey 대응)
cdKey="DEPT_CD"
nmKey
표시명 필드명 (기본 'nm' — MOCA data-m-nmkey 대응)
nmKey="DEPT_NM"
value
선택 코드값. 주면 **제어 컴포넌트**가 되어 값을 바꾸는 책임이 화면(state)에 있다 — onChange 에서 반영하지 않으면 값이 그대로 남는다. 비제어로 쓰려면 defaultValue
defaultValue
비제어일 때 초기 선택값 (기본 ''). 목록에 없는 값이면 표시는 빈 칸
displayFormat
표시문구 틀 — '[value]'/'[label]' 을 코드/표시명으로 치환한다 (MOCA data-m-displayformat 대응). **필터가 이 문구 기준**이라 코드·분류로도 검색시키려면 여기 넣어야 한다
displayFormat="[value] · [label]"
placeholder
입력칸 안내문 (기본 '내용을 입력하세요' — MOCA 동일)
readOnly
입력 불가 + ▼ 버튼 없음 (MOCA data-m-readonly 대응). 값은 표시만 된다
disabled
true 면 비활성 — 입력·열기 모두 막고 흐리게 표시
clearOnSelect
true 면 확정 후 입력칸을 비운다 (기본 false — React 판 자체 기능). "값을 고르는 칸"이 아니라 "검색해서 실행하는 칸"일 때 쓴다
clearOnSelect
emptyText
결과가 0건일 때 목록에 표시할 안내문 (기본 '검색 결과가 없습니다'). MOCA 는 목록을 닫아버려 고장과 구분이 안 됐다
maxHeight
목록 최대 높이(px, 기본 200 — MOCA 동일). 남은 공간이 이보다 작으면 그만큼만 쓰고 스크롤한다
width
컴포넌트 폭 (기본 CSS 160px — MOCA 기본 140px 과 달리 표시문구가 길어지는 용법을 기준으로 잡았다)
className
외형 확장 — 쓰는 쪽 CSS 가 기본 외형(.mrsc)을 덮을 수 있다
데모 앱 헤더의 mdi-menu-search 가 입력칸만 반투명으로 덮는다

이벤트 (콜백 props)

onChange
(value, item, changed) => void — **확정(Enter/클릭) 시에만** 발화한다. 타이핑 중에는 오지 않는다(MOCA 는 필터가 돌 때마다 첫 매칭을 선값해 값이 계속 바뀌었다). changed 는 이전 값과 다른지 여부로, **같은 항목을 다시 골라도 changed:false 로 발화**한다 — 메뉴 검색처럼 값이 아니라 행위가 필요한 화면 때문
onChange={(v, row) => setArea(v)}

메서드 (ref API)

getValue()
선택 코드값 (MOCA getValue 대응)
getLabel()
선택 표시문구 (MOCA getLabel 대응)
setValue(value)
값 지정 + 입력칸 표시 갱신 (MOCA setValue 대응). 제어 모드면 표시만 갱신되고 값의 주인은 그대로 화면이다
clear()
선택·입력 초기화
focus()
입력칸 포커스 — 화면을 열자마자 검색부터 시키고 싶을 때
open()
필터를 풀고 전체 목록 열기 (MOCA _searchComboFullShow 대응)
close()
목록 닫기 — 입력칸은 확정값 표시로 되돌아간다
getList()
현재 목록 사본 (MOCA getList 대응)

모듈

컴포넌트가 아니라 함수·훅 묶음이다 — 서버 통신(tran), 알림·확인(msgbox), 팝업(popup).

msgbox

import { msgbox } from '@teammoca/react-msgbox'

메시지박스 3종(alert · error · confirm) + 필수값 안내 + 진행 모달 (MOCA moca.$g.alert/error/confirm·requiredFail·showProgress 이식, 데모 TPL023 대응). MOCA 처럼 화면 어디서나 명령형으로 부르고, React 답게 await 로 답을 받을 수도 있다. 실제 렌더는 앱 루트에 한 번 놓는 <MocaReactMsgHost /> 가 맡는다(없으면 콘솔 경고). 모달은 큐로 한 번에 하나씩 표시하고(MOCA 는 겹쳐 쌓였다 — 의도된 개선), 진행 모달은 그 위에 별도로 뜬다. Enter=확인 · ESC=취소(confirm)/확인(alert·error), 열리면 확인 버튼에 포커스

사용 예

import { msgbox, MocaReactMsgHost } from '@teammoca/react-msgbox'

<MocaReactMsgHost />                      // 앱 루트에 한 번(App.jsx)

msgbox.alert('저장되었습니다.')             // MOCA 와 같은 호출
msgbox.alert('완료', () => reload())       // 콜백(MOCA 호환)
if (await msgbox.confirm('저장하시겠습니까?')) save()      // 답을 그 자리에서
const mode = await msgbox.confirm('어디에 추가할까요?', { radios: [...] })

if (!name) return msgbox.requiredFail('사용자명', () => ref.current.focus())

Props

message(문자열 | ReactNode)
문자열은 **텍스트로** 넣고 줄바꿈(\n)만 반영한다. 강조가 필요하면 노드를 넘긴다 — MOCA 는 innerHTML 이라 <b>/<br/> 을 썼지만, 서버 문장을 그대로 HTML 로 넣던 통로를 막아 XSS 여지를 없앤 것이다(의도된 차이)
msgbox.alert(<>성공 <b>12건</b></>)

메서드 (ref API)

msgbox.alert(message, cb?)
알림(확인 1개, ! 아이콘). cb 는 [확인] 후 실행. Promise 도 반환하므로 await 가능
msgbox.alert("저장되었습니다.")
msgbox.error(message, cb?)
오류 알림 — alert 과 같은 구조이되 danger 색 + ✕ 아이콘으로 구분한다(MOCA 동일)
msgbox.error("삭제할 수 없는 데이터입니다.")
msgbox.confirm(message, okCb?, cancelCb?, {radios}?)
확인/취소. 호출 형태 4가지 — confirm(msg) → Promise<boolean> · confirm(msg, ok, cancel) → MOCA 호환 콜백 · confirm(msg, ok, cancel, {radios}) → 확인 콜백에 선택값 · confirm(msg, {radios}) → Promise<선택값|null>(취소 null). radios=[{value,label,desc,checked}] — checked 가 없으면 첫 항목이 기본 선택
const mode = await msgbox.confirm("어디에 추가할까요?", { radios: [{ value: "child", label: "하위로" }] })
msgbox.requiredFail(label, focusCb?)
필수값 안내 — "{label}은(는) 필수입력항목입니다." 를 띄우고 **항상 false 를 즉시 반환**한다(MOCA 와 같은 규약이라 `if (!v) return msgbox.requiredFail(...)` 패턴이 그대로 동작). focusCb 는 [확인] 후 실행 — 해당 입력칸으로 포커스를 옮긴다
if (!name) return msgbox.requiredFail("사용자명", () => ref.current.focus())
msgbox.showProgress(message?, delay?)
진행 모달 표시 — delay(ms, 기본 300) 뒤에 나타난다. 그 안에 작업이 끝나면 모달이 아예 뜨지 않아 짧은 작업의 깜빡임이 없다(MOCA 와 같은 의도). 0 이면 즉시
msgbox.showProgress("조회중입니다", 0)
msgbox.setProgressMsg(message)
진행 모달 문구 교체(단계 표시)
msgbox.setProgressMsg("데이터 처리중 2/3")
msgbox.hideProgress()
진행 모달 닫기 — 통신과 함께 쓸 때는 **finally 에서** 부른다(실패해도 반드시 닫히게)
<MocaReactMsgHost />
메시지박스 호스트 — 앱 루트에 **한 번만** 놓는다(App.jsx). 이것이 없으면 msgbox 호출이 표시되지 않고 콘솔에 경고가 뜬다. 로그인 화면에서도 쓸 수 있어야 하므로 인증 게이트보다 앞에 둔다

popup

import { popup } from '@teammoca/react-popup'

화면을 모달 창으로 띄우고 결과를 받는다 (MOCA $p.openPop / $p.getParameter / $p.close 이식, 데모 TPL037 대응). **팝업 3동사** — 열기 popup.open(Component, 옵션) · 파라미터 팝업이 props.data 로 받기 · 닫기 팝업이 props.close(결과)로 닫기. MOCA 는 url 로 팝업 HTML 을 지정했지만 React 는 **컴포넌트를 그대로** 넘긴다(lazy 컴포넌트도 가능). 실제 렌더는 앱 루트에 한 번 놓는 <MocaReactPopupHost /> 가 맡는다(없으면 콘솔 경고). 메시지박스가 큐(하나씩)인 것과 달리 팝업은 **스택**이라 팝업 안에서 또 팝업을 열 수 있고(품목검색 → 거래처검색) 각 팝업은 자기 데이터·결과만 소유한다. 제목바 드래그 이동, ESC 로 맨 위 팝업 취소

사용 예

import { popup, MocaReactPopupHost } from '@teammoca/react-popup'
import PopItemSearch from './pop/PopItemSearch'

<MocaReactPopupHost />                    // 앱 루트에 한 번(App.jsx)

// await 로 받기(권장 — 답에 따라 다음 줄이 갈린다)
const row = await popup.open(PopItemSearch, { title: '품목 검색', width: 640, data: { keyword } })
if (row) setItem(row)                     // 취소면 undefined

// 콜백(MOCA openPop 호환) — 취소면 콜백이 아예 불리지 않는다
popup.open(PopItemSearch, { title: '품목 검색', callback: (row) => setItem(row) })

// 팝업 화면 — 업무 화면과 똑같이 만들고 결과를 close 로 넘긴다
function PopItemSearch({ data, close }) {
  return <button onClick={() => close(row)}>선택</button>   // close() 는 취소
}

Props

title
제목바 문구 — aria-label 로도 들어간다
width
팝업 너비(숫자=px 또는 CSS 문자열). 생략 시 560px. 화면보다 크면 화면 폭에 맞춰 줄어든다
width: 640
height
팝업 높이(숫자=px 또는 CSS 문자열). 생략 시 내용 높이에 맞추고 최대 90vh. **그리드를 담은 팝업은 height 를 주거나 팝업 화면 루트에 minHeight 를 준다** — 높이가 내용 기준이면 레이아웃의 auto(남는 공간) 칸이 0 이 되어 목록이 보이지 않는다(데모 팝업 화면들이 minHeight 방식)
height: 520
modal
false 면 뒤 화면을 계속 조작할 수 있는 보조창(딤이 클릭을 막지 않는다). 기본 true
modal: false
data
팝업에 넘기는 파라미터 — 팝업 컴포넌트가 **props.data** 로 받는다 (MOCA $p.getParameter() 대응)
data: { keyword: "마우스" }
callback
확정 결과를 받는 콜백(MOCA openPop 호환). close(결과)로 닫힐 때만 호출되고 **취소(close()·× ·ESC)면 호출되지 않는다** — MOCA 와 같은 규약. await 를 쓰면 없어도 된다
draggable
false 면 제목바 드래그 이동 금지 (기본 true). 이동 위치는 화면 밖으로 나가지 않게 클램프된다

메서드 (ref API)

popup.open(Component, { title, width, height, modal, data, callback, draggable })
팝업 열기 — **결과 Promise** 를 돌려준다(확정=close 로 넘긴 값, 취소=undefined). Component 는 화면 컴포넌트 자체이며 lazy() 도 된다(받는 동안 "화면 로딩중…"). 이미 팝업이 떠 있으면 그 위로 겹쳐 쌓인다
const row = await popup.open(PopItemSearch, { title: "품목 검색", width: 640 })
props.close(결과) ← 팝업 안에서
팝업이 스스로 닫는 방법 (MOCA $p.close 대응). **close(결과)=확정** — callback 발화 + Promise 결과 / **close()=취소** — 콜백 미발화 + Promise undefined. 닫기 버튼은 푸터 한 곳에만 두면 된다(제목바 × 는 셸이 준다)
popup.close(id?)
바깥에서 팝업을 닫는다(취소 처리). id 생략 시 가장 위 팝업 — 통신 실패로 팝업을 접어야 할 때
popup.closeAll()
열린 팝업 전부 닫기(모두 취소) — 화면 전환·로그아웃 때 부른다(App.jsx 로그아웃에 적용)
<MocaReactPopupHost />
팝업 호스트 — 앱 루트에 **한 번만** 놓는다(App.jsx). 없으면 popup.open 이 표시되지 않고 콘솔에 경고가 뜬다. document.body 포털로 그리므로 화면의 overflow 에 잘리지 않는다. z-index 는 7500 부터 겹친 순서대로(메시지박스 8000·달력 7800 이 그 위에 오도록 설계)

tran

import { tran } from '@teammoca/react-tran'

MOCA 서버 공용 쿼리 API 통신 레이어 (MOCA moca.$t.tran.exe 대응). 화면은 SQL 을 모르고 queryId 만 안다 — SQL 은 서버 매퍼(MocaMapper.xml)에 있다. CSRF 토큰 자동 첨부(쿠키 MOCA-CSRF-TOKEN → 헤더 X-CSRF-TOKEN), 세션 쿠키 동반, 401(미로그인)/403(CSRF) 콜백 처리, 결과 언랩(list1 → rows)을 대신한다. 개발 중에는 Vite 프록시가 /common 을 8080 으로 넘겨 동일 출처를 유지한다(vite.config.js)

사용 예

import { tran, useSelect, configureTran } from '@teammoca/react-tran'

// 화면 — 선언적 조회(조건이 바뀌면 자동 재조회)
const { rows, loading, error, reload } = useSelect('selectTdemoSaleDtlList', { REGION_NM })

// 명령형 호출
const rows = await tran.select('selectDemoUserList', { USER_NM: '김' })
await tran.save({ insertQueryId: 'insertDemoUser', list: rows })

// 앱 부팅 1회 — 인증 실패 시 로그인 화면 전환 통로
configureTran({ onAuthFail: () => setAuth('out') })

메서드 (ref API)

tran.select(queryId, params)
단일 조회 — rows 배열 반환(list1 언랩). 서버 응답은 컬럼 키를 대문자로도 함께 주므로 row.SALE_AMT 로 읽는다
const rows = await tran.select("selectDemoUserList", { DEPT_NM: "개발" })
tran.selectMulti(queryIds, bodyMap)
다건 조회 — { list1, list2, … } 반환. bodyMap 은 { queryId: params } 형태
const { list1, list2 } = await tran.selectMulti(["selectDashSalesMonthly", "selectDashDeptMember"])
tran.selectOne(queryId, params, preUpdateQueryId?)
단건 조회 — row 객체 반환(없으면 null). preUpdateQueryId 를 주면 조회 전 UPDATE 실행(조회수 증가 등)
tran.save({ insertQueryId, updateQueryId, deleteQueryId, list })
CRUD 저장 — list 각 행의 _system.status(C/U/D)에 따라 서버가 분기 실행하고 savedList 를 반환한다(그리드 행상태와 같은 규약)
tran.sessionCheck()
로그인 여부 확인 — { logined, user }. 앱 부팅이 가장 먼저 부른다(MOCA index.html 의 sessionchk 대응)
tran.login(id, pw)
ID/PW 로그인 — { ok, message, user }. 데모 계정 moca/moca
const { ok } = await tran.login("moca", "moca")
tran.logout()
세션 종료
useSelect(queryId, params, { enabled })
조회 훅 — { rows, loading, error, reload }. params 내용이 바뀌면 자동 재조회(검색조건 변경 = 재조회), 늦게 온 응답은 폐기, enabled:false 로 조회 보류. MOCA 의 onpageload + tran.exe 콜백 패턴을 선언형으로 바꾼 것
const { rows, reload } = useSelect("selectDemoUserList", { USER_NM })
configureTran({ base, onAuthFail, onCsrfFail })
앱 시작 시 1회 설정 — 401/403 을 받았을 때 앱이 할 일(로그인 화면 전환 등)을 연결한다. base 는 서버 context-path 가 다를 때만

앱 규약

데모 앱(src/)이 화면을 등록하고 메뉴에 올리는 방법 — 패키지 API 가 아니라 이 앱의 약속이다.

app.registry

화면 레지스트리(src/screens/registry.jsx) — MOCA 처럼 2단 메뉴 구조. 1단(그룹)은 상단 메뉴바에, 2단(화면)은 좌측 메뉴에 표시된다. 화면 컴포넌트는 전부 lazy 로딩(MOCA frame 의 화면 지연 로드 대응) — 빌드 시 화면별 청크로 잘리고 탭을 처음 열 때 받아온다(로딩 중 표시는 App.jsx 의 Suspense). 새 화면은 lazy 한 줄 + 그룹 children 등록 한 줄이면 메뉴에 자동으로 나타나고 MDI 탭으로 열린다

사용 예

// src/screens/registry.jsx — lazy 선언 한 줄 + 그룹의 children 에 한 줄
const UserList = lazy(() => import('./UserList'))
{
  key: 'work', title: '업무', icon: '🗂️',
  children: [
    { key: 'userlist', title: '사원 목록', icon: '👥', component: UserList },
  ],
}
app.registry · group
key
그룹 고유 식별자
title
상단 메뉴바에 표시할 그룹명
icon
메뉴에 표시할 이모지
children
이 그룹의 화면 목록(아래 screen 필드)
app.registry · screen
props.menuInfo
셸(App.jsx)이 모든 화면에 자동 주입하는 메뉴정보 { id, label, icon, path } (MOCA frame 의 getParameter() 대응). 공통 타이틀 <ComTitle menu={menuInfo} /> 에 그대로 전달하면 [ID]·아이콘·제목·브레드크럼이 표시된다. 필요 없으면 무시
function SaleAgg({ menuInfo }) { ... }
key
화면 고유 식별자 — 같은 key 의 탭은 중복으로 열리지 않고 활성화만 된다
title
좌측 메뉴 표시명 겸 탭 제목
icon
메뉴/탭 제목에 표시할 이모지
component
렌더링할 리액트 컴포넌트(src/screens/*.jsx)