wix-cli-site-widget
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWix Site Widget Builder
Wix Site Widget Builder
Creates custom element widget extensions for Wix CLI applications. Site widgets are React components converted to web components that appear in the Wix Editor, allowing site owners to add interactive, configurable widgets to their pages with a built-in settings panel.
为Wix CLI应用创建自定义元素小组件扩展。站点小组件是转换为Web组件的React组件,可在Wix编辑器中显示,允许站点所有者通过内置设置面板向其页面添加交互式、可配置的小组件。
Quick Start Checklist
快速开始清单
Follow these steps in order when creating a site widget:
- Create widget folder:
src/site/widgets/custom-elements/<widget-name>/ - Create with React component and
widget.tsxconversionreactToWebComponent - Create with WDS components and
panel.tsxwidget.getProp/setProp - Create with
extensions.tsand unique UUIDextensions.customElement() - Update to import and use the new extension
src/extensions.ts
创建站点小组件时,请按以下顺序执行步骤:
- 创建小组件文件夹:
src/site/widgets/custom-elements/<widget-name>/ - 创建包含React组件和转换逻辑的
reactToWebComponentwidget.tsx - 创建包含WDS组件和的
widget.getProp/setProppanel.tsx - 创建包含和唯一UUID的
extensions.customElement()extensions.ts - 更新以导入并使用新扩展
src/extensions.ts
Architecture
架构
Site widgets consist of two required files:
站点小组件包含两个必填文件:
1. Widget Component (widget.tsx
)
widget.tsx1. 小组件组件(widget.tsx
)
widget.tsxReact component converted to a web component using :
react-to-webcomponent- Define Props interface with configurable properties (camelCase)
- Create a React functional component that renders the widget UI
- Convert to web component with props mapping
- Use inline styles (no CSS imports)
- Handle Wix Editor environment when using Wix Data API
使用转换为Web组件的React组件:
react-to-webcomponent- 定义包含可配置属性的Props接口(小驼峰命名)
- 创建渲染小组件UI的React函数式组件
- 转换为带属性映射的Web组件
- 使用内联样式(禁止CSS导入)
- 使用Wix Data API时需适配Wix编辑器环境
2. Settings Panel (panel.tsx
)
panel.tsx2. 设置面板(panel.tsx
)
panel.tsxSettings panel shown in the Wix Editor sidebar:
- Uses Wix Design System components (see references/SETTINGS_PANEL.md)
- Manages widget properties via widget API
@wix/editor - Loads initial values with
widget.getProp('kebab-case-name') - Updates properties with
widget.setProp('kebab-case-name', value) - Wrapped in
WixDesignSystemProvider > SidePanel > SidePanel.Content
在Wix编辑器侧边栏显示的设置面板:
- 使用Wix设计系统组件(参见references/SETTINGS_PANEL.md)
- 通过小组件API管理小组件属性
@wix/editor - 使用加载初始值
widget.getProp('kebab-case-name') - 使用更新属性
widget.setProp('kebab-case-name', value) - 包裹在中
WixDesignSystemProvider > SidePanel > SidePanel.Content
Widget Component Pattern
小组件组件模式
typescript
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;
bgColor?: string;
textColor?: string;
font?: string;
}
const CustomElement: FC<WidgetProps> = ({
title = "Default Title",
targetDate = "",
bgColor = "#ffffff",
textColor = "#333333",
font = "{}",
}) => {
// Parse font if needed
const { font: textFont, textDecoration } = JSON.parse(font);
// Component logic and state
const [data, setData] = useState(null);
// Use inline styles
const styles = {
wrapper: {
display: "flex",
flexDirection: "column",
padding: "20px",
backgroundColor: bgColor,
color: textColor,
fontFamily: textFont || "inherit",
},
};
return (
<div style={styles.wrapper}>
{title && <h2 style={{ margin: 0 }}>{title}</h2>}
{/* Widget content */}
</div>
);
};
// Convert to web component
const customElement = reactToWebComponent(CustomElement, React, ReactDOM, {
props: {
title: "string",
targetDate: "string",
bgColor: "string",
textColor: "string",
font: "string",
},
});
export default customElement;Key Points:
- Props interface uses camelCase (e.g., ,
targetDate)bgColor - config uses camelCase keys with
reactToWebComponenttype'string' - All props are passed as strings from the web component
- Use inline styles, not CSS imports
- Parse complex props (like ) from JSON strings if needed
font
typescript
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;
bgColor?: string;
textColor?: string;
font?: string;
}
const CustomElement: FC<WidgetProps> = ({
title = "Default Title",
targetDate = "",
bgColor = "#ffffff",
textColor = "#333333",
font = "{}",
}) => {
// Parse font if needed
const { font: textFont, textDecoration } = JSON.parse(font);
// Component logic and state
const [data, setData] = useState(null);
// Use inline styles
const styles = {
wrapper: {
display: "flex",
flexDirection: "column",
padding: "20px",
backgroundColor: bgColor,
color: textColor,
fontFamily: textFont || "inherit",
},
};
return (
<div style={styles.wrapper}>
{title && <h2 style={{ margin: 0 }}>{title}</h2>}
{/* Widget content */}
</div>
);
};
// Convert to web component
const customElement = reactToWebComponent(CustomElement, React, ReactDOM, {
props: {
title: "string",
targetDate: "string",
bgColor: "string",
textColor: "string",
font: "string",
},
});
export default customElement;核心要点:
- Props接口使用小驼峰命名(例如:,
targetDate)bgColor - 配置使用小驼峰键和
reactToWebComponent类型'string' - 所有属性从Web组件以字符串形式传递
- 使用内联样式,而非CSS导入
- 必要时从JSON字符串解析复杂属性(如)
font
Settings Panel Pattern
设置面板模式
typescript
import React, { type FC, useState, useEffect, useCallback } from "react";
import { widget } from "@wix/editor";
import {
SidePanel,
WixDesignSystemProvider,
Input,
FormField,
Box,
} from "@wix/design-system";
import "@wix/design-system/styles.global.css";
const Panel: FC = () => {
const [title, setTitle] = useState<string>("");
const [targetDate, setTargetDate] = useState<string>("");
const [bgColor, setBgColor] = useState<string>("#ffffff");
// Load initial values (kebab-case prop names)
useEffect(() => {
Promise.all([
widget.getProp("title"),
widget.getProp("target-date"),
widget.getProp("bg-color"),
])
.then(([titleVal, dateVal, bgColorVal]) => {
setTitle(titleVal || "");
setTargetDate(dateVal || "");
setBgColor(bgColorVal || "#ffffff");
})
.catch((error) =>
console.error("Failed to fetch widget properties:", error)
);
}, []);
// Update both local state and widget prop (kebab-case)
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);
},
[]
);
return (
<WixDesignSystemProvider>
<SidePanel width="300" height="100vh">
<SidePanel.Header title="Widget Settings" />
<SidePanel.Content noPadding stretchVertically>
<Box direction="vertical" gap="24px">
<SidePanel.Field>
<FormField label="Title">
<Input
type="text"
value={title}
onChange={handleTitleChange}
placeholder="Enter title"
/>
</FormField>
</SidePanel.Field>
<SidePanel.Field>
<FormField label="Target Date">
<Input
type="date"
value={targetDate}
onChange={handleDateChange}
/>
</FormField>
</SidePanel.Field>
</Box>
</SidePanel.Content>
</SidePanel>
</WixDesignSystemProvider>
);
};
export default Panel;Key Points:
- Prop names in and
widget.getProp()use kebab-case (e.g.,widget.setProp(),"target-date")"bg-color" - Always update both local state AND widget prop in onChange handlers
- Wrap content in
WixDesignSystemProvider > SidePanel > SidePanel.Content - Use WDS components from (see references/SETTINGS_PANEL.md)
@wix/design-system - Import for styles
@wix/design-system/styles.global.css
typescript
import React, { type FC, useState, useEffect, useCallback } from "react";
import { widget } from "@wix/editor";
import {
SidePanel,
WixDesignSystemProvider,
Input,
FormField,
Box,
} from "@wix/design-system";
import "@wix/design-system/styles.global.css";
const Panel: FC = () => {
const [title, setTitle] = useState<string>("");
const [targetDate, setTargetDate] = useState<string>("");
const [bgColor, setBgColor] = useState<string>("#ffffff");
// Load initial values (kebab-case prop names)
useEffect(() => {
Promise.all([
widget.getProp("title"),
widget.getProp("target-date"),
widget.getProp("bg-color"),
])
.then(([titleVal, dateVal, bgColorVal]) => {
setTitle(titleVal || "");
setTargetDate(dateVal || "");
setBgColor(bgColorVal || "#ffffff");
})
.catch((error) =>
console.error("Failed to fetch widget properties:", error)
);
}, []);
// Update both local state and widget prop (kebab-case)
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);
},
[]
);
return (
<WixDesignSystemProvider>
<SidePanel width="300" height="100vh">
<SidePanel.Header title="Widget Settings" />
<SidePanel.Content noPadding stretchVertically>
<Box direction="vertical" gap="24px">
<SidePanel.Field>
<FormField label="Title">
<Input
type="text"
value={title}
onChange={handleTitleChange}
placeholder="Enter title"
/>
</FormField>
</SidePanel.Field>
<SidePanel.Field>
<FormField label="Target Date">
<Input
type="date"
value={targetDate}
onChange={handleDateChange}
/>
</FormField>
</SidePanel.Field>
</Box>
</SidePanel.Content>
</SidePanel>
</WixDesignSystemProvider>
);
};
export default Panel;核心要点:
- 和
widget.getProp()中的属性名称使用短横线命名(例如:widget.setProp(),"target-date")"bg-color" - 在onChange处理函数中始终同时更新本地状态和小组件属性
- 将内容包裹在中
WixDesignSystemProvider > SidePanel > SidePanel.Content - 使用中的WDS组件(参见references/SETTINGS_PANEL.md)
@wix/design-system - 导入以应用样式
@wix/design-system/styles.global.css
Props Naming Convention
属性命名规范
Critical: Props use different naming conventions in each file:
| File | Convention | Example |
|---|---|---|
| camelCase | |
| kebab-case | |
| camelCase | |
The web component automatically converts between camelCase (React props) and kebab-case (HTML attributes).
重要提示: 各文件中属性使用不同的命名规范:
| 文件 | 命名规范 | 示例 |
|---|---|---|
| 小驼峰 | |
| 短横线 | |
| 小驼峰 | |
Web组件会自动在小驼峰(React属性)和短横线(HTML属性)之间进行转换。
Wix Data API Integration
Wix Data API集成
When using Wix Data API in widgets, you must handle the Wix Editor environment gracefully:
typescript
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>
);
};Requirements:
- Import from
{ window as wixWindow }"@wix/site-window" - Check before fetching data
await wixWindow.viewMode() - If , show a placeholder UI instead
viewMode === 'Editor' - Only fetch and render real data when NOT in editor mode
在小组件中使用Wix Data API时,您必须优雅适配Wix编辑器环境:
typescript
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() - 如果,则显示占位UI而非真实数据
viewMode === 'Editor' - 仅在非编辑器模式下获取并渲染真实数据
Font Selection
字体选择
For font selection in settings panels, use component with :
FontPickerFieldinputs.selectFont()typescript
import { inputs } from "@wix/editor";
import { FontPickerField } from "./components/FontPickerField";
const Panel: FC = () => {
const [font, setFont] = useState({ font: "", textDecoration: "" });
const handleFontChange = async () => {
const selectedFont = await inputs.selectFont();
if (selectedFont) {
const fontValue = {
font: selectedFont.fontFamily || "",
textDecoration: selectedFont.textDecoration || "",
};
setFont(fontValue);
widget.setProp("font", JSON.stringify(fontValue));
}
};
return (
<FontPickerField
label="Text Font"
value={font}
onChange={handleFontChange}
/>
);
};Important: Use from , NOT a text Input. This provides a rich font picker dialog with bold, italic, size, and typography features.
inputs.selectFont()@wix/editor在设置面板中选择字体时,使用组件和:
FontPickerFieldinputs.selectFont()typescript
import { inputs } from "@wix/editor";
import { FontPickerField } from "./components/FontPickerField";
const Panel: FC = () => {
const [font, setFont] = useState({ font: "", textDecoration: "" });
const handleFontChange = async () => {
const selectedFont = await inputs.selectFont();
if (selectedFont) {
const fontValue = {
font: selectedFont.fontFamily || "",
textDecoration: selectedFont.textDecoration || "",
};
setFont(fontValue);
widget.setProp("font", JSON.stringify(fontValue));
}
};
return (
<FontPickerField
label="Text Font"
value={font}
onChange={handleFontChange}
/>
);
};重要提示: 使用中的,而非文本输入框。这将提供一个功能丰富的字体选择对话框,包含加粗、斜体、字号和排版功能。
@wix/editorinputs.selectFont()Output Structure
输出结构
src/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.tssrc/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.tsExamples
示例
Countdown Timer Widget
倒计时器小组件
Request: "Create a countdown timer widget"
Output:
- Widget with configurable title, target date/time, colors, and font
- Settings panel with date picker, time input, color pickers, font picker
- Real-time countdown display with days, hours, minutes, seconds
需求: "创建一个倒计时器小组件"
输出:
- 带可配置标题、目标日期/时间、颜色和字体的小组件
- 包含日期选择器、时间输入框、颜色选择器、字体选择器的设置面板
- 显示天、时、分、秒的实时倒计时
Product Showcase Widget
产品展示小组件
Request: "Create a widget that displays products from a collection"
Output:
- Widget that queries Wix Data collection
- Editor environment handling (shows placeholder in editor)
- Settings panel for collection selection, display options, styling
- Responsive grid layout with product cards
需求: "创建一个显示集合中产品的小组件"
输出:
- 查询Wix Data集合的小组件
- 编辑器环境适配(在编辑器中显示占位符)
- 用于集合选择、显示选项、样式设置的面板
- 带产品卡片的响应式网格布局
Interactive Calculator Widget
交互式计算器小组件
Request: "Create a calculator widget with customizable colors"
Output:
- Functional calculator component
- Settings panel for color customization (background, buttons, text)
- Inline styles for all styling
- No external dependencies
需求: "创建一个可自定义颜色的计算器小组件"
输出:
- 功能完整的计算器组件
- 用于颜色自定义(背景、按钮、文本)的设置面板
- 所有样式均使用内联样式
- 无外部依赖
Frontend Aesthetics
前端美学
Avoid generic aesthetics. Create distinctive designs with unique fonts (avoid Inter, Roboto, Arial), cohesive color palettes, CSS animations for micro-interactions, and context-specific choices. Don't use clichéd color schemes or predictable layouts.
避免通用化的美学设计。创建具有独特字体(避免Inter、Roboto、Arial)、协调调色板、微交互CSS动画和特定上下文选择的独特设计。不要使用陈词滥调的配色方案或可预测的布局。
Extension Registration
扩展注册
Extension registration is MANDATORY and has TWO required steps.
扩展注册是强制性的,包含两个必填步骤。
Step 1: Create Widget-Specific Extension File
步骤1:创建小组件专属扩展文件
Each site widget requires an file in its folder:
extensions.tstypescript
import { extensions } from "@wix/astro/builders";
export const sitewidgetMyWidget = extensions.customElement({
id: "{{GENERATE_UUID}}",
name: "My Widget",
tagName: "my-widget",
element: "./site/widgets/custom-elements/my-widget/widget.tsx",
settings: "./site/widgets/custom-elements/my-widget/panel.tsx",
installation: {
autoAdd: true,
},
width: {
defaultWidth: 500,
allowStretch: true,
},
height: {
defaultHeight: 500,
},
});CRITICAL: UUID Generation
The must be a unique, static UUID v4 string. Generate a fresh UUID for each extension - do NOT use or copy UUIDs from examples. Replace with a freshly generated UUID like .
idrandomUUID(){{GENERATE_UUID}}"a1b2c3d4-e5f6-7890-abcd-ef1234567890"| Property | Type | Description |
|---|---|---|
| string | Unique static UUID v4 (generate fresh) |
| string | Display name in editor |
| string | HTML custom element tag (kebab-case) |
| string | Path to widget React component |
| string | Path to settings panel component |
| object | Auto-add behavior |
| object | Default width and stretch settings |
| object | Default height settings |
每个站点小组件在其文件夹中都需要一个文件:
extensions.tstypescript
import { extensions } from "@wix/astro/builders";
export const sitewidgetMyWidget = extensions.customElement({
id: "{{GENERATE_UUID}}",
name: "My Widget",
tagName: "my-widget",
element: "./site/widgets/custom-elements/my-widget/widget.tsx",
settings: "./site/widgets/custom-elements/my-widget/panel.tsx",
installation: {
autoAdd: true,
},
width: {
defaultWidth: 500,
allowStretch: true,
},
height: {
defaultHeight: 500,
},
});重要提示:UUID生成
idrandomUUID(){{GENERATE_UUID}}"a1b2c3d4-e5f6-7890-abcd-ef1234567890"| 属性 | 类型 | 描述 |
|---|---|---|
| string | 唯一的静态UUID v4(需新生成) |
| string | 编辑器中的显示名称 |
| string | HTML自定义元素标签(短横线命名) |
| string | 小组件React组件的路径 |
| string | 设置面板组件的路径 |
| object | 自动添加行为 |
| object | 默认宽度和拉伸设置 |
| object | 默认高度设置 |
Step 2: Register in Main Extensions File
步骤2:在主扩展文件中注册
CRITICAL: After creating the widget-specific extension file, you MUST read wix-cli-extension-registration and follow the "App Registration" section to update .
src/extensions.tsWithout completing Step 2, the site widget will not be available in the Wix Editor.
重要提示: 创建小组件专属扩展文件后,您必须阅读wix-cli-extension-registration并按照“应用注册”部分的说明更新。
src/extensions.ts未完成步骤2,站点小组件将无法在Wix编辑器中使用。
Code Quality Requirements
代码质量要求
- Strict TypeScript (no , explicit return types)
any - Functional React components with hooks
- Proper error handling and loading states
- No comments
@ts-ignore - Inline styles only (no CSS imports)
- Handle Wix Editor environment when using Wix Data API
- Consistent prop naming (camelCase in widget, kebab-case in panel)
- 严格的TypeScript(禁止,显式返回类型)
any - 使用Hooks的React函数式组件
- 适当的错误处理和加载状态
- 禁止注释
@ts-ignore - 仅使用内联样式(禁止CSS导入)
- 使用Wix Data API时适配Wix编辑器环境
- 一致的属性命名(小组件中使用小驼峰,面板中使用短横线)
Verification
验证
After implementation completes, the wix-cli-orchestrator will run validation using wix-cli-app-validation.
实现完成后,wix-cli-orchestrator将使用wix-cli-app-validation运行验证。