본문으로 건너뛰기
Unveilydocs

내비게이션

웹에서 하단 탭 바, 사이드 드로어, 상단 드롭다운 메뉴, 하단 시트를 제어합니다.

한눈에 보기

네비게이션 Bridge를 사용하면 앱 내 모든 네이티브 패널을 웹 페이지에서 직접 제어할 수 있습니다.

컴포넌트Bridge 네임스페이스config.json 키
Bottom TabsunveilyBridge.bottomTabsmodules.bottomTabs.enabled
Side DrawerunveilyBridge.sideDrawermodules.sideDrawer.enabled
Top Down MenuunveilyBridge.topDownMenumodules.topDownMenu.enabled
Bottom SheetunveilyBridge.bottomSheetmodules.bottomSheet.enabled

패널 상호 배타성

Bottom Sheet, Top Down Menu, Side Drawer는 동시에 하나만 열립니다. 새 패널을 열면 열려 있던 다른 패널은 자동으로 닫힙니다.


타이밍 — unveilyGlueReady 대기

window.unveilyBridge는 페이지 로드 전에 등록되지만, 모듈 네임스페이스(bottomTabs, sideDrawer 등)는 네이티브 쪽의 onPageFinished 이후에 글루 스크립트가 주입되어야 생성됩니다.

React, Vue 등 SPA에서 useEffect 또는 onMounted는 글루 주입 이전에 실행될 수 있어, setConfig() 같은 호출이 조용히 무시될 수 있습니다.

마운트 시점에 브릿지를 호출하는 경우, 반드시 unveilyGlueReady 이벤트를 기다린 후 호출하세요:

window.addEventListener('unveilyGlueReady', () => {
  window.unveilyBridge.bottomTabs.setConfig({ items: [...] });
  window.unveilyBridge.sideDrawer.setConfig({ items: [...] });
}, { once: true });

버튼 클릭 등 사용자 동작으로 호출하는 경우에는 이 처리가 필요 없습니다 — 사용자가 조작하는 시점에는 글루가 항상 준비되어 있습니다.


모듈 활성화

배포되는 config.json모든 UI 모듈이 비활성(opt-in) 상태로 제공됩니다. 앱에서 실제로 쓰는 패널만 켜세요.

필요한 것만 켜기

요청하지 않은 탭·드로어·제스처가 앱에 주입되지 않도록, 그리고 불필요한 권한·스토어 심사 이슈를 피하도록 모듈은 기본적으로 꺼져 있습니다. 사용할 컴포넌트만 "enabled": true로 바꾸세요.

assets/config.json
{
  "modules": {
    "bottomTabs":  { "enabled": true, "autoHide": false, "heightDp": 60 },
    "sideDrawer":  { "enabled": true },
    "topDownMenu": { "enabled": true },
    "bottomSheet": { "enabled": true }
  }
}

빠르게 둘러보려면 모든 패널이 켜진 데모 설정 config.sample.json이 함께 제공됩니다. config.json을 백업한 뒤 교체해 보고, 끝나면 되돌리세요. (Android: app/src/main/assets/, iOS: Resources/)

cp config.json config.json.bak && cp config.sample.json config.json

로컬 config로 티어(플랜)를 판단하지 마세요

플랜 게이팅은 원격/라이선스 경로에서만 적용됩니다. 로컬 config.json은 이를 우회하므로, 로컬 데모에서는 실제 플랜에서 막히는 패널도 보일 수 있습니다. 프로덕션 가용성은 플랜 기준으로 확인하세요.


패널 색상 커스터마이징

각 모듈에 panelColor 옵션을 설정하면 앱 브랜드 컬러로 패널 배경색을 지정할 수 있습니다. config.json에 한 번만 설정하면 SDK 업데이트 후에도 유지됩니다.

단일 색상 (라이트/다크 공통 적용):

assets/config.json
{
  "modules": {
    "bottomTabs": {
      "enabled": true,
      "panelColor": "#CC1A1A2E"
    }
  }
}

라이트/다크 모드 분리 (시스템 테마에 따라 자동 전환):

