メインコンテンツまでスキップ

テンプレート詳細

各テンプレートの説明と実装ガイドです。

📦 GitHubテンプレートリポジトリ: https://github.com/urth-inc/metatell-plugin-template

各テンプレートの詳細

AdditionalToolbarButton

ツールバーに独自のボタンを追加する最もシンプルなテンプレートです。

実装例

import { useState } from 'react';
import styles from './AdditionalToolbarButton.module.scss';

export const AdditionalToolbarButton = () => {
const [isActive, setIsActive] = useState(false);

const handleClick = () => {
setIsActive(!isActive);
// カスタムアクションの実行
console.log('Toolbar button clicked');
};

return (
<button
className={`${styles.button} ${isActive ? styles.active : ''}`}
onClick={handleClick}
title="カスタム機能"
>
<svg className={styles.icon}>
{/* アイコンSVG */}
</svg>
</button>
);
};

主な用途

  • ショートカット機能の追加
  • 外部サービスとの連携
  • カスタム設定パネルの表示

CustomOverlay

画面上の任意位置に配置可能な汎用的なオーバーレイコンポーネントです。

実装例

import { useState, useEffect } from 'react';
import styles from './CustomOverlay.module.scss';

export const CustomOverlay = () => {
const [position, setPosition] = useState({ x: 20, y: 20 });
const [isMinimized, setIsMinimized] = useState(false);

// ドラッグ可能にする処理
const handleDragStart = (e: React.DragEvent) => {
const rect = e.currentTarget.getBoundingClientRect();
e.dataTransfer.setData('offsetX', String(e.clientX - rect.left));
e.dataTransfer.setData('offsetY', String(e.clientY - rect.top));
};

const handleDragEnd = (e: React.DragEvent) => {
setPosition({
x: e.clientX - Number(e.dataTransfer.getData('offsetX')),
y: e.clientY - Number(e.dataTransfer.getData('offsetY'))
});
};

return (
<div
className={styles.overlay}
style={{ left: position.x, top: position.y }}
draggable
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
>
<div className={styles.header}>
<h3>カスタムパネル</h3>
<button onClick={() => setIsMinimized(!isMinimized)}>
{isMinimized ? '+' : '−'}
</button>
</div>
{!isMinimized && (
<div className={styles.content}>
{/* コンテンツ */}
</div>
)}
</div>
);
};

主な用途

  • ステータス表示パネル
  • ミニマップ
  • 通知システム
  • ゲーム要素の表示

CustomEntryPanel

ルーム入室時に表示されるカスタムパネルです。

実装例

interface EntryPanelProps {
onComplete: (settings: any) => void;
}

export const CustomEntryPanel = ({ onComplete }: EntryPanelProps) => {
const [step, setStep] = useState(1);
const [settings, setSettings] = useState({
username: '',
avatar: null,
preferences: {}
});

const handleNext = () => {
if (step < 3) {
setStep(step + 1);
} else {
onComplete(settings);
}
};

return (
<div className={styles.entryPanel}>
<div className={styles.progress}>
ステップ {step} / 3
</div>

{step === 1 && (
<div className={styles.step}>
<h2>ようこそ!</h2>
<input
type="text"
placeholder="お名前を入力"
value={settings.username}
onChange={(e) => setSettings({...settings, username: e.target.value})}
/>
</div>
)}

{step === 2 && (
<div className={styles.step}>
<h2>アバターを選択</h2>
{/* アバター選択UI */}
</div>
)}

{step === 3 && (
<div className={styles.step}>
<h2>設定完了</h2>
{/* 最終確認 */}
</div>
)}

<button onClick={handleNext}>
{step < 3 ? '次へ' : '入室'}
</button>
</div>
);
};

主な用途

  • カスタム登録フロー
  • ルール説明
  • 初期設定の収集

CustomProfileModal

ユーザープロフィール表示をカスタマイズします。

実装例

interface ProfileModalProps {
userId: string;
isOpen: boolean;
onClose: () => void;
}

