공개이슈 ··· 및 그 전수 해소분. - 설정 저장·복원·초기화가 상주 프로세스 미러를 갱신하도록 단일 소유자화 - 만료 데이터 자동 정리 예약 12종 신설, 파기는 기본키 배치로 분할 - 회원 탈퇴를 전 경로 원자화하고 관리자 상태변경 경로를 정식 탈퇴로 통일 - 일괄 처리 목록의 선택을 화면에 보이는 행으로 한정(selectionScope) - 비밀번호 정책 문구를 도메인 중립 라벨로 정정 - 정적 검사 2종·검증 스킬 설계 정합성 축 신설
34 KiB
컴포넌트 Props 레퍼런스
중요: 레이아웃 JSON 작성 시 반드시 이 문서에서 지원되는 props를 확인하세요.
목차
- 핵심 원칙
- Select 컴포넌트
- Input 컴포넌트
- Button 컴포넌트
- DataGrid 컴포넌트
- HtmlEditor 컴포넌트
- CodeEditor 컴포넌트
- Modal 컴포넌트
- LoadingSpinner 컴포넌트
- 관련 문서
핵심 원칙
필수: 레이아웃 JSON 작성 전 이 문서에서 props 확인 (문서에 없는 props 사용 금지)
필수: 작성 후 /validate-frontend 스킬로 검증
props 사용 전 확인 체크리스트
레이아웃 JSON에서 컴포넌트 props 작성 시:
- 이 문서에서 해당 컴포넌트의 지원 props 확인
- props 타입과 기본값 확인
- 레이아웃 JSON 작성
/validate-frontend스킬로 검증
Select 컴포넌트
타입: basic | 템플릿: sirsoft-admin_basic, sirsoft-basic
파일: templates/*/src/components/basic/Select.tsx
Props 인터페이스
export interface SelectOption {
value: string | number; // 옵션 값 (필수)
label: string; // 표시될 라벨 (필수)
disabled?: boolean; // 옵션 비활성화 여부
}
export interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
// 커스텀 Props
options?: SelectOption[] | string[]; // 옵션 배열
label?: string; // 레이블 텍스트 (상위 컴포넌트에서 처리)
error?: string; // 에러 메시지 (상위 컴포넌트에서 처리)
searchable?: boolean; // 드롭다운 내 검색 input 활성화 (engine-v1.40.0+)
searchPlaceholder?: string; // 검색 input placeholder (searchable=true일 때)
// HTML Select 표준 Props (모두 지원)
value?: string | number; // 선택된 값 (제어 컴포넌트)
defaultValue?: string | number; // 기본 선택값 (비제어 컴포넌트)
name?: string; // 폼 필드명
id?: string; // 요소 ID
disabled?: boolean; // 비활성화
required?: boolean; // 필수 입력
className?: string; // CSS 클래스
multiple?: boolean; // 다중 선택 (children 모드만)
size?: number; // 표시 행 수 (children 모드만)
}
지원 Props 요약
| Props | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
options |
SelectOption[] | string[] |
❌ | - | 드롭다운 옵션 배열 |
value |
string | number |
❌ | - | 선택된 값 (제어 컴포넌트) |
defaultValue |
string | number |
❌ | - | 기본 선택값 (비제어 컴포넌트) |
name |
string |
❌ | - | 폼 필드명 |
disabled |
boolean |
❌ | false |
비활성화 |
required |
boolean |
❌ | false |
필수 입력 |
className |
string |
❌ | - | CSS 클래스 |
placeholder |
string |
❌ | - | 플레이스홀더 (옵션 배열 첫 항목) |
searchable |
boolean |
❌ | false |
드롭다운 내 검색 input 활성화 (label/value 부분일치 필터링, engine-v1.40.0+) |
searchPlaceholder |
string |
❌ | 'Search...' |
검색 input placeholder (searchable=true일 때) |
렌더링 모드
Select 컴포넌트는 두 가지 모드로 동작합니다:
- Options 모드 (권장):
optionsprop 제공 시 커스텀 드롭다운 - Children 모드:
options없이 children으로<option>제공 시 기본 HTML select
레이아웃 JSON 사용 예시
기본 사용 (제어 컴포넌트)
{
"type": "basic",
"name": "Select",
"props": {
"name": "category",
"value": "{{_local.category}}",
"className": "w-full",
"options": [
{ "value": "", "label": "선택하세요" },
{ "value": "1", "label": "카테고리 1" },
{ "value": "2", "label": "카테고리 2" }
]
},
"actions": [
{
"type": "change",
"handler": "setState",
"params": {
"target": "_local",
"category": "{{$event.target.value}}"
}
}
]
}
기본값 설정 (defaultValue 사용)
{
"type": "basic",
"name": "Select",
"props": {
"name": "status",
"defaultValue": "active",
"options": [
{ "value": "active", "label": "활성" },
{ "value": "inactive", "label": "비활성" }
]
}
}
공란 방지 패턴 (권장)
방법 1: state 초기값 설정 (권장)
{
"state": {
"salesStatus": "on_sale"
}
}
{
"type": "basic",
"name": "Select",
"props": {
"name": "salesStatus",
"value": "{{_local.salesStatus}}",
"options": [
{ "value": "on_sale", "label": "판매중" },
{ "value": "sold_out", "label": "품절" }
]
}
}
방법 2: defaultValue 사용
{
"type": "basic",
"name": "Select",
"props": {
"name": "status",
"value": "{{_local.status}}",
"defaultValue": "on_sale",
"options": [
{ "value": "on_sale", "label": "판매중" },
{ "value": "sold_out", "label": "품절" }
]
}
}
주의사항
value 타입 일치: value와 option.value 타입을 맞춰야 함
공란 방지: state 초기값 또는 defaultValue 설정 필수
✅ options 배열: 첫 번째 항목이 기본 선택됨 (value가 일치할 때)
Input 컴포넌트
타입: basic | 템플릿: sirsoft-admin_basic, sirsoft-basic
파일: templates/*/src/components/basic/Input.tsx
Props 인터페이스
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string; // 레이블 텍스트
error?: string; // 에러 메시지
helperText?: string; // 도움말 텍스트
}
지원 Props 요약
| Props | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
type |
string |
❌ | "text" |
input 타입 (text, email, password, number, date 등) |
value |
string | number |
❌ | - | 입력값 (제어 컴포넌트) |
defaultValue |
string | number |
❌ | - | 기본값 (비제어 컴포넌트) |
name |
string |
❌ | - | 폼 필드명 |
placeholder |
string |
❌ | - | 플레이스홀더 |
disabled |
boolean |
❌ | false |
비활성화 |
required |
boolean |
❌ | false |
필수 입력 |
readOnly |
boolean |
❌ | false |
읽기 전용 |
className |
string |
❌ | - | CSS 클래스 |
min |
string | number |
❌ | - | 최소값 (number, date) |
max |
string | number |
❌ | - | 최대값 (number, date) |
step |
string | number |
❌ | - | 증가 단위 (number) |
maxLength |
number |
❌ | - | 최대 길이 |
pattern |
string |
❌ | - | 정규식 패턴 |
IME 처리 (한글/CJK 입력)
Input 컴포넌트는 한글 등 IME 조합 입력을 올바르게 처리합니다:
- IME 조합 중에는 외부
onChange를 호출하지 않음 (내부localValue로 즉시 시각적 피드백 제공) - 조합 완료 후 최종 값으로
onChange호출 - 조합 중에는
onKeyPress이벤트도 발생하지 않음
디바운스와 IME 조합 상호작용:
디바운스가 설정된 Input에서 한글 입력 시:
- 조합 중에는 디바운스 타이머가 시작되지 않음
- 조합 완료 시점에 onChange 호출 → 디바운스 타이머 시작
- 빠른 타이핑 시 조합 완료 + 디바운스 지연이 합산됨
✅ localValue 패턴:
- Input은 내부 localValue 상태로 즉시 화면에 반영 (시각적 지연 없음)
- 외부 상태(_local)와 내부 상태(localValue)가 일시적으로 불일치 가능
- 디바운스 완료 시 외부 상태와 동기화됨
레이아웃 JSON 사용 예시
{
"type": "basic",
"name": "Input",
"props": {
"type": "text",
"name": "searchKeyword",
"placeholder": "$t:common.search_placeholder",
"className": "input w-full",
"value": "{{_local.searchKeyword}}"
},
"actions": [
{
"type": "change",
"handler": "setState",
"params": {
"target": "_local",
"searchKeyword": "{{$event.target.value}}"
}
},
{
"type": "keypress",
"key": "Enter",
"handler": "refetchDataSource",
"params": {
"dataSourceId": "products"
}
}
]
}
Button 컴포넌트
타입: basic | 템플릿: sirsoft-admin_basic, sirsoft-basic
파일: templates/*/src/components/basic/Button.tsx
Props 인터페이스
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'danger' | 'success' | 'outline' | 'ghost';
size?: 'sm' | 'md' | 'lg';
loading?: boolean;
icon?: string;
}
지원 Props 요약
| Props | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
type |
"button" | "submit" | "reset" |
❌ | "button" |
버튼 타입 |
disabled |
boolean |
❌ | false |
비활성화 |
className |
string |
❌ | - | CSS 클래스 |
variant |
string |
❌ | "primary" |
버튼 스타일 |
size |
string |
❌ | "md" |
버튼 크기 |
loading |
boolean |
❌ | false |
로딩 상태 |
레이아웃 JSON 사용 예시
{
"type": "basic",
"name": "Button",
"props": {
"type": "button",
"className": "btn btn-primary"
},
"text": "$t:common.search",
"actions": [
{
"type": "click",
"handler": "refetchDataSource",
"params": {
"dataSourceId": "products"
}
}
]
}
DataGrid 컴포넌트
타입: composite | 템플릿: sirsoft-admin_basic
파일: templates/sirsoft-admin_basic/src/components/composite/DataGrid.tsx
Props 인터페이스
export interface DataGridColumn {
field: string; // 필드명 (필수)
header: string; // 컬럼 헤더 텍스트 (필수)
width?: string; // 컬럼 너비 (예: "100px", "auto")
sortable?: boolean; // 정렬 가능 여부
template?: ComponentDefinition; // 셀 커스텀 템플릿
}
export interface DataGridProps {
data: any[]; // 데이터 배열 (필수)
columns: DataGridColumn[]; // 컬럼 정의 (필수)
rowKey?: string; // 행 고유 키 필드 (기본: 'id')
selectable?: boolean; // 행 선택 기능
selectedRows?: any[]; // 선택된 행 배열
selectionScope?: 'page' | 'free'; // 선택의 유효 범위 (아래 "선택의 유효 범위" 참조)
loading?: boolean; // 로딩 상태
className?: string; // CSS 클래스
// SubRow Props (engine-v1.6.0+)
subRowChildren?: ComponentDefinition[]; // 서브 행 컴포넌트 배열
subRowCondition?: string; // 서브 행 표시 조건 표현식
subRowClassName?: string; // 서브 행 CSS 클래스
// Footer Row Props
footerCells?: DataGridFooterCell[]; // 합계 행 셀 정의 (field별 매핑)
footerClassName?: string; // 합계 행 CSS 클래스
footerCardChildren?: ComponentDefinition[]; // 모바일 카드 뷰 합계 커스텀
// Responsive Props
responsiveBreakpoint?: number; // 반응형 전환점 (기본: 768)
}
지원 Props 요약
| Props | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
data |
any[] |
✅ | - | 테이블에 표시할 데이터 배열 |
columns |
DataGridColumn[] |
✅ | - | 컬럼 정의 배열 |
rowKey |
string |
❌ | 'id' |
행 고유 키로 사용할 필드명 |
selectable |
boolean |
❌ | false |
체크박스 선택 기능 활성화 |
selectedRows |
any[] |
❌ | [] |
선택된 행 배열 (ID 배열) |
selectionScope |
'page' | 'free' |
❌ | 'free' |
선택의 유효 범위 (아래 참조) |
loading |
boolean |
❌ | false |
로딩 상태 표시 |
className |
string |
❌ | - | CSS 클래스 |
선택의 유효 범위 (selectionScope)
체크박스 선택은 컴포넌트 밖(전역/로컬 상태)에 저장된다. 그래서 검색·필터·페이지 이동으로 행이 목록에서 빠져도 그 행의 선택은 남는다. 그 상태에서 일괄 처리를 실행하면 사용자가 보고 있지도, 체크하지도 않은 행이 대상이 된다 — 확인 모달은 건수만 말하므로 실행 전에 알아챌 방법이 없고, 처리는 정상 성공하므로 오류도 남지 않는다.
onSelectionChange 를 배선하는 DataGrid 는 둘 중 하나를 반드시 명시한다 (기본값에 기대지 않는다).
| 값 | 동작 | 쓰는 곳 |
|---|---|---|
"page" |
지금 그려진 행에 없는 선택을 버린다. 대상은 언제나 "보이고 체크된 행" | 일괄 처리 버튼이 달린 목록 화면 |
"free" |
선택을 컴포넌트가 건드리지 않는다 | 선택 자체가 저장 대상인 폼(적용 대상 고르기 등) — 여러 페이지에 걸쳐 고르는 선택기 포함 |
{
"type": "composite",
"name": "DataGrid",
"props": {
"selectable": true,
"selectionScope": "page",
"selectedIds": "{{_global.selectedIds_admin_users || []}}"
},
"actions": [
{
"event": "onSelectionChange",
"type": "change",
"handler": "setState",
"params": { "target": "global", "selectedIds_admin_users": "{{$args[0]}}" }
}
]
}
값은 리터럴로 고정한다. 표현식으로 분기하면 분기마다 보존 여부가 갈려 한쪽이 조용히 대상 밖 행을 싣는다. selectable 이 꺼진 화면이라고 안전한 것도 아니다 — 체크박스가 없으면 남은 선택이 더 안 보이므로, 범위 판정은 selectable 과 무관하게 적용된다.
SubRow Props (engine-v1.6.0+)
행 아래에 추가 정보를 표시하는 서브 행 기능입니다.
| Props | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
subRowChildren |
ComponentDefinition[] |
❌ | - | 서브 행에 렌더링할 컴포넌트 배열 |
subRowCondition |
string |
❌ | - | 서브 행 표시 조건 (예: "{{row.status !== 'free'}}") |
subRowClassName |
string |
❌ | - | 서브 행 CSS 클래스 |
subRowCondition 컨텍스트 변수:
row: 현재 행 데이터 객체$index: 현재 행 인덱스
Footer Row Props
테이블 하단에 합계 행을 표시하는 기능입니다.
| Props | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
footerCells |
DataGridFooterCell[] |
❌ | - | 합계 행 셀 정의 (컬럼 field별 매핑) |
footerClassName |
string |
❌ | - | 합계 행 CSS 클래스 |
footerCardChildren |
ComponentDefinition[] |
❌ | - | 모바일 카드 뷰 합계 커스텀 렌더링 |
FooterCell 구조:
field: 매핑할 컬럼 field (해당 컬럼 위치에 렌더링)children: 셀 내부 컴포넌트 정의 (condition속성으로 조건부 표시 가능)
레이아웃 JSON 사용 예시
기본 사용
{
"type": "composite",
"name": "DataGrid",
"props": {
"data": "{{products?.data?.data || []}}",
"rowKey": "id",
"selectable": true,
"selectedRows": "{{_local.selectedItems || []}}",
"loading": "{{products?.loading}}",
"columns": [
{
"field": "name",
"header": "$t:product.table.name",
"sortable": true,
"width": "200px"
},
{
"field": "price",
"header": "$t:product.table.price",
"sortable": true,
"width": "100px"
}
]
},
"actions": [
{
"event": "onSelectionChange",
"type": "change",
"handler": "setState",
"params": {
"target": "_local",
"selectedItems": "{{$args[0]}}"
}
}
]
}
SubRow 사용 (배송비 요약 표시)
{
"type": "composite",
"name": "DataGrid",
"props": {
"data": "{{shipping_policies?.data?.data || []}}",
"rowKey": "id",
"subRowCondition": "{{row.charge_policy !== 'free'}}",
"subRowClassName": "px-6 py-2 bg-gray-50 dark:bg-gray-800/50 text-sm text-gray-600 dark:text-gray-400",
"columns": [...],
"subRowChildren": [
{
"type": "layout",
"name": "Div",
"props": {
"className": "flex items-center gap-2"
},
"children": [
{
"type": "basic",
"name": "Icon",
"props": {
"name": "truck",
"className": "w-4 h-4 text-gray-400"
}
},
{
"type": "basic",
"name": "Span",
"props": {
"className": "text-gray-500 dark:text-gray-400"
},
"text": "$t:module::admin/entity.fee_summary.label"
},
{
"type": "basic",
"name": "Span",
"props": {
"className": "font-medium text-gray-700 dark:text-gray-300"
},
"text": "{{row.fee_summary}}"
}
]
}
]
}
}
컬럼 템플릿 사용
{
"columns": [
{
"field": "status",
"header": "$t:common.status",
"width": "100px",
"template": {
"type": "basic",
"name": "Span",
"props": {
"className": "{{$row.status === 'active' ? 'text-green-600' : 'text-gray-400'}}"
},
"text": "{{$row.status_label}}"
}
}
]
}
컬럼 템플릿 컨텍스트 변수:
$row: 현재 행 데이터 객체$index: 현재 행 인덱스$value: 현재 셀 값 (field에 해당하는 값)
주의사항
subRowChildren vs subRowCondition
- subRowChildren만 설정: 모든 행에 서브 행 표시
- subRowCondition 함께 설정: 조건 만족하는 행만 서브 행 표시
컨텍스트 변수 차이
- columns.template 내부: $row, $index, $value 사용
- subRowChildren 내부: row, $index 사용 ($ 없이 row)
✅ 다크 모드: subRowClassName에 dark: 클래스 쌍으로 지정
cellChildren — 셀 내 커스텀 컴포넌트 (engine-v1.8.0+)
컬럼 template 대신 셀 단위로 커스텀 컴포넌트를 렌더링합니다. 조건부 렌더링과 다중 컴포넌트 배치를 지원합니다.
interface DataGridCellChild {
column: string; // 대상 컬럼 field명 (필수)
condition?: string; // 표시 조건 (예: "{{row.status === 'active'}}")
children: ComponentDefinition[]; // 셀에 렌더링할 컴포넌트 배열
}
| Props | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
cellChildren |
DataGridCellChild[] |
❌ | - | 셀 내 커스텀 컴포넌트 배열 |
cellChildren 컨텍스트 변수:
row: 현재 행 데이터 객체value/$value: 현재 셀 값 (column.field에 해당)$index: 현재 행 인덱스
{
"cellChildren": [
{
"column": "actions",
"children": [
{
"type": "basic",
"name": "Button",
"props": {
"type": "button",
"className": "btn btn-sm btn-outline"
},
"text": "$t:common.edit",
"actions": [
{
"type": "click",
"handler": "navigate",
"params": { "path": "/admin/products/{{row.id}}/edit" }
}
]
}
]
},
{
"column": "status",
"condition": "{{row.status === 'draft'}}",
"children": [
{
"type": "basic",
"name": "Span",
"props": { "className": "text-yellow-600 dark:text-yellow-400" },
"text": "$t:common.draft"
}
]
}
]
}
expandChildren — 확장 가능한 행 (engine-v1.10.0+)
행을 클릭하여 추가 콘텐츠를 펼치는 확장 행 기능입니다.
| Props | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
expandable |
boolean |
❌ | false |
확장 기능 활성화 |
expandedRowIds |
any[] |
❌ | [] |
현재 펼쳐진 행 ID 배열 |
expandChildren |
ComponentDefinition[] |
❌ | - | 확장 영역에 렌더링할 컴포넌트 배열 |
expandContext |
object |
❌ | - | 확장 영역에 추가 전달할 컨텍스트 |
expandChildren 컨텍스트 변수:
__componentContext: 확장 행에 자동 전달되는 컨텍스트 (현재 행 데이터 포함)row: 현재 행 데이터 객체- iteration 지원: expandChildren 내에서
iteration속성으로 하위 데이터 반복 렌더링 가능
{
"type": "composite",
"name": "DataGrid",
"props": {
"data": "{{orders?.data?.data || []}}",
"rowKey": "id",
"expandable": true,
"expandedRowIds": "{{_local.expandedRows ?? []}}",
"columns": [...],
"expandChildren": [
{
"type": "layout",
"name": "Div",
"props": { "className": "p-4 bg-gray-50 dark:bg-gray-800" },
"children": [
{
"type": "basic",
"name": "H3",
"props": { "className": "text-sm font-medium mb-2" },
"text": "$t:order.items"
},
{
"type": "basic",
"name": "Span",
"text": "{{row.item_count}} $t:common.items"
}
]
}
]
},
"actions": [
{
"event": "onExpandChange",
"type": "change",
"handler": "setState",
"params": {
"target": "_local",
"expandedRows": "{{$args[0]}}"
}
}
]
}
disabledField — 행별 액션 비활성화 (engine-v1.14.0+)
rowActions의 개별 버튼을 행 데이터 기반으로 비활성화합니다. 권한(abilities) 연동에 주로 사용됩니다.
| Props | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
disabledField |
string |
❌ | - | 행 데이터 내 비활성화 판단 경로 (dot notation). falsy 값이면 비활성화 |
{
"columns": [...],
"rowActions": [
{
"label": "$t:common.edit",
"handler": "navigate",
"params": { "path": "/admin/items/{{row.id}}/edit" },
"disabledField": "abilities.can_update"
},
{
"label": "$t:common.delete",
"handler": "apiCall",
"params": { "endpoint": "/api/admin/items/{{row.id}}", "method": "DELETE" },
"disabledField": "abilities.can_delete",
"confirm": "$t:common.confirm_delete"
}
]
}
disabledField 동작
- disabledField 경로의 값이 falsy (false, null, undefined, 0, '') → 버튼 비활성화
- truthy → 버튼 활성화
- 경로가 존재하지 않으면 undefined → 비활성화
✅ abilities 패턴: API Resource에서 abilities 객체 반환 → disabledField로 참조
HtmlEditor 컴포넌트
타입: composite | 템플릿: sirsoft-admin_basic, sirsoft-basic
파일: templates/sirsoft-admin_basic/src/components/composite/HtmlEditor.tsx
Props 인터페이스
export interface HtmlEditorProps {
content?: string; // 콘텐츠 값
onChange?: (event: { target: { name: string; value: string } }) => void;
isHtml?: boolean; // HTML 모드 여부
onIsHtmlChange?: (event: { target: { name: string; checked: boolean } }) => void;
rows?: number; // Textarea 행 수 (기본: 15)
placeholder?: string;
label?: string;
showHtmlModeToggle?: boolean; // HTML 모드 체크박스 표시 (기본: true)
contentClassName?: string; // HtmlContent 미리보기 클래스
purifyConfig?: any; // DOMPurify 설정
className?: string;
name?: string; // 콘텐츠 필드명 (기본: 'content')
htmlFieldName?: string; // HTML 모드 필드명 (기본: 'content_mode')
readOnly?: boolean;
}
지원 Props 요약
| Props | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
content |
string |
❌ | '' |
콘텐츠 값 |
onChange |
function |
❌ | - | 콘텐츠 변경 콜백 |
isHtml |
boolean |
❌ | false |
HTML 모드 여부 |
onIsHtmlChange |
function |
❌ | - | HTML 모드 변경 콜백 |
rows |
number |
❌ | 15 |
Textarea 행 수 |
placeholder |
string |
❌ | '' |
플레이스홀더 |
label |
string |
❌ | - | 라벨 텍스트 |
showHtmlModeToggle |
boolean |
❌ | true |
HTML 모드 체크박스 표시 |
name |
string |
❌ | 'content' |
콘텐츠 필드명 |
htmlFieldName |
string |
❌ | 'content_mode' |
HTML 모드 필드명 |
readOnly |
boolean |
❌ | false |
읽기 전용 |
레이아웃 JSON 사용 예시
{
"type": "composite",
"name": "HtmlEditor",
"props": {
"content": "{{_local.form?.content ?? ''}}",
"isHtml": "{{(_local.form?.content_mode ?? 'text') === 'html'}}",
"rows": 15,
"placeholder": "$t:board.form.content_placeholder",
"label": "$t:board.form.content",
"name": "content",
"htmlFieldName": "content_mode"
},
"actions": [
{
"event": "onChange",
"type": "change",
"handler": "setState",
"params": {
"target": "local",
"form": { "content": "{{$args[0].target.value}}" }
}
},
{
"event": "onIsHtmlChange",
"type": "change",
"handler": "setState",
"params": {
"target": "local",
"form": { "content_mode": "{{$args[0].target.checked ? 'html' : 'text'}}" }
}
}
]
}
CodeEditor 컴포넌트
타입: composite | 템플릿: sirsoft-admin_basic
파일: templates/sirsoft-admin_basic/src/components/composite/CodeEditor.tsx
Props 인터페이스
export interface CodeEditorProps {
value: string; // 에디터 값 (필수)
onChange?: (value: string) => void; // 값 변경 콜백
language?: string; // 언어 (기본: 'json')
height?: string; // 높이 (기본: '100%')
readOnly?: boolean;
theme?: 'vs-dark' | 'vs-light'; // 테마 (기본: 'vs-dark')
}
지원 Props 요약
| Props | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
value |
string |
✅ | - | 에디터 값 |
onChange |
function |
❌ | - | 값 변경 콜백 |
language |
string |
❌ | 'json' |
언어 (json, javascript, html, css 등) |
height |
string |
❌ | '100%' |
에디터 높이 |
readOnly |
boolean |
❌ | false |
읽기 전용 |
theme |
string |
❌ | 'vs-dark' |
에디터 테마 |
레이아웃 JSON 사용 예시
{
"type": "composite",
"name": "CodeEditor",
"props": {
"value": "{{_local.jsonContent ?? '{}'}}",
"language": "json",
"height": "400px",
"theme": "vs-dark"
},
"actions": [
{
"event": "onChange",
"type": "change",
"handler": "setState",
"params": {
"target": "local",
"jsonContent": "{{$args[0]}}"
}
}
]
}
주의사항
HtmlEditor vs CodeEditor onChange 차이
- HtmlEditor: { target: { name, value } } 이벤트 객체 전달
- CodeEditor: 문자열 직접 전달 ($args[0]가 문자열)
Modal 컴포넌트
타입: composite | 템플릿: sirsoft-admin_basic, sirsoft-basic
파일: templates/*/src/components/composite/Modal.tsx
Props 인터페이스
export interface ModalProps {
isOpen: boolean; // 모달 열림 상태 (템플릿 엔진이 자동 주입)
onClose: () => void; // 모달 닫기 콜백 (템플릿 엔진이 자동 주입)
title?: string; // 모달 타이틀
icon?: string; // 타이틀 옆 아이콘 (sirsoft-basic만)
iconClassName?: string; // 아이콘 색상 클래스 (sirsoft-basic만)
closeOnBackdropClick?: boolean; // 배경 클릭 시 닫기 (기본: true)
closeOnOverlayClick?: boolean; // closeOnBackdropClick 별칭 (레이아웃 JSON 호환)
showCloseButton?: boolean; // 닫기 버튼 표시 (기본: true)
closeOnEscape?: boolean; // ESC 키로 닫기 (기본: true)
width?: string; // 모달 너비 (기본: '500px')
className?: string; // CSS 클래스
style?: React.CSSProperties; // 인라인 스타일
children?: React.ReactNode; // 모달 내용
}
지원 Props 요약
| Props | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
isOpen |
boolean |
✅ | - | 모달 열림 상태 (템플릿 엔진 자동 주입) |
onClose |
function |
✅ | - | 모달 닫기 콜백 (템플릿 엔진 자동 주입) |
title |
string |
❌ | - | 모달 타이틀 |
icon |
string |
❌ | - | 타이틀 옆 아이콘 (sirsoft-basic) |
iconClassName |
string |
❌ | 'text-gray-500' |
아이콘 색상 클래스 |
closeOnBackdropClick |
boolean |
❌ | true |
배경 클릭 시 닫기 |
closeOnOverlayClick |
boolean |
❌ | true |
closeOnBackdropClick 별칭 |
showCloseButton |
boolean |
❌ | true |
헤더 닫기 버튼 표시 |
closeOnEscape |
boolean |
❌ | true |
ESC 키로 닫기 허용 |
width |
string |
❌ | '500px' |
모달 너비 |
className |
string |
❌ | - | CSS 클래스 |
회피 불가 모달 설정
사용자가 닫기 버튼, 배경 클릭, ESC 키로 모달을 회피할 수 없도록 설정:
{
"type": "composite",
"name": "Modal",
"props": {
"title": "$t:common.error",
"showCloseButton": false,
"closeOnOverlayClick": false,
"closeOnEscape": false
},
"children": [...]
}
레이아웃 JSON 사용 예시
modals 섹션에 정의 (권장)
{
"modals": [
{
"id": "confirm_modal",
"type": "composite",
"name": "Modal",
"props": {
"title": "$t:common.confirm",
"width": "400px"
},
"children": [
{
"type": "basic",
"name": "P",
"text": "$t:common.confirm_message"
},
{
"type": "basic",
"name": "Button",
"props": { "type": "button" },
"text": "$t:common.ok",
"actions": [
{
"type": "click",
"handler": "closeModal"
}
]
}
]
}
]
}
모달 열기/닫기
// 모달 열기
{
"handler": "openModal",
"target": "confirm_modal"
}
// 모달 닫기
{
"handler": "closeModal"
}
주의사항
isOpen/onClose는 템플릿 엔진이 자동 주입 - 직접 설정 불필요
modals 섹션은 반드시 배열 형식 (객체 형식 사용 시 렌더링 안 됨)
각 모달에 고유한 id 필수
✅ 회피 불가 모달: showCloseButton, closeOnOverlayClick, closeOnEscape 모두 false
LoadingSpinner 컴포넌트
타입: composite | 템플릿: sirsoft-admin_basic
파일: templates/sirsoft-admin_basic/src/components/composite/LoadingSpinner.tsx
Props 인터페이스
export type SpinnerSize = 'sm' | 'md' | 'lg' | 'xl';
export interface LoadingSpinnerProps {
id?: string; // 요소 ID (engine-v1.11.0+)
size?: SpinnerSize; // 스피너 크기 (기본: 'md')
color?: string; // 색상 클래스 (기본: 'text-blue-600')
fullscreen?: boolean; // 전체 화면 모드 (기본: false)
text?: string; // 로딩 텍스트
className?: string; // CSS 클래스
}
지원 Props 요약
| Props | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
id |
string |
❌ | - | 요소 ID (DOM selector 사용 시 필요) |
size |
'sm' | 'md' | 'lg' | 'xl' |
❌ | 'md' |
스피너 크기 |
color |
string |
❌ | 'text-blue-600' |
Tailwind 색상 클래스 |
fullscreen |
boolean |
❌ | false |
전체 화면 모드 |
text |
string |
❌ | - | 로딩 텍스트 ($t: 다국어 지원) |
className |
string |
❌ | '' |
추가 CSS 클래스 |
size별 스피너 크기
| Size | 스피너 | 텍스트 |
|---|---|---|
sm |
w-4 h-4 border-2 |
text-xs |
md |
w-8 h-8 border-2 |
text-sm |
lg |
w-12 h-12 border-3 |
text-base |
xl |
w-16 h-16 border-4 |
text-lg |
레이아웃 JSON 사용 예시
기본 사용
{
"type": "composite",
"name": "LoadingSpinner",
"props": {
"size": "md",
"text": "$t:common.loading"
}
}
id 지정 (scrollIntoView 등 DOM selector 사용 시)
{
"id": "loading_indicator",
"type": "composite",
"name": "LoadingSpinner",
"if": "{{_global.infiniteScroll.isLoadingMore}}",
"props": {
"size": "sm",
"text": "$t:common.loading",
"color": "text-blue-600",
"className": "py-3"
}
}
렌더링 결과:
<div id="loading_indicator" class="flex items-center justify-center p-4" aria-live="polite" aria-busy="true">
<div class="flex flex-col items-center justify-center gap-3 py-3">
<div class="w-4 h-4 border-2 border-gray-200 dark:border-gray-700 border-t-current rounded-full animate-spin text-blue-600" role="status"></div>
<span class="text-xs text-blue-600 font-medium">로딩 중...</span>
</div>
</div>
전체 화면 모드
{
"type": "composite",
"name": "LoadingSpinner",
"if": "{{_global.isLoading}}",
"props": {
"fullscreen": true,
"size": "lg",
"text": "$t:common.loading"
}
}
주의사항
id prop 사용: scrollIntoView 등 DOM selector로 접근해야 할 때 필수 지정
✅ 다크 모드: 자동 지원 (border-gray-200 dark:border-gray-700)
✅ 접근성: role="status", aria-label, aria-live="polite" 자동 설정
관련 문서
- 컴포넌트 개발 규칙 - basic, composite, layout 컴포넌트
- 레이아웃 JSON 스키마 - 컴포넌트를 레이아웃 JSON에서 사용하는 방법
- sirsoft-admin_basic 컴포넌트 - Admin 컴포넌트 목록 (111개)
- sirsoft-basic 컴포넌트 - User 컴포넌트 목록 (58개)
- 에디터 컴포넌트 - HtmlEditor, CodeEditor 상세 가이드
- 데이터 바인딩 -
{{}}표현식,$t:다국어