assets/config.json
{
  "modules": {
    "bottomTabs": {
      "enabled": true,
      "panelColor": {
        "light": "#CCF0F0F5",
        "dark":  "#CC1A1A2E"
      }
    },
    "sideDrawer": {
      "enabled": true,
      "panelColor": {
        "light": "#CCFFFFFF",
        "dark":  "#CC2C2C2C"
      }
    },
    "topDownMenu": {
      "enabled": true,
      "panelColor": "#CC101020"
    },
    "bottomSheet": {
      "enabled": true,
      "panelColor": "#CC1C1C1E"
    }
  }
}

색상 형식: "#AARRGGBB" (알파 포함) 또는 "#RRGGBB" (불투명). panelColor를 생략하면 SDK 기본 배경이 사용됩니다.

우선순위

panelColor는 접근성 패널의 High Contrast 설정보다 우선 적용됩니다. High Contrast를 지원하려면 panelColor를 설정하지 않거나, 접근성 설정 변경 시 JS에서 동적으로 색상을 조정하세요.

아이콘 / 라벨 색상

iconColorlabelColor로 아이콘 틴트와 라벨 글자색을 별도 지정할 수 있습니다. Bottom Tabs, Top Down Menu, Side Drawer에서 지원하며, panelColor와 동일한 단일 색상 / {light, dark} 객체 형식을 사용합니다.

assets/config.json
{
  "modules": {
    "bottomTabs": {
      "enabled": true,
      "panelColor":  "#CC1A1A2E",
      "iconColor":   { "light": "#1A1A2E", "dark": "#FFFFFF" },
      "labelColor":  { "light": "#1A1A2E", "dark": "#FFFFFF" },
      "activeAlpha":   1.0,
      "inactiveAlpha": 0.5
    },
    "sideDrawer": {
      "enabled": true,
      "panelColor": "#CC2C2C2C",
      "iconColor":  "#FFFFFF",
      "labelColor": "#FFFFFF"
    },
    "topDownMenu": {
      "enabled": true,
      "panelColor": "#CC101020",
      "iconColor":  "#FFFFFF",
      "labelColor": "#CCCCCC"
    }
  }
}
옵션적용 대상기본값
iconColor아이콘 ImageView 틴트 (SRC_IN 방식)원본 이미지 색상
labelColor라벨 TextView 글자색#FFFFFF (흰색)
activeAlphaBottom Tabs 활성 탭 불투명도1.0
inactiveAlphaBottom Tabs 비활성 탭 불투명도0.6

아이콘 틴트

iconColor는 단색 아이콘(SVG, 단색 PNG)에 최적입니다. 멀티컬러 아이콘에 적용하면 단색으로 채워집니다.

활성 탭 인디케이터

activeIndicator로 현재 활성 탭에 시각적 마커를 추가할 수 있습니다. activeIndicatorColor를 지정하지 않으면 iconColor가 대신 사용됩니다.

"bottomTabs": {
  "enabled": true,
  "panelColor": "#E8101020",
  "iconColor":  { "light": "#1F2937", "dark": "#FFFFFF" },
  "activeIndicator":      "dot",
  "activeIndicatorColor": { "light": "#2563EB", "dark": "#60A5FA" }
}
옵션설명기본값
activeIndicator인디케이터 스타일 — "none" / "dot" / "pill" / "bar""none"
activeIndicatorColor인디케이터 색상 (panelColor와 동일한 단일/객체 형식)#6366F1 / #818CF8 (Indigo)
activeIndicatorWidth"bar" 너비 — "short" (20dp 중앙 정렬) / "full" (탭 전체 너비)"short"
activeIndicatorPosition"bar" 위치 — "top" (아이콘 위) / "bottom" (라벨 아래)"top"
  • dot — 라벨 아래 작은 원형 점
  • bar — 아이콘 위 짧은 가로 막대
  • pill — 아이콘과 라벨을 감싸는 둥근 직사각형 배경

pill 투명도

