EmDash의 json 필드 타입은 임의의 구조화된 데이터를 저장하지만, 기본 에디터는 손으로 원시 JSON을 입력해야 하는 단일 행 텍스트 입력입니다. Field Kit은 seed options를 통해 완전히 구성되는 json 필드용 4개의 조합 가능한 위젯을 제공하는 퍼스트 파티 플러그인입니다. 사이트 빌더에게 React가 필요하지 않습니다.
설치
npm i @emdash-cms/plugin-field-kit
astro.config.mjs에서 플러그인을 등록합니다:
import { defineConfig } from "astro/config";
import emdash from "emdash";
import { fieldKitPlugin } from "@emdash-cms/plugin-field-kit";
export default defineConfig({
integrations: [
emdash({
plugins: [fieldKitPlugin()],
}),
],
});
그런 다음 widget을 field-kit:<name>으로 설정하여 모든 json 필드에 위젯을 첨부합니다:
{
"slug": "ingredients",
"type": "json",
"widget": "field-kit:list",
"options": { "fields": [...] }
}
위젯
| 위젯 | 용도 | 저장되는 값 |
|---|---|---|
object-form | 플랫 JSON 객체용 인라인 폼 | { key: value, ... } |
list | 추가 / 제거 / 재정렬 기능이 있는 정렬된 배열 에디터 | [{ ... }, ...] |
grid | 행 × 열 매트릭스 | { rowKey: { colKey: value } } |
tags | 자유 형식 칩/태그 입력 | ["tag1", "tag2"] |
위젯에 필요한 options가 누락된 경우(예: object-form/list의 fields 또는 grid의 rows/columns), 에디터는 깨진 입력 대신 인라인 “위젯이 잘못 구성됨” 경고를 렌더링합니다. 이는 seed 스키마를 반복하는 동안 유용합니다.
object-form
단일 JSON 객체로 저장되는 타입이 지정된 하위 필드 그룹을 렌더링합니다. 영양 정보나 연락처 정보와 같은 고정된 형태의 구조화된 데이터에 적합합니다.
{
"slug": "nutrition",
"type": "json",
"widget": "field-kit:object-form",
"options": {
"collapsed": false,
"fields": [
{ "key": "calories", "label": "Calories", "type": "number", "suffix": "kcal" },
{ "key": "protein", "label": "Protein", "type": "number", "suffix": "g" },
{ "key": "fat", "label": "Fat", "type": "number", "suffix": "g" },
{ "key": "carbs", "label": "Carbs", "type": "number", "suffix": "g" }
]
}
}
저장되는 값: { "calories": 250, "protein": 12.5, "fat": 8, "carbs": 30 }.
| 옵션 | 타입 | 기본값 | 설명 |
|---|---|---|---|
fields | SubFieldDef[] | (필수) | 하위 필드 정의 — 하위 필드 참조. |
collapsed | boolean | false | 기본적으로 그룹을 접힌 상태로 렌더링합니다. |
helpText | string | — | 위젯 아래에 표시되는 도움말 텍스트. |
list
추가, 제거 및 재정렬 컨트롤이 있는 정렬된 배열 에디터입니다. 각 행은 fields로 정의된 형태의 JSON 객체입니다. 행 헤더는 Mustache 스타일 템플릿에서 렌더링된 요약을 표시합니다.
{
"slug": "ingredients",
"type": "json",
"widget": "field-kit:list",
"options": {
"itemLabel": "Ingredient",
"min": 1,
"max": 50,
"sortable": true,
"summary": "{{name}} — {{amount}}",
"fields": [
{ "key": "name", "label": "Name", "type": "text", "required": true },
{ "key": "amount", "label": "Amount", "type": "text" },
{ "key": "optional", "label": "Optional", "type": "boolean" }
]
}
}
저장되는 값:
[
{ "name": "Flour", "amount": "500g", "optional": false },
{ "name": "Butter", "amount": "200g", "optional": false }
]
| 옵션 | 타입 | 기본값 | 설명 |
|---|---|---|---|
fields | SubFieldDef[] | (필수) | 각 행에 대한 하위 필드 정의. |
itemLabel | string | "Item" | 행의 단수형 레이블(“추가” 버튼 및 폴백 행 제목에 사용됨). |
min | number | — | 최소 항목 수. 이보다 적으면 제거 버튼이 숨겨집니다. |
max | number | — | 최대 항목 수. 이 개수에서 추가 버튼이 숨겨집니다. |
sortable | boolean | true | 위/아래 재정렬 버튼을 표시합니다. |
summary | string | — | 접힌 행 제목으로 렌더링되는 Mustache 템플릿. 요약 템플릿 참조. |
helpText | string | — | 위젯 아래에 표시되는 도움말 텍스트. |
grid
행 × 열의 2차원 매트릭스입니다. 각 셀은 토글, 텍스트 입력, 숫자 입력 또는 선택일 수 있습니다. 계절별 가용성, 가격표 또는 기능 비교와 같은 매트릭스에 유용합니다.
{
"slug": "availability",
"type": "json",
"widget": "field-kit:grid",
"options": {
"cell": "toggle",
"rows": [
{ "key": "berries", "label": "Berries" },
{ "key": "stoneFruit", "label": "Stone fruit" },
{ "key": "citrus", "label": "Citrus" }
],
"columns": [
{ "key": "spring", "label": "Spring" },
{ "key": "summer", "label": "Summer" },
{ "key": "autumn", "label": "Autumn" },
{ "key": "winter", "label": "Winter" }
]
}
}
저장되는 값:
{
"berries": { "spring": false, "summer": true, "autumn": false, "winter": false },
"stoneFruit": { "spring": false, "summer": true, "autumn": true, "winter": false },
"citrus": { "spring": false, "summer": false, "autumn": true, "winter": true }
}
| 옵션 | 타입 | 기본값 | 설명 |
|---|---|---|---|
rows | GridAxisDef[] | (필수) | 행 정의: { key, label, image? }. |
columns | GridAxisDef[] | (필수) | 열 정의: { key, label, image? }. |
cell | "toggle" | "text" | "number" | "select" | "toggle" | 모든 셀에 균일하게 적용되는 셀 입력 타입. |
cellOptions | string[] | Array<{ label, value }> | [] | cell이 "select"일 때 필수. |
helpText | string | — | 위젯 아래에 표시되는 도움말 텍스트. |
tags
문자열 배열용 칩 스타일 입력입니다. 고정된 suggestions 목록, 자유 형식 사용자 정의 값(토글 가능), 대소문자 변환 및 선택적 max를 지원합니다.
{
"slug": "keywords",
"type": "json",
"widget": "field-kit:tags",
"options": {
"placeholder": "Add a keyword…",
"max": 10,
"transform": "lowercase",
"allowCustom": true,
"suggestions": ["vegan", "vegetarian", "gluten-free", "dairy-free", "nut-free"]
}
}
저장되는 값: ["vegan", "gluten-free"].
Enter 또는 ,를 눌러 태그를 커밋합니다. 빈 입력에서 Backspace를 누르면 마지막 태그가 제거됩니다. 중복 태그는 자동으로 무시됩니다.
| 옵션 | 타입 | 기본값 | 설명 |
|---|---|---|---|
placeholder | string | "Add..." | 태그가 없을 때 표시되는 입력 플레이스홀더. |
max | number | — | 최대 태그 수. 제한에 도달하면 입력이 숨겨집니다. |
suggestions | string[] | [] | <datalist>를 통해 표시되는 자동 완성 제안. |
allowCustom | boolean | true | false일 때 suggestions의 값만 추가할 수 있습니다. |
transform | "none" | "lowercase" | "uppercase" | "trim" | "none" | 추가될 때 태그를 정규화합니다. |
helpText | string | — | 위젯 아래에 표시되는 도움말 텍스트. |
하위 필드
object-form과 list는 타입이 지정된 하위 필드 정의의 options.fields 배열을 허용합니다. 각 항목에는 key(작성할 JSON 객체 키), label, type 및 타입별 추가 사항이 있습니다.
| 하위 필드 타입 | 렌더링 형식 | 주목할 추가 사항 |
|---|---|---|
text | 단일 행 입력 | placeholder |
textarea | 여러 행 입력 | rows(기본값 3), placeholder |
number | 숫자 입력 | min, max, step, prefix, suffix, placeholder |
boolean | 토글 스위치 | — |
select | 드롭다운 | options: string[] | Array<{ label, value }>, placeholder |
date | 날짜 입력 | — |
color | 16진수 텍스트 입력과 쌍을 이룬 네이티브 컬러 피커 | — |
url | URL 입력(HTML5 type="url") | placeholder |
모든 하위 필드의 공통 속성: required, helpText, defaultValue.
요약 템플릿
list 위젯은 options.summary의 Mustache 스타일 템플릿을 사용하여 접힌 각 행을 렌더링합니다. {{key}}는 해당 키에 대한 행의 값(문자열로 강제 변환)으로 대체됩니다. falsy 값은 "{itemLabel} {n}"으로 폴백됩니다.
"summary": "{{name}} — {{amount}}"
Flour — 500g와 같은 행을 렌더링합니다. 템플릿은 단순한 문자열 대체입니다. HTML 없음, 중첩된 표현식 없음.
데이터 내구성
Field Kit 위젯은 필드의 기존 열에 일반 JSON을 저장합니다. 플러그인별 테이블, 외래 키, 스키마 변경이 없습니다. 구성에서 @emdash-cms/plugin-field-kit을 제거하면 데이터는 유효한 상태로 유지됩니다. 편집 UI만 기본 json 텍스트 입력으로 돌아갑니다.
이는 위젯 형태를 변경할 때도 적용됩니다: 저장된 객체의 알 수 없는 키는 다음 쓰기 시 보존되므로 이전 필드 세트에서 캡처한 데이터를 잃지 않고 스키마를 발전시킬 수 있습니다.
참조
- 플러그인 개요 — EmDash 플러그인 작동 방식.
- 플러그인 형식 선택 — Field Kit이 맞지 않으면 자체 필드 위젯을 작성하세요.
- Discussion #571 — 이 플러그인으로 이어진 제안.