export const CustomProfileModal = ({ userId, isOpen, onClose }: ProfileModalProps) => {
const [userData, setUserData] = useState(null);

useEffect(() => {
if (isOpen && userId) {
// ユーザーデータの取得
fetchUserData(userId).then(setUserData);
}
}, [isOpen, userId]);

if (!isOpen || !userData) return null;

return (
<div className={styles.modalOverlay}>
<div className={styles.modal}>
<button className={styles.close} onClick={onClose}>×</button>

<div className={styles.profile}>
<img src={userData.avatar} alt={userData.name} />
<h2>{userData.name}</h2>
<p>{userData.bio}</p>

<div className={styles.stats}>
<div>参加日: {userData.joinDate}</div>
<div>スコア: {userData.score}</div>
</div>

<div className={styles.badges}>
{userData.badges?.map(badge => (
<span key={badge.id} className={styles.badge}>
{badge.name}
</span>
))}
</div>
</div>
</div>
</div>
);
};

主な用途

  • 拡張プロフィール情報
  • 実績・バッジシステム
  • ソーシャル機能の統合

CustomChatButton

チャット機能を完全にカスタマイズする高度なテンプレートです。

実装例

export const CustomChatButton = () => {
const [isOpen, setIsOpen] = useState(false);
const [messages, setMessages] = useState([]);
const [unreadCount, setUnreadCount] = useState(0);

useEffect(() => {
// WebSocketまたはイベントリスナーでメッセージを受信
const handleMessage = (event: CustomEvent) => {
const newMessage = event.detail;
setMessages(prev => [...prev, newMessage]);

if (!isOpen) {
setUnreadCount(prev => prev + 1);
}
};

window.addEventListener('chat-message', handleMessage);
return () => window.removeEventListener('chat-message', handleMessage);
}, [isOpen]);

const sendMessage = (text: string) => {
// メッセージ送信処理
const message = {
id: Date.now(),
text,
userId: 'current-user',
timestamp: new Date().toISOString()
};

// サーバーに送信
window.dispatchEvent(new CustomEvent('send-chat-message', { detail: message }));
};

return (
<>
<button
className={styles.chatButton}
onClick={() => {
setIsOpen(!isOpen);
setUnreadCount(0);
}}
>
チャット
{unreadCount > 0 && (
<span className={styles.badge}>{unreadCount}</span>
)}
</button>

{isOpen && (
<ChatModal
messages={messages}
onSend={sendMessage}
onClose={() => setIsOpen(false)}
/>
)}
</>
);
};

主な用途

  • カスタムチャットUI
  • プライベートメッセージング
  • チャンネル機能
  • 翻訳機能の統合

CustomNearestUserProfile

最寄りのユーザー情報をリアルタイムで表示する高度なテンプレートです。

実装例

export const CustomNearestUserProfile = () => {
const [nearestUser, setNearestUser] = useState(null);
const [distance, setDistance] = useState(Infinity);

useEffect(() => {
const updateNearestUser = () => {
// metatell APIから位置情報を取得
const myPosition = getMyPosition();
const users = getAllUsers();

let nearest = null;
let minDistance = Infinity;

users.forEach(user => {
const d = calculateDistance(myPosition, user.position);
if (d < minDistance && d > 0) {
minDistance = d;
nearest = user;
}
});

setNearestUser(nearest);
setDistance(minDistance);
};

// 定期的に更新
const interval = setInterval(updateNearestUser, 1000);
return () => clearInterval(interval);
}, []);

if (!nearestUser) return null;

return (
<div className={styles.nearestUser}>
<h4>最寄りのユーザー</h4>
<div className={styles.userInfo}>
<img src={nearestUser.avatar} alt={nearestUser.name} />
<div>
<div>{nearestUser.name}</div>
<div className={styles.distance}>
{distance.toFixed(1)}m
</div>
</div>
</div>
</div>
);
};

主な用途

  • ソーシャル機能
  • ゲームメカニクス
  • 位置ベースの機能