"pill" 스타일에서는 activeIndicatorColor에 알파 채널을 포함하세요 (예: "#332563EB" ≈ 20% 불투명도). 불투명한 색상을 사용하면 아이콘과 라벨이 가려집니다.


Bottom Tabs

화면 하단에 고정된 탭 바입니다.

탭바 높이 설정

config.jsonheightDp 옵션으로 탭 바 높이를 조정할 수 있습니다.

assets/config.json
{
  "modules": {
    "bottomTabs": {
      "enabled": true,
      "heightDp": 64
    }
  }
}
옵션타입기본값설명
heightDpnumber54탭 바 높이(dp). 44–96 범위로 자동 제한됩니다.

접근성 최솟값

44dp 미만으로 설정해도 44dp로 올림 처리됩니다 (WCAG 2.5.5 최소 터치 타겟 기준). 96dp를 초과하면 96dp로 내림 처리됩니다.

show / hide / toggle

window.unveilyBridge.bottomTabs.show();
window.unveilyBridge.bottomTabs.hide();
window.unveilyBridge.bottomTabs.toggle();

setConfig

window.unveilyBridge.bottomTabs.setConfig({
  visible: false,  // 선택 — 기본값: true. false 로 설정하면 최초 로드 시 숨긴 상태로 시작합니다.
  items: [
    { id: "home",   label: "홈",   icon: "home.svg",   url: "/home" },
    { id: "search", label: "검색", icon: "search.svg", url: "/search" },
    { id: "my",     label: "마이", icon: "person.svg", url: "/my" }
  ]
});

최초 setConfig() 호출 시 탭 바가 자동으로 표시됩니다. visible: false를 전달하면 숨긴 상태로 시작하며, 원하는 시점에 show()를 호출해 표시합니다. 이후 setConfig() 재호출은 표시 상태를 변경하지 않습니다.

아이콘: assets/menu_icons/에 아이콘 파일을 넣고 파일명으로 참조합니다. .svg와 래스터 포맷(.png, .webp 등)을 모두 지원합니다. 로컬 .svg 파일은 AndroidSVG로 렌더링되어 모든 해상도에서 선명하게 표시되며, 그 외 포맷은 Glide를 사용합니다.

setBadge

특정 탭에 알림 배지를 표시합니다. 0을 전달하면 제거됩니다.

window.unveilyBridge.bottomTabs.setBadge("home", 5);  // 배지 표시
window.unveilyBridge.bottomTabs.setBadge("home", 0);  // 배지 제거

콜백

function onBottomTabClick(result) {
  console.log(result.id);   // 탭 id
  console.log(result.url);  // 해당 탭에 설정된 url
}

Side Drawer

화면 측면에서 슬라이드해 들어오는 네비게이션 목록입니다.

open / close / toggle

window.unveilyBridge.sideDrawer.open();
window.unveilyBridge.sideDrawer.close();
window.unveilyBridge.sideDrawer.toggle();

setConfig

window.unveilyBridge.sideDrawer.setConfig({
  widthDp: 320,   // 선택 사항 — 드로어 너비(dp), 기본값: 320
  items: [
    { id: "profile",  label: { ko: "프로필",  en: "Profile",  ja: "プロフィール" }, icon: "person.png",   url: "/profile" },
    { id: "settings", label: { ko: "설정",    en: "Settings", ja: "設定"         }, icon: "settings.png", url: "/settings" },
    { id: "refresh",  type: "button", label: { ko: "새로고침", en: "Refresh", ja: "更新" } },
    { id: "lang",     type: "select", value: "ko", options: [
        { value: "ko", label: { ko: "한국어", en: "Korean",   ja: "韓国語" } },
        { value: "en", label: { ko: "영어",   en: "English",  ja: "英語"   } },
        { value: "ja", label: { ko: "일본어", en: "Japanese", ja: "日本語" } }
    ]}
  ]
});

setItemEnabled / setItemVisible

window.unveilyBridge.sideDrawer.setItemEnabled("settings", false);
window.unveilyBridge.sideDrawer.setItemVisible("settings", false);

setBadge

