ナビゲーション
ウェブから下部タブバー・サイドドロワー・上部ドロップダウンメニュー・ボトムシートを制御する
ひと目でわかる
ナビゲーション Bridge を使うと、アプリ内のすべてのネイティブパネルをウェブページから直接制御できます。
| コンポーネント | Bridge 名前空間 | config.json のキー |
|---|---|---|
| Bottom Tabs | unveilyBridge.bottomTabs | modules.bottomTabs.enabled |
| Side Drawer | unveilyBridge.sideDrawer | modules.sideDrawer.enabled |
| Top Down Menu | unveilyBridge.topDownMenu | modules.topDownMenu.enabled |
| Bottom Sheet | unveilyBridge.bottomSheet | modules.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 にしてください。
{
"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アップデート後も維持されます(MainActivity.kt の変更不要)。
単一カラー(ライト/ダークモード共通):
{
"modules": {
"bottomTabs": {
"enabled": true,
"panelColor": "#CC1A1A2E"
}
}
}ライト/ダーク別指定(システムテーマに応じて自動切替):
{
"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 はアクセシビリティパネルのハイコントラスト設定より優先されます。ハイコントラストとブランドカラーを両立したい場合は、panelColor を未設定のままにするか、アクセシビリティ設定変更時にJSから色を動的に調整してください。
アイコン / ラベル カラー
iconColor と labelColor で、アイコンのティントとラベルのテキストカラーを個別に設定できます。Bottom Tabs・Top Down Menu・Side Drawer で使用可能で、panelColor と同じ単一文字列または {light, dark} オブジェクト形式を使用します。
{
"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(白) |
activeAlpha | アクティブタブの不透明度(Bottom Tabs のみ) | 1.0 |
inactiveAlpha | 非アクティブタブの不透明度(Bottom 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.json の heightDp オプションでタブバーの高さを調整できます。
{
"modules": {
"bottomTabs": {
"enabled": true,
"heightDp": 64
}
}
}| オプション | 型 | デフォルト | 説明 |
|---|---|---|---|
heightDp | number | 54 | タブバーの高さ(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: "ja", 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: "ja", 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)の動作
columns は type:'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"
}| フィールド | 型 | デフォルト | 説明 |
|---|---|---|---|
icon | string | — | アセットのファイル名(例: "banner.svg")またはリモート URL |
heightDp | number | 80 | 行の高さ(dp)(20–400) |
url | string | — | 省略可。設定するとタップ時に 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": "ja",
"options": [
{ "value": "ko", "label": { "ko": "한국어", "en": "Korean", "ja": "韓国語" } },
{ "value": "en", "label": { "ko": "영어", "en": "English", "ja": "英語" } },
{ "value": "ja", "label": { "ko": "일본어", "en": "Japanese", "ja": "日本語" } }
]
}| フィールド | 型 | デフォルト | 説明 |
|---|---|---|---|
value | string | — | 初期選択するオプションの値 |
options | array | — | { value, label } オブジェクトの配列 |
type: "toggle"
ラベルが左、トグルスイッチが右に配置された全幅スイッチです。value: "true" または "false" で onTopDownMenuAction / onSideDrawerAction を呼び出します。
{
"id": "dark",
"type": "toggle",
"label": { "ko": "다크 모드", "en": "Dark Mode", "ja": "ダークモード" },
"value": false
}| フィールド | 型 | デフォルト | 説明 |
|---|---|---|---|
label | string | i18n | — | スイッチ左側に表示する説明テキスト |
value | boolean | false | 初期チェック状態 |
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("ja"); // "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: "ja", 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);
}
}