mcpField
W
wix-cli-site-widget

wix-cli-site-widget

github.com/wix開発36288 回インストール今日更新
リポジトリ

wix-cli-site-widget について

wix-cli-site-widgetは、Wix CLIアプリ向けのサイトウィジェット開発スキルです。ReactコンポーネントをWebコンポーネントに変換し、Wix Editorの設定パネルやプロパティ設定、拡張機能の登録を行う方法を説明します。

主な機能

  1. 01ReactからWebコンポーネントへの変換
  2. 02Wix Editor設定パネル
  3. 03ウィジェットのプロパティ設定
  4. 04カラー・フォントピッカー
  5. 05Wix Dataのエディターモード対応

活用シーン

  1. 01カウントダウンウィジェットの作成
  2. 02コレクションの商品表示
  3. 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 エディターに表示されるため、サイト所有者は組み込みの設定パネルを使って、インタラクティブで設定可能なウィジェットをページに追加できます。

クイックスタートチェックリスト

サイトウィジェットを作成する際は、次の手順を順番に実行してください。

  1. ウィジェットフォルダーを作成します:src/extensions/site/widgets/custom-elements/<widget-name>/
  2. React コンポーネントと reactToWebComponent による変換を含む widget.tsx を作成します
  3. WDS コンポーネントと widget.getProp/setProp を含む panel.tsx を作成します
  4. extensions.customElement() と一意の UUID を含む extensions.ts を作成します
  5. 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)
  • font CSS ショートハンドと 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 インターフェース)camelCasetargetDate、bgColor、textColor
panel.tsx(ウィジェット API)kebab-case"target-date"、"bg-color"、"text-color"
reactToWebComponent の設定camelCasetargetDate: '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 に置き換えてください。

プロパティ型説明
idstring静的で一意な UUID v4(新しく生成)
namestringエディターに表示する名前
tagNamestringHTML カスタム要素のタグ(ケバブケース)
elementstringウィジェットの React コンポーネントへのパス
settingsstring設定パネルコンポーネントへのパス
installationobject自動追加の動作
widthobject既定の幅と伸縮設定
heightobject既定の高さ設定
ステップ 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)

コメント