아이콘 타입 항목에 알림 배지를 표시합니다. 0을 전달하면 제거됩니다.

window.unveilyBridge.sideDrawer.setBadge("profile", 3);
window.unveilyBridge.sideDrawer.setBadge("profile", 0); // 제거

콜백

// 아이콘 타입 항목 탭 (하위 호환)
function onSideDrawerItemClick(result) {
  console.log(result.id);   // 항목 id
  console.log(result.url);  // 해당 항목의 url
}

// 버튼 또는 셀렉트 항목 조작
function onSideDrawerAction(result) {
  console.log(result.id);     // 항목 id
  console.log(result.type);   // "button" | "select"
  console.log(result.value);  // 버튼이면 ""; 셀렉트이면 선택된 값
}

Top Down Menu

화면 상단에서 아래로 슬라이드하는 그리드 메뉴입니다.

open / close / toggle

window.unveilyBridge.topDownMenu.open();
window.unveilyBridge.topDownMenu.close();
window.unveilyBridge.topDownMenu.toggle();

setConfig

window.unveilyBridge.topDownMenu.setConfig({
  columns: 4,              // 그리드 열 수 (1–5, 기본값: 4)
  maxHeightFraction: 0.5,  // 최대 높이 비율 — 상한 (0.1–1.0, 기본값: 0.5). 내용이 적으면 상한보다 작게 표시
  items: [
    { id: "home",    label: { ko: "홈",    en: "Home",    ja: "ホーム" }, icon: "home.png" },
    { id: "search",  label: { ko: "검색",  en: "Search",  ja: "検索"   }, icon: "search.png" },
    { id: "refresh", type: "button", label: { ko: "새로고침", en: "Refresh", ja: "更新" } },
    { id: "lang",    type: "select", value: "ko", options: [
        { value: "ko", label: { ko: "한국어", en: "Korean",   ja: "韓国語" } },
        { value: "en", label: { ko: "영어",   en: "English",  ja: "英語"   } },
        { value: "ja", label: { ko: "일본어", en: "Japanese", ja: "日本語" } }
    ]},
    { id: "banner",  type: "image",  icon: "promo.svg", heightDp: 80 }
  ]
});

열(column) 동작

columnstype:'icon' 그리드 셀에만 적용됩니다. button/select/toggle/image/header/separator 항목은 항상 전체 너비 행으로 표시됩니다. 또한 columns는 고정값이라 태블릿에서 열 수가 자동으로 늘지 않고 각 열이 가로로 넓어집니다. 태블릿에서 열을 더 두려면 app.isTablet()로 분기하세요.

var cols = window.unveilyBridge.app.isTablet() ? 5 : 3;
window.unveilyBridge.topDownMenu.setConfig({ columns: cols, items: [ /* ... */ ] });

setItemEnabled / setItemVisible

window.unveilyBridge.topDownMenu.setItemEnabled("search", false);
window.unveilyBridge.topDownMenu.setItemVisible("search", false);

닫기 제스처

  • 위로 스와이프: 메뉴가 열린 상태에서 빠르게 위로 밀면 닫힙니다.
  • 뒤로 가기 버튼: 앱이 종료되지 않고 메뉴를 먼저 닫습니다.

콜백

// 아이콘 타입 항목 탭 (하위 호환)
function onTopDownMenuItemClick(result) {
  console.log(result.id);   // 항목 id
  console.log(result.url);  // 해당 항목의 url
}

// 버튼 또는 셀렉트 항목 조작
function onTopDownMenuAction(result) {
  console.log(result.id);     // 항목 id
  console.log(result.type);   // "button" | "select"
  console.log(result.value);  // 버튼이면 ""; 셀렉트이면 선택된 값
}

Bottom Sheet

화면 아래에서 위로 슬라이드하는 패널입니다. 하단 탭 바와 함께 사용할 수 있습니다.

show

window.unveilyBridge.bottomSheet.show({
  title: "상품 상세",     // 선택 사항
  content: "설명 텍스트", // 선택 사항
  heightFraction: 0.5,   // 화면 높이 비율 (0.1–1.0, 기본값: 0.5)
  heightDp: 400          // 고정 높이(dp) — heightFraction보다 우선
});

