星占いのFigma Widgetをつくってみる
Figma開発アドベントカレンダー16日目として「星占いのFigma Widget」をつくるチャレンジをしました。
FigJamでのワークショップの前などのアイスブレイクに使えるようなものをイメージしています。
この記事ではWidget開発の勘所が分かる範囲で、簡易的なもの目指して実装してみようとおもいます。
- Figmaのデスクトップアプリから新規作成する
- WidgetのUIをつくる
- Widgetのメニューから星座を選ぶ
- 星占いの結果を返すAPIを叩いて表示する
この記事の範囲での最終的な成果物はGitHubに公開しています。
FigmaのPluginとWidgetはデスクトップアプリのメニューから作り始めることができます。
Widget
> Development
> New widget
今回のはFigJamで使えるものにしたいので、Figma design&FigJam
を選びます。
適当に名前をつけて、今回はUIなどを用意せずにWidgetの部分だけで完結するものをつくるので、
Simple Widget
を選びます。
このウィザードに従っていくと、任意のフォルダを指定して、必要なコードが展開されます。
前述のウィザードで展開したフォルダに移動し、ターミナルやコードエディタからnpmの各パッケージをインストールします。
# 該当フォルダに移動
$ cd Horoscope
# npmの各パッケージをインストールします
$ npm install
インストール後は下記のコマンドを実行して、コードのビルドをします。これで実際にFigma上で動くコードへの変換をし、Figmaのデスクトップアプリから開発中であるこのWidgetを呼び出すことができます。
# コードの編集時に自動でビルドされる
$ npm run watch
開発中のWidgetはデスクトップアプリのPluginやWidgetを呼び出すメニューから選べます。
開いたpanelのWidget
タブを選び、Recent
と表示されているプルダウンでDevelopment
を選ぶと見つけやすいです。
Simple Widgetを選ぶと「-(マイナス)」ボタンと「+(プラス)」ボタンで数値を増減させる単純なWidgetが表示されるはずです。
主に触るファイルは code.tsx
です。
Simple Widgetとしての最初のコードは下記のコードです。
const { widget } = figma
const { useSyncedState, usePropertyMenu, AutoLayout, Text, SVG } = widget
function Widget() {
const [count, setCount] = useSyncedState('count', 0)
if (count !== 0) {
usePropertyMenu(
[
{
itemType: 'action',
propertyName: 'reset',
tooltip: 'Reset',
icon: `<svg width="22" height="15" viewBox="0 0 22 15" fill="none" xmlns="http://www.w3.org/2000/svg">
// ※SVGのところはコード量が多いため省略
</svg>
`,
},
],
() => {
setCount(0)
},
)
}
return (
<AutoLayout
verticalAlignItems={'center'}
spacing={8}
padding={16}
cornerRadius={8}
fill={'#FFFFFF'}
stroke={'#E6E6E6'}
>
<SVG
src={`<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="30" height="30" rx="15" fill="white"/>
<rect x="7.5" y="14.0625" width="15" height="1.875" fill="black" fill-opacity="0.8"/>
<rect x="0.5" y="0.5" width="29" height="29" rx="14.5" stroke="black" stroke-opacity="0.1"/>
</svg>`}
onClick={() => {
setCount(count - 1)
}}
></SVG>
<Text fontSize={32} width={42} horizontalAlignText={'center'}>
{count}
</Text>
<SVG
src={`<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="30" height="30" rx="15" fill="white"/>
<path d="M15.9375 7.5H14.0625V14.0625H7.5V15.9375H14.0625V22.5H15.9375V15.9375H22.5V14.0625H15.9375V7.5Z" fill="black" fill-opacity="0.8"/>
<rect x="0.5" y="0.5" width="29" height="29" rx="14.5" stroke="black" stroke-opacity="0.1"/>
</svg>`}
onClick={() => {
setCount(count + 1)
}}
></SVG>
</AutoLayout>
)
}
widget.register(Widget)
基本的にはこの構成をふまえ、この記事の目標となる星占いWidgetをつくっていきます。
本題であるコードに入る前にデザインの話をしておきます。
WidgetのUIは完全にゼロからつくるのではなく、Figmaのキャンバス上でのFrameやText等の要素で組み合わせてUIをつくるのと同じようにつくります。それがGUIでの組み合わせではなく、コードで実装するというのがWidgetの開発です。
詳細なコードの解説は後ほど解説しますが、例えば、
FrameをAuto Layoutにして、垂直方向に子要素を並べ、
内側余白の四方16px、子要素間の余白を8px、Fillを #CCCCCCとする
このようなFrameがあった場合、Widgetのコードでは下記のように書きます。
前述のコードでいうと、function Widget()
の中の return
のあとに記述するコードです。
<AutoLayout
direction={"vertical"}
padding={16}
spacing={8}
fill={"#CCCCCC"}
> ... </AutoLayout>
なので最終形のデザインはFigma上でモックとしてAuto Layoutや適切な余白や幅の設計、使う色の整理などをしておくと良いでしょう。
Widgetでどのようにそれらを再現するか、使える要素についてはAPI Referenceを参照しましょう。
今回の記事の範囲では、このUI部分のコードは下記のようになります。
const CONTAINER_COLOR = "#FFFFFF";
const HEADER_COLOR = "#FCE1BC";
const TEXT_COLOR = "#37143B";
const EMPHASIS_TEXT_COLOR = "#CF4720";
const GRAY_TEXT_COLOR = "#676767";
return (
<AutoLayout direction={"vertical"} verticalAlignItems={"center"} horizontalAlignItems={"center"} width={360} cornerRadius={12} fill={CONTAINER_COLOR}>
<AutoLayout direction={"vertical"} horizontalAlignItems={"center"} padding={{ bottom: 16 }} spacing={4} width={"fill-parent"} fill={HEADER_COLOR}>
{sign ? (
// 星座を選択しているとき
<Fragment>
<SVG src={getSignImage(sign)} width={200} height={170} />
<Text fill={TEXT_COLOR} fontSize={24}>
{capitalizeFirstLetter(sign)}
</Text>
</Fragment>
) : (
// 星座が未選択のとき
<Fragment>
<SVG src={PlaceholderImage} width={200} height={170} />
<Text fill={TEXT_COLOR} fontSize={24}>
Choose Your Zodiac Sign
</Text>
</Fragment>
)}
</AutoLayout>
// 星座を選択しているとき
{sign ? (
<AutoLayout direction="vertical" padding={16} spacing={12} width={"fill-parent"}>
<Text fontSize={24} width={"fill-parent"} horizontalAlignText={"center"} fill={EMPHASIS_TEXT_COLOR}>
{currentDate}
</Text>
<Text fontSize={14} width={"fill-parent"} horizontalAlignText={"left"}>
{description}
</Text>
<AutoLayout padding={{ top: 8 }} width={"fill-parent"}>
<Text fontSize={12} width={"fill-parent"} horizontalAlignText={"center"} fill={GRAY_TEXT_COLOR}>
Zodiac images by <Span href="https://www.freepik.com/free-vector/hand-drawn-zodiac-signs-set_14669618.htm#query=aries&position=26&from_view=search&track=sph">Freepik</Span>
</Text>
</AutoLayout>
</AutoLayout>
) : null}
</AutoLayout>
);
次にWidgetのメニューの作り方を解決します。WidgetのメニューというのはWidgetを選んだときに表示されるメニューです。
画像の引用元: https://www.figma.com/widget-docs/api/properties/widget-usepropertymenu#remarks
開発に関するドキュメントは下記の usePropertyMenu
の項目を参照してください。
メニュー部分のコードは下記です。
// # 1. メニューの表示項目
// Dropdownの項目
const signOptions = [
{ option: "", label: "Choose your sign" },
{ option: "aries", label: "Aries" },
{ option: "taurus", label: "Taurus" },
{ option: "gemini", label: "Gemini" },
{ option: "cancer", label: "Cancer" },
{ option: "leo", label: "Leo" },
{ option: "virgo", label: "Virgo" },
{ option: "libra", label: "Libra" },
{ option: "scorpio", label: "Scorpio" },
{ option: "sagittarius", label: "Sagittarius" },
{ option: "capricorn", label: "Capricorn" },
{ option: "aquarius", label: "Aquarius" },
{ option: "pisces", label: "Pisces" },
];
usePropertyMenu(
[
{ // 星座を選ぶメニュー
itemType: "dropdown",
propertyName: "signs",
tooltip: "Zodiac signs",
selectedOption: sign,
options: signOptions,
},
{
itemType: "separator",
},
{ // 初期状態にするためのアクションボタン
itemType: "action",
tooltip: "Reset",
propertyName: "reset",
},
],
// # 2. メニューの選択・実行後の処理
({ propertyName, propertyValue }) => {
if (propertyName === "reset") {
// propertyNameがresetのメニュー(acition)を実行したとき
resetSign();
} else if (propertyName === "signs") {
// propertyNameがsignsのメニュー(dropdown)を実行したとき
if (propertyValue) {
// propertyValueが値がある(=星座が選ばれている)とき
waitForTask(getSign(propertyValue));
} else {
// propertyValueが値がない(=星座が選ばれていない初期状態になった)とき
resetSign();
}
}
}
);
1. メニューの表示項目
Widgetに実装できるメニューの種類は6種類あります。
種類 | 内容 | 利用例 |
---|---|---|
action | 単純なアクションボタン | データの更新や初期化 |
color-selector | 色を選択するオプション | 背景色・文字色の変更 |
dropdown | ドロップダウンメニュー | 表示する内容の変更 |
toggle | トグル切り替え | UIの表示・非表示の切り替え |
link | リンク | ヘルプページへの誘導 |
separator | メニュー項目の区切り | メニューのグルーピング |
今回は「星座を選択する」ための dropdown
と、初期状態に戻す操作のために action
を使います。
2. メニューの選択・実行後の処理
usePropertyMenu
の第2引数にはメニューのクリック時に実行される関数を指定できます。その関数の引数に、メニューの名前( propertyName
)とその値( PropertyValue
)を渡せるので、それらを用いて必要な処理をすることができます。
ここには今回の肝となる「星座ごとの占い結果を取得する」関数 getSign()
があります。
const FETCH_URL = "https://aztro.sameerkumar.website/?day=today";
const [currentDate, setCurrentDate] = useSyncedState<string>("currentDate", "");
const [description, setDescription] = useSyncedState<string>("description", "");
const [sign, setSign] = useSyncedState<string>("sign", "");
const options = {
method: "POST",
};
function getSign(sign: string) {
return fetch(`${FETCH_URL}&sign=${sign}`, options)
.then(function (response) {
return response.text();
})
.then(function (text) {
const result = JSON.parse(text);
setDescription(result.description);
setCurrentDate(result.current_date);
})
.then(() => {
setSign(sign);
figma.notify("Wish you well 🪄");
})
.catch(function (error) {
console.error(error);
});
}
今回採用したaztro APIは https://aztro.sameerkumar.website/
にPOSTでリクエストし、パラメータに星座(sign
)と日にち(day
)を渡せば、その日の星座の運勢に関する情報が返ってきます。
項目 | 内容 | 例 |
---|---|---|
current_date |
指定した日付 | June 23, 2017 |
compatibility |
相性の良い(?)星座 | Cancer |
lucky_time |
幸運な時間帯 | 7am |
lucky_number |
幸運な数字 | 64 |
color |
幸運な色 | Spring Green |
date_range |
星座の該当する期間 | Mar 21 - Apr 20 |
mood |
気分 | Relaxed |
description |
運勢の説明 | It's finally time ... |
今回のWidgetの仕様としては「今日の運勢」にしたいので、 day
は today
で固定し、星座(sign
)のみを選ぶようにします。また利用する項目も今回は current_date
と description
に絞ります。
APIへのリクエストとレスポンスの処理ところは fetch
を使い、リクエストが成功して結果が返ってきたら、順に処理をしていくようにしていきます。今回はfetchに関する詳細な説明は省きます。
補足として getSign()
の処理の中で知っておくべきことを書いておくと、下記のようなコードの部分です。
const [currentDate, setCurrentDate] = useSyncedState<string>("currentDate", "");
const [description, setDescription] = useSyncedState<string>("description", "");
const [sign, setSign] = useSyncedState<string>("sign", "");
// ...省略
.then(function (text) {
const result = JSON.parse(text);
setDescription(result.description);
setCurrentDate(result.current_date);
})
.then(() => {
setSign(sign);
figma.notify("Wish you well 🪄");
})
usesyncedstate
は、状態管理として値を保持することができます。ここではAPIへのリクエストが成功したら、今日の日付(currentDate
)と運勢の説明文(description
)をそれぞれ保持するようにしています。
ここまでの星座を選んでからの処理の流れを整理しましょう。
「今日の日付と運勢の説明文の保持」をした後は、その内容をWidgetのUIに反映する必要があります。
その処理のポイントは usePropertyMenu
の解説にあるコードです。
星座(signs
)メニューで選択した項目の値をpropertyValue
として、getSign()
の引数に渡しています。
// ...省略
} else if (propertyName === "signs") {
if (propertyValue) {
waitForTask(getSign(propertyValue));
}
waitForTask()
はその中の非同期処理、今回であればAPIリクエストをして結果が返ってくるのを永続的に待つ場合に使えます。
リクエストも成功して各値が更新された後は、その内容に応じてUIの表示を変えます。
// signが正 = 星座が選ばれていれば、その値に応じた内容にする
{sign ? (
<Fragment>
<SVG src={getSignImage(sign)} width={200} height={170} />
<Text fill={TEXT_COLOR} fontSize={24}>
{capitalizeFirstLetter(sign)}
</Text>
</Fragment>
) : (
<Fragment>
<SVG src={PlaceholderImage} width={200} height={170} />
<Text fill={TEXT_COLOR} fontSize={24}>
Choose Your Zodiac Sign
</Text>
</Fragment>
)}
getSignImage()
は星座に応じて画像を入れ替える部分のコードです。
Widget開発の解説としては直接関係がないのですが、一応該当部分のコードも掲載しておきます。
細かいところは全体のコードを確認してください。
function getSignImage(sign: string) {
// 選択された星座ごとのSVG画像を返す
switch (sign) {
case "aries":
return Aries;
case "taurus":
return Taurus;
// 省略
default:
return "";
}
}
星座を選んでから、改めて別の星座を選べばその星座でリクエストし直すことができます。ですが、初期状態に戻したいこともあるかもしれません。今回はサンプルの実装としてその処理についても書いておきます。
まずメニューの部分は action
のtypeを指定しています。
usePropertyMenu(
[
// ...省略
{
itemType: "action",
tooltip: "Reset",
propertyName: "reset",
},
],
// ...省略
そして初期状態にする、つまり保持していた値をリセットするための処理を書きます。
Resetのアクションボタンをクリックされたら、その値を元にresetSign()
という関数を実行します。
function resetSign() {
setSign("");
setDescription("");
setCurrentDate("");
}
usePropertyMenu(
// ...省略
({ propertyName, propertyValue }) => {
// Resetの操作をされた場合の処理
if (propertyName === "reset") {
resetSign();
}
// ...省略
}
);
冒頭でも案内しましたが、今回の記事で解説した範囲のコードはGitHubに公開しています。
このコードをローカルにcloneした後は、Figmaのデスクトップアプリからインポートすると、ローカルで実際に動くものを確認できるはずです。
既存のコードをインポートする場合は、PluginやWidgetを呼び出すメニューのところにある「+(プラス)」ボタンをクリックし、Import widget from manifest ...
で、cloneしたファイルにある manifest.json
を選べばインポートできます。
利用するAPIではラッキーナンバーやカラーも受け取れますし、もっとカスタマイズする余地があります。
- ラッキーナンバー、カラーを表示する
- カラーはFillにいれて表示する
- フォントを変える
- 星座を選び方を変えてみる...など
またそのあたりの解説については、いつかまた記事か小さな書籍にできればと思います。
Figma開発アドベントカレンダーはFigmaに関する開発の有益な記事が展開されているので、他の記事もぜひチェックしてみてください!
その後に色々とUIをアップデートしました。
UIをアップデートしたものがFigma Communityが公開されました。
実際の操作感など試してみてください。
残念ながら利用していたAPIが停止したため、このWidgetの公開を停止します。
※他のAPIがあれば、この記事のコードを使って似たようなものは作れるかとおもいます