wix-cli-site-widget について
wix-cli-site-widgetは、Wix CLIアプリ向けのサイトウィジェット開発スキルです。ReactコンポーネントをWebコンポーネントに変換し、Wix Editorの設定パネルやプロパティ設定、拡張機能の登録を行う方法を説明します。
主な機能
- 01ReactからWebコンポーネントへの変換
- 02Wix Editor設定パネル
- 03ウィジェットのプロパティ設定
- 04カラー・フォントピッカー
- 05Wix Dataのエディターモード対応
活用シーン
- 01カウントダウンウィジェットの作成
- 02コレクションの商品表示
- 03色を設定できる計算機の作成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wix/ skills --skill wix-cli-site-widgetインストール後、Claude Code で/wix-cli-site-widgetを入力して読み込まれたか確認します。
同梱ファイルreferences/SETTINGS_PANEL.md
詳細
Wix サイトウィジェットビルダー
Wix CLI アプリケーション向けのカスタム要素ウィジェット拡張機能を作成します。サイトウィジェットは、ウェブコンポーネントに変換された React コンポーネントです。Wix エディターに表示されるため、サイト所有者は組み込みの設定パネルを使って、インタラクティブで設定可能なウィジェットをページに追加できます。
クイックスタートチェックリスト
サイトウィジェットを作成する際は、次の手順を順番に実行してください。
- ウィジェットフォルダーを作成します:
src/extensions/site/widgets/custom-elements/<widget-name>/ - React コンポーネントと
reactToWebComponentによる変換を含むwidget.tsxを作成します - WDS コンポーネントと
widget.getProp/setPropを含むpanel.tsxを作成します -
extensions.customElement()と一意の UUID を含むextensions.tsを作成します -
src/extensions.tsを更新して、新しい拡張機能をインポートして使用します
アーキテクチャ
サイトウィジェットは、必須ファイルが2つあります。
1. ウィジェットコンポーネント(widget.tsx)
react-to-webcomponent を使ってウェブコンポーネントに変換する React コンポーネントです。
- 設定可能なプロパティを含む Props インターフェースを定義します(camelCase)
- ウィジェットの UI をレンダリングする React 関数コンポーネントを作成します
- プロパティをマッピングしてウェブコンポーネントに変換します
- インラインスタイルを使用します(CSS のインポートは使用しません)
- Wix Data API を使用する場合は、Wix エディターの環境に対応します
2. 設定パネル(panel.tsx)
Wix エディターのサイドバーに表示される設定パネルです。
- Wix Design System コンポーネントを使用します(references/SETTINGS_PANEL.md を参照)
@wix/editorのウィジェット API を使ってウィジェットのプロパティを管理しますwidget.getProp('kebab-case-name')で初期値を読み込みますwidget.setProp('kebab-case-name', value)でプロパティを更新しますWixDesignSystemProvider > SidePanel > SidePanel.Contentでラップします- カラーピッカーには、
@wix/editorのinputs.selectColor()とFillPreviewを使用します。<Input type="color">は使用しないでください - フォントピッカーには、
@wix/editorのinputs.selectFont()とButtonを使用します。テキストのInputは使用しないでください
ウィジェットコンポーネントのパターン
import React, { type FC, useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import reactToWebComponent from 'react-to-webcomponent';
interface WidgetProps {
title?: string;
targetDate?: string;
targetTime?: string;
bgColor?: string;
textColor?: string;
font?: string;
}
const CustomElement: FC<WidgetProps> = ({
title = 'Countdown',
targetDate = '',
targetTime = '00:00',
bgColor = '#ffffff',
textColor = '#333333',
font = "{}",
}) => {
const { font: textFont, textDecoration } = JSON.parse(font);
const [time, setTime] = useState({ days: 0, hours: 0, minutes: 0, seconds: 0, isExpired: false });
useEffect(() => {
if (!targetDate) return;
const update = () => {
const target = new Date(`${targetDate}T${targetTime}`);
const diff = target.getTime() - Date.now();
if (diff <= 0) {
setTime({ days: 0, hours: 0, minutes: 0, seconds: 0, isExpired: true });
return;
}
setTime({
days: Math.floor(diff / (1000 * 60 * 60 * 24)),
hours: Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
minutes: Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)),
seconds: Math.floor((diff % (1000 * 60)) / 1000),
isExpired: false,
});
};
update();
const interval = setInterval(update, 1000);
return () => clearInterval(interval);
}, [targetDate, targetTime]);
const styles = {
wrapper: {
backgroundColor: bgColor,
padding: '24px 32px',
textAlign: 'center' as const,
display: 'inline-block',
},
title: {
font: textFont || '600 24px sans-serif',
color: textColor,
textDecoration,
marginBottom: '16px',
},
};
return (
<div style={styles.wrapper}>
{title && <div style={styles.title}>{title}</div>}
{/* Widget content */}
</div>
);
};
const customElement = reactToWebComponent(CustomElement, React, ReactDOM, {
props: {
title: 'string',
targetDate: 'string',
targetTime: 'string',
bgColor: 'string',
textColor: 'string',
font: 'string',
},
});
export default customElement;
主なポイント:
- Props インターフェースには camelCase を使用します(例:
targetDate、bgColor) reactToWebComponentの設定では、キーに camelCase、型に'string'を使用します- すべてのプロパティは、ウェブコンポーネントから文字列として渡されます
- CSS のインポートではなく、インラインスタイルを使用します
- 複雑なプロパティ(
fontなど)は JSON 文字列から解析します:const { font: textFont, textDecoration } = JSON.parse(font) fontCSS ショートハンドとtextDecorationプロパティを使ってフォントを適用します- コードを整理するため、ヘルパーコンポーネント、ユーティリティ関数、スタイルは個別のファイルに分けます
設定パネルのパターン
import React, { type FC, useState, useEffect, useCallback } from "react";
import { widget } from "@wix/editor";
import {
SidePanel,
WixDesignSystemProvider,
Input,
FormField,
TimeInput,
Box,
} from "@wix/design-system";
import "@wix/design-system/styles.global.css";
import { ColorPickerField } from "./components/ColorPickerField";
import { FontPickerField } from "./components/FontPickerField";
import { parseTimeValue } from "./utils";
const DEFAULT_BG_COLOR = "#0a0e27";
const DEFAULT_TEXT_COLOR = "#00ff88";
const DEFAULT_TEXT_FONT = "";
const DEFAULT_TEXT_DECORATION = "";
const Panel: FC = () => {
const [title, setTitle] = useState<string>("Countdown");
const [targetDate, setTargetDate] = useState<string>("");
const [targetTime, setTargetTime] = useState<string>("00:00");
const [bgColor, setBgColor] = useState<string>(DEFAULT_BG_COLOR);
const [textColor, setTextColor] = useState<string>(DEFAULT_TEXT_COLOR);
const [font, setFont] = useState({ font: DEFAULT_TEXT_FONT, textDecoration: DEFAULT_TEXT_DECORATION });
useEffect(() => {
Promise.all([
widget.getProp("title"),
widget.getProp("target-date"),
widget.getProp("target-time"),
widget.getProp("bg-color"),
widget.getProp("text-color"),
widget.getProp("font"),
])
.then(([titleVal, dateVal, timeVal, bgColorVal, textColorVal, fontString]) => {
setTitle(titleVal || "Countdown");
setTargetDate(dateVal || "");
setTargetTime(timeVal || "00:00");
setBgColor(bgColorVal || DEFAULT_BG_COLOR);
setTextColor(textColorVal || DEFAULT_TEXT_COLOR);
setFont(JSON.parse(fontString || "{}"));
})
.catch((error) => console.error("Failed to fetch widget properties:", error));
}, []);
const handleTitleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const newTitle = event.target.value;
setTitle(newTitle);
widget.setProp("title", newTitle);
}, []);
const handleDateChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const newDate = event.target.value;
setTargetDate(newDate);
widget.setProp("target-date", newDate);
}, []);
const handleTimeChange = useCallback(({ date }: { date: Date }) => {
if (date) {
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const newTime = `${hours}:${minutes}`;
setTargetTime(newTime);
widget.setProp("target-time", newTime);
}
}, []);
const handleBgColorChange = (value: string) => {
setBgColor(value);
widget.setProp("bg-color", value);
};
const handleTextColorChange = (value: string) => {
setTextColor(value);
widget.setProp("text-color", value);
};
const handleFontChange = (value: { font: string; textDecoration: string }) => {
setFont(value);
widget.setProp("font", JSON.stringify(value));
};
return (
<WixDesignSystemProvider>
<SidePanel width="300" height="100vh">
<SidePanel.Header title="Countdown Settings" />
<SidePanel.Content noPadding stretchVertically>
<Box direction="vertical" gap="24px">
<SidePanel.Field>
<FormField label="Title" required>
<Input
type="text"
value={title}
onChange={handleTitleChange}
placeholder="Enter countdown title"
/>
</FormField>
</SidePanel.Field>
<SidePanel.Field>
<FormField label="Target Date" required>
<Input
type="date"
value={targetDate}
onChange={handleDateChange}
/>
</FormField>
</SidePanel.Field>
<SidePanel.Field>
<FormField label="Target Time" required>
<TimeInput
value={parseTimeValue(targetTime)}
onChange={handleTimeChange}
/>
</FormField>
</SidePanel.Field>
<ColorPickerField
label="Background Color"
value={bgColor}
onChange={handleBgColorChange}
/>
<ColorPickerField
label="Text Color"
value={textColor}
onChange={handleTextColorChange}
/>
<FontPickerField
label="Text Font"
value={font}
onChange={handleFontChange}
/>
</Box>
</SidePanel.Content>
</SidePanel>
</WixDesignSystemProvider>
);
};
export default Panel;
主なポイント:
widget.getProp()とwidget.setProp()のプロパティ名には kebab-case を使用します(例:"target-date"、"bg-color")- onChange ハンドラーでは、ローカルの状態とウィジェットのプロパティの両方を必ず更新します
- コンテンツを
WixDesignSystemProvider > SidePanel > SidePanel.Contentでラップします @wix/design-systemの WDS コンポーネントを使用します(references/SETTINGS_PANEL.md を参照)- スタイルには
@wix/design-system/styles.global.cssをインポートします - 色には、
@wix/editorのinputs.selectColor()を使用するColorPickerFieldを使います。<Input type="color">は使用しないでください - フォントには、
@wix/editorのinputs.selectFont()を使用するFontPickerFieldを使います。テキストのInputは使用しないでください - フォント値は、
JSON.stringify()/JSON.parse()を使って JSON 文字列として保存します
プロパティの命名規則
重要: 各ファイルでプロパティの命名規則が異なります。
| ファイル | 命名規則 | 例 |
|---|---|---|
widget.tsx(Props インターフェース) | camelCase | targetDate、bgColor、textColor |
panel.tsx(ウィジェット API) | kebab-case | "target-date"、"bg-color"、"text-color" |
reactToWebComponent の設定 | camelCase | targetDate: 'string' |
ウェブコンポーネントは、camelCase(React のプロパティ)と kebab-case(HTML 属性)の間を自動的に変換します。
Wix Data API の統合
ウィジェットで Wix Data API を使用する場合は、Wix Editor 環境を適切に処理する必要があります:
import { items } from "@wix/data";
import { window as wixWindow } from "@wix/site-window";
const CustomElement: FC<WidgetProps> = ({ collectionId }) => {
const [data, setData] = useState(null);
const [isEditor, setIsEditor] = useState(false);
useEffect(() => {
const loadData = async () => {
const currentViewMode = await wixWindow.viewMode();
if (currentViewMode === "Editor") {
// Don't fetch data in editor - show placeholder
setIsEditor(true);
return;
}
// Fetch real data only on live site
try {
const results = await items.query(collectionId).limit(10).find();
setData(results.items);
} catch (error) {
console.error("Failed to load data:", error);
}
};
loadData();
}, [collectionId]);
if (isEditor) {
return (
<div style={{ padding: "20px", border: "2px dashed #ccc" }}>
<p>Widget will display data on the live site</p>
<p>Collection: {collectionId}</p>
</div>
);
}
// Render widget with real data
return (
<div>
{data?.map((item) => (
<div key={item._id}>{item.title}</div>
))}
</div>
);
};
要件:
"@wix/site-window"から{ window as wixWindow }をインポートします- データを取得する前に
await wixWindow.viewMode()を確認します viewMode === 'Editor'の場合は、代わりにプレースホルダー UI を表示します- エディター以外のモードの場合にのみ、実際のデータを取得して表示します
色の選択
設定パネルで色を選択する場合は、@wix/editor の inputs.selectColor() を使用する ColorPickerField コンポーネントを使います。<Input type="color"> は使用しないでください。
// components/ColorPickerField.tsx
import React, { type FC } from 'react';
import { inputs } from '@wix/editor';
import { FormField, Box, FillPreview, SidePanel } from '@wix/design-system';
interface ColorPickerFieldProps {
label: string;
value: string;
onChange: (value: string) => void;
}
export const ColorPickerField: FC<ColorPickerFieldProps> = ({
label,
value,
onChange,
}) => (
<SidePanel.Field>
<FormField label={label}>
<Box width="30px" height="30px">
<FillPreview
fill={value}
onClick={() => inputs.selectColor(value, { onChange: (val) => { if (val) onChange(val); } })}
/>
</Box>
</FormField>
</SidePanel.Field>
);
パネルでの使用方法:
const handleBgColorChange = (value: string) => {
setBgColor(value);
widget.setProp("bg-color", value);
};
<ColorPickerField label="Background Color" value={bgColor} onChange={handleBgColorChange} />
重要: WDS の FillPreview とともに、@wix/editor の inputs.selectColor(value, { onChange }) を使用します。これにより、テーマカラーやグラデーションなどに対応した Wix のネイティブカラーピッカーが開きます。<Input type="color"> は絶対に使用しないでください。
フォントの選択
設定パネルでフォントを選択する場合は、@wix/editor の inputs.selectFont() を使用する FontPickerField コンポーネントを使います。テキスト入力は使用しないでください。
// components/FontPickerField.tsx
import React, { type FC } from 'react';
import { inputs } from '@wix/editor';
import { FormField, Button, Text, SidePanel } from '@wix/design-system';
interface FontValue {
font: string;
textDecoration: string;
}
interface FontPickerFieldProps {
label: string;
value: FontValue;
onChange: (value: FontValue) => void;
}
export const FontPickerField: FC<FontPickerFieldProps> = ({
label,
value,
onChange,
}) => (
<SidePanel.Field>
<FormField label={label}>
<Button
size="small"
priority="secondary"
onClick={() => inputs.selectFont(value, { onChange: (val) => onChange({ font: val.font, textDecoration: val.textDecoration || "" }) })}
fullWidth
>
<Text size="small" ellipsis>Change Font</Text>
</Button>
</FormField>
</SidePanel.Field>
);
パネルでの使用方法:
const [font, setFont] = useState<FontValue>({ font: "", textDecoration: "" });
const handleFontChange = (value: FontValue) => {
setFont(value);
widget.setProp("font", JSON.stringify(value));
};
<FontPickerField label="Text Font" value={font} onChange={handleFontChange} />
重要: コールバックパターンを使用して、@wix/editor の inputs.selectFont(value, { onChange }) を使います。これにより、太字、斜体、サイズ、タイポグラフィ機能を備えた豊富なフォント選択ダイアログが表示されます。フォントの値は JSON 文字列として保存されます。
出力構成
src/extensions/site/widgets/custom-elements/
└── {widget-name}/
├── widget.tsx # Main widget component
├── panel.tsx # Settings panel component
├── extensions.ts # Extension registration
├── components/ # Optional sub-components
│ ├── ColorPickerField.tsx
│ └── FontPickerField.tsx
└── utils/ # Optional helper functions
└── formatters.ts
例
カウントダウンタイマーウィジェット
リクエスト:「カウントダウンタイマーウィジェットを作成してください」
出力:
- タイトル、目標日時、色、フォントを設定できるウィジェット
- 日付選択、時刻入力、カラーピッカー、フォントピッカーを備えた設定パネル
- 日、時間、分、秒をリアルタイムで表示するカウントダウン
商品ショーケースウィジェット
リクエスト:「コレクションの商品を表示するウィジェットを作成してください」
出力:
- Wix Data コレクションにクエリを実行するウィジェット
- エディター環境への対応(エディターではプレースホルダーを表示)
- コレクションの選択、表示オプション、スタイルを設定するパネル
- 商品カードを備えたレスポンシブなグリッドレイアウト
インタラクティブな計算機ウィジェット
リクエスト:「色をカスタマイズできる計算機ウィジェットを作成してください」
出力:
- 機能する計算機コンポーネント
- 色(背景、ボタン、テキスト)をカスタマイズする設定パネル
- すべてのスタイルにインラインスタイルを使用
- 外部依存関係なし
フロントエンドのデザイン
ありきたりなデザインは避けてください。独自のフォント(Inter、Roboto、Arial は避けてください)、統一感のあるカラーパレット、マイクロインタラクション向けの CSS アニメーション、コンテキストに合った選択を用いて、個性のあるデザインを作成してください。陳腐な配色や予測可能なレイアウトは使用しないでください。
拡張機能の登録
拡張機能の登録は必須であり、必要な手順は2つあります。
手順 1:ウィジェット専用の拡張機能ファイルを作成する
各サイトウィジェットのフォルダーに extensions.ts ファイルが必要です:
import { extensions } from "@wix/astro/builders";
export const sitewidgetMyWidget = extensions.customElement({
id: "{{GENERATE_UUID}}",
name: "My Widget",
tagName: "my-widget",
element: "./extensions/site/widgets/custom-elements/my-widget/widget.tsx",
settings: "./extensions/site/widgets/custom-elements/my-widget/panel.tsx",
installation: {
autoAdd: true,
},
width: {
defaultWidth: 500,
allowStretch: true,
},
height: {
defaultHeight: 500,
},
});
重要:UUID の生成
id は、静的で一意な UUID v4 文字列である必要があります。拡張機能ごとに新しい UUID を生成してください。randomUUID() を使用したり、例にある UUID をコピーしたりしないでください。{{GENERATE_UUID}} は、"a1b2c3d4-e5f6-7890-abcd-ef1234567890" のように新しく生成した UUID に置き換えてください。
| プロパティ | 型 | 説明 |
|---|---|---|
id | string | 静的で一意な UUID v4(新しく生成) |
name | string | エディターに表示する名前 |
tagName | string | HTML カスタム要素のタグ(ケバブケース) |
element | string | ウィジェットの React コンポーネントへのパス |
settings | string | 設定パネルコンポーネントへのパス |
installation | object | 自動追加の動作 |
width | object | 既定の幅と伸縮設定 |
height | object | 既定の高さ設定 |
ステップ 2:メインの拡張機能ファイルに登録する
重要: ウィジェット固有の拡張機能ファイルを作成した後、必ず wix-cli-extension-registration を読み、「App Registration」セクションに従って src/extensions.ts を更新してください。
ステップ 2 を完了しないと、サイトウィジェットを Wix Editor で利用できません。
コード品質要件
- 厳格な TypeScript(
anyは使用せず、戻り値の型を明示する) - フックを使用した関数型 React コンポーネント
- 適切なエラー処理と読み込み状態
@ts-ignoreコメントは使用しない- インラインスタイルのみ(CSS のインポートは禁止)
- Wix Data API の使用時は Wix Editor 環境に対応する
- 一貫した props の命名(ウィジェットでは camelCase、パネルでは kebab-case)