hide / toggle

window.unveilyBridge.bottomSheet.hide();
window.unveilyBridge.bottomSheet.toggle();

setConfig

setConfig로 시트 높이를 설정합니다. 열려 있으면 즉시 반영(라이브 리사이즈), 닫혀 있으면 다음 show() 호출 시 반영됩니다.

// 닫힌 상태 — 다음 show() 때 높이 적용
window.unveilyBridge.bottomSheet.setConfig({ heightFraction: 0.7 });

// 열린 상태에서 호출 — 즉시 라이브 리사이즈
window.unveilyBridge.bottomSheet.setConfig({ heightFraction: 0.3 });

패널 크기 설정 공통 규칙

상단 메뉴·사이드 드로어·바텀 시트 3종 모두 크기는 setConfig가 담당합니다. 열린 상태에서 호출하면 즉시 반영되고, 닫힌 상태에서는 다음 open()/show() 시 반영됩니다. open()/show()는 표시만 담당하며, 이미 열린 패널에 다시 호출해도 크기는 바뀌지 않습니다.

크기 옵션: 상단 메뉴 maxHeightFraction(상한) · 사이드 드로어 widthDp(고정 너비) · 바텀 시트 heightFraction 또는 heightDp(고정 높이, heightDp 우선).

닫기 제스처

  • 아래로 스와이프: 시트가 열린 상태에서 빠르게 아래로 밀면 닫힙니다.
  • 뒤로 가기 버튼: 앱이 종료되지 않고 시트를 먼저 닫습니다.
  • 바깥 영역 탭: 시트 위의 어두운 스크림을 탭하면 닫힙니다.

항목 타입

Top Down Menu와 Side Drawer의 항목은 type 필드로 렌더링 방식을 결정합니다.

type렌더링조작 시 패널 닫힘콜백
"icon"아이콘 + 라벨 그리드/목록 셀 (기본값)onTopDownMenuItemClick / onSideDrawerItemClick
"image"전체 너비 이미지 행 (SVG 권장)url 설정 시만onTopDownMenuItemClick / onSideDrawerItemClick
"button"전체 너비 네이티브 버튼아니오onTopDownMenuAction / onSideDrawerAction
"select"전체 너비 네이티브 스피너(드롭다운)아니오onTopDownMenuAction / onSideDrawerAction
"toggle"전체 너비 스위치 (라벨 + 온/오프)아니오onTopDownMenuAction / onSideDrawerAction
"separator"가로 구분선없음
"header"비대화형 섹션 제목 라벨없음

"button", "select", "toggle"은 조작 시 패널을 자동으로 닫지 않습니다. JS 콜백에서 직접 처리하세요.

type: "icon" (기본값)

{
  "id": "settings",
  "label": { "ko": "설정", "en": "Settings", "ja": "設定" },
  "icon": "settings.png",
  "url": "/settings"
}

label은 단일 언어 환경에서 일반 문자열로도 사용할 수 있습니다: "label": "설정"

type: "image"

전체 너비 이미지 행입니다. 로컬 .svg 파일은 AndroidSVG로 렌더링되고 다른 포맷은 Glide를 사용합니다.

{
  "id": "banner",
  "type": "image",
  "icon": "promo.svg",
  "heightDp": 80,
  "url": "/promo"
}
필드타입기본값설명
iconstring에셋 파일명(예: "banner.svg") 또는 원격 URL
heightDpnumber80행 높이(dp) (20–400)
urlstring선택 사항. 설정 시 탭하면 onTopDownMenuItemClick을 실행하고 패널을 닫음

SVG 파일은 assets/menu_icons/에 넣고 파일명으로 참조합니다.

type: "button"

전체 너비 네이티브 버튼입니다. 클릭하면 onTopDownMenuAction / onSideDrawerAction이 실행됩니다.

{
  "id": "refresh",
  "type": "button",
  "label": { "ko": "새로고침", "en": "Refresh", "ja": "更新" }
}

type: "select"

전체 너비 네이티브 스피너(드롭다운)입니다. 옵션을 선택하면 onTopDownMenuAction / onSideDrawerAction이 실행됩니다.

{
  "id": "lang",
  "type": "select",
  "value": "ko",
  "options": [
    { "value": "ko", "label": { "ko": "한국어", "en": "Korean",   "ja": "韓国語"  } },
    { "value": "en", "label": { "ko": "영어",   "en": "English",  "ja": "英語"    } },
    { "value": "ja", "label": { "ko": "일본어", "en": "Japanese", "ja": "日本語"  } }
  ]
}
필드타입기본값설명
valuestring미리 선택할 옵션 값
optionsarray{ value, label } 객체 배열

type: "toggle"

라벨이 왼쪽, 토글 스위치가 오른쪽에 위치하는 전체 너비 스위치입니다. value: "true" 또는 "false"onTopDownMenuAction / onSideDrawerAction을 실행합니다.

{
  "id": "dark",
  "type": "toggle",
  "label": { "ko": "다크 모드", "en": "Dark Mode", "ja": "ダークモード" },
  "value": false
}
필드타입기본값설명
labelstring | i18n스위치 왼쪽에 표시할 설명 텍스트
valuebooleanfalse초기 체크 상태

type: "separator"

항목 그룹화를 위한 가로 구분선입니다. id와 콜백이 없습니다.

{ "type": "separator" }

type: "header"

비대화형 섹션 제목 라벨입니다. 콜백 없음.

{
  "type": "header",
  "label": { "ko": "설정", "en": "Settings", "ja": "設定" }
}

title 필드

button, select, toggle, image 항목은 선택 사항인 title 필드를 지원합니다. 설정하면 해당 컨트롤 바로 위에 작은 보조 텍스트 라벨이 렌더링됩니다. 일반 문자열 또는 i18n 객체 모두 지원합니다.

{
  "id": "dark",
  "type": "toggle",
  "title": { "ko": "화면", "en": "Appearance", "ja": "画面" },
  "label": { "ko": "다크 모드", "en": "Dark Mode", "ja": "ダークモード" },
  "value": false
}

다국어 라벨

모든 항목의 label 필드는 일반 문자열 또는 ko, en, ja 키를 가진 i18n 객체를 모두 지원합니다.

// 일반 문자열 — 언어와 무관하게 동일한 라벨 표시
{ id: "home", label: "홈", icon: "home.png" }

// i18n 객체 — setLocale()에 따라 라벨이 전환됨
{ id: "home", label: { ko: "홈", en: "Home", ja: "ホーム" }, icon: "home.png" }

i18n 객체를 사용하면 현재 로케일에 맞는 라벨이 표시됩니다 (아래 setLocale 참고). 폴백 순서: 요청된 로케일 → en → 항목 id


setLocale

메뉴 항목 라벨 표시에 사용할 로케일을 재정의합니다. 앱에서 사용자가 언어를 변경할 때 호출하세요.

window.unveilyBridge.app.setLocale("ko");  // "ko" | "en" | "ja"
window.unveilyBridge.app.setLocale("");    // 기기 로케일로 초기화

열려 있는 패널의 라벨은 다시 열지 않아도 즉시 업데이트됩니다.

예시 — Top Down Menu에서 언어 선택기 구현

window.unveilyBridge.topDownMenu.setConfig({
  items: [
    { id: "home", label: { ko: "홈", en: "Home", ja: "ホーム" }, icon: "home.png" },
    { id: "lang", type: "select", value: "ko", options: [
        { value: "ko", label: { ko: "한국어", en: "Korean",   ja: "韓国語" } },
        { value: "en", label: { ko: "영어",   en: "English",  ja: "英語"   } },
        { value: "ja", label: { ko: "일본어", en: "Japanese", ja: "日本語" } }
    ]}
  ]
});

function onTopDownMenuAction(result) {
  if (result.id === "lang" && result.type === "select") {
    window.unveilyBridge.app.setLocale(result.value);
  }
}

On this page