wix-cli-site-component

仓库: wix/skills
安装量: 118
排名: #7263

安装

npx skills add https://github.com/wix/skills --skill wix-cli-site-component

Wix Site Component Builder

Creates production-quality React site components with editor manifests for Wix CLI applications. Site components are React components that integrate with the Wix Editor, allowing site owners to customize content, styling, and behavior through a visual interface.

Non-Matching Intents

Do NOT use this skill for:

Dashboard admin interfaces → Use wix-cli-dashboard-page Site widgets with settings panels → Use wix-cli-site-widget Embedded scripts (HTML/JavaScript injection) → Use wix-cli-embedded-script Backend API endpoints → Use wix-cli-backend-api Service plugins (eCommerce SPIs) → Use wix-cli-service-plugin Architecture

Site components consist of four required files:

  1. Component Manifest (manifest.json)

Defines the contract between the React component and Wix ecosystem:

editorElement: Root element configuration (selector, displayName, archetype, layout) cssProperties: CSS API for styling customization data: Data API for content configuration elements: Nested element definitions for granular editing behaviors: Editor interaction behaviors 2. React Component (component.tsx)

Production-ready React functional component:

Implements props interface matching manifest data structure Applies className and id to root element Handles element removal state via wix.elementsRemovalState Uses sub-component pattern for nested elements Includes proper TypeScript typing and error handling 3. CSS Styles (style.css)

Modern CSS with responsive design:

Synced selectors with manifest and React classNames CSS variables for dynamic styling Responsive design without media queries (flexbox, grid, clamp) Pointer events enabled for editor elements No inline styles for static values Each selector once only, box-sizing: border-box all elements NO transition: all, NO media queries (except prefers-reduced-motion) Root display: Declare --display: [value] CSS variable, then use display: var(--display) on root 4. TypeScript Types (types.ts)

Strict type definitions:

Props interfaces for all components Data type mappings (text → string, image → Image object) Element props structure with optional chaining Wix system types (Wix interface, REMOVED type) Component Manifest Structure

You MUST read MANIFEST_GUIDELINES.md before implementing a site component. It contains the complete manifest structure, all data types, element configurations, and required patterns.

The manifest defines the editor contract using these key sections:

editorElement (Root Configuration) { "selector": ".component-name", "displayName": "Component Name", "archetype": "Container", "layout": { "resizeDirection": "horizontalAndVertical", "contentResizeDirection": "horizontal" }, "cssProperties": { "backgroundColor": { "displayName": "Background Color", "defaultValue": "#ffffff" } }, "data": { "columns": { "dataType": "number", "displayName": "Number of Columns", "number": { "minimum": 1, "maximum": 4 } } }, "elements": { "title": { "elementType": "inlineElement", "inlineElement": { "selector": ".component-name__title", "displayName": "Title", "data": { "titleText": { "dataType": "text", "displayName": "Title Text" } }, "behaviors": { "selectable": true, "removable": true } } } } }

Data Types Reference Type Runtime Value Use Case text string Names, titles, descriptions textEnum string Predefined options number number Quantities, dimensions booleanValue boolean Toggles, flags a11y Object Accessibility attributes link { href, target, rel } Navigation links image { uri, url, alt, width, height } Images video Video object Media content vectorArt Sanitized SVG object Icons, graphics localDate string (YYYY-MM-DD) Date values localTime string (hh:mm) Time values webUrl string External URLs richText string (HTML) Formatted content arrayItems Array Collections, lists direction string HTML dir attribute menuItems Array of menu items Navigation menus CSS Properties Reference

Common CSS properties for styling customization:

Layout: display, gap, padding, margin, width, height Typography: font, fontSize, fontWeight, textAlign, color Background: backgroundColor, backgroundImage Border: border, borderRadius, boxShadow Positioning: alignItems, justifyContent, flexDirection

Complete CSS properties reference: See CSS_GUIDELINES.md for all CSS properties, variable patterns, and styling best practices.

React Component Patterns

Complete reference: See REACT_PATTERNS.md for detailed component architecture, all coding patterns, and implementation examples.

Props Structure interface ComponentProps { // Standard props (always present) className: string; id: string; wix?: Wix;

// Component-level data (from editorElement.data) columns?: number; layout?: string;

// Element props (from elements definitions) elementProps?: { title?: { titleText?: string; wix?: Wix; }; button?: { buttonText?: string; buttonLink?: Link; wix?: Wix; }; }; }

Sub-Component Pattern

Extract every distinct UI element into named sub-components:

// Title sub-component interface TitleProps { titleText?: string; className: string; }

const Title: FC = ({ titleText = "Default Title", className }) => (

{titleText}

);

// Main component const ProductCard: FC = ({ className, id, elementProps, wix }) => { const removalState = wix?.elementsRemovalState || {};

return (

product-card ${className}} id={id}> {!removalState['title'] && ( )} {!removalState['button'] && ( <Button className="product-card__button" {...elementProps?.button} /> )} </div> ); };</p> <p>Conditional Rendering</p> <p>All elements must be conditionally rendered based on removal state:</p> <p>const removalState = wix?.elementsRemovalState || {};</p> <p>return ( <div className={<code>component ${className}</code>} id={id}> {!removalState['elementKey'] && <Element />} </div> );</p> <p>CSS Guidelines Responsive Design Strategy</p> <p>Components live in user-resizable containers (300-1200px) within varying viewports:</p> <p>Root element: width: 100%; height: 100% Layout structure: Use CSS Grid and Flexbox for fluid responsiveness Typography: Use clamp() for fluid scaling Spacing: Fixed or tight clamp spacing (≤50% variation) CSS Variables for Dynamic Styling .component { --display: block; --background-color: #ffffff; --text-color: #333333;</p> <p>display: var(--display); background-color: var(--background-color); color: var(--text-color); pointer-events: auto; }</p> <p>Selector Synchronization</p> <p>CRITICAL: CSS selectors must match manifest selectors and React classNames exactly:</p> <p>React: className="product-card__title" CSS: .product-card__title { ... } Manifest: "selector": ".product-card__title" Design Guidelines</p> <p>Complete reference: See DESIGN_SYSTEM.md for visual design principles, creative guidelines, and aesthetic best practices.</p> <p>Spacing as Communication Relationship Value Use Case Tight (icon + label) 0.25-0.5rem (4-8px) Clustering related items Same category 1-1.5rem (16-24px) Card sections, form fields Different sections 2-3rem (32-48px) Major content blocks Emphasis/Drama 4rem+ (64px+) Hero content, luxury feel Visual Consistency Corner Radius: All similar elements share same radius (0-4px sharp, 6-12px rounded) Shadow Levels: Max 3 levels (rest, hover, floating) Element Heights: Consistent heights for similar elements Color Strategy: Use full palette purposefully for hierarchy and zones Creative Exploration</p> <p>Push beyond obvious solutions:</p> <p>Cards: Asymmetric grids, overlapping elements, thick accent borders Lists: Alternating styles, spotlight patterns, color rhythm Interactive Elements: Split buttons, colored icon circles, smooth transitions Content Hierarchy: Large numbers for stats, quote callouts, whitespace dividers Component Elements Guidelines One Element = One Manifest Entry</p> <p>Each distinct visual part requires a separate manifest element:</p> <p>✅ 3 buttons → 3 separate elements ✅ Image + text → 2 separate elements ❌ Multiple items grouped as one element Data Scoping Rules</p> <p>editorElement.data - Component-wide configuration only:</p> <p>✅ Layout enums, numbers (columns: 3, speed: 500) ❌ Text, links, images (belongs to elements) ❌ show/hide booleans (use removable: true instead)</p> <p>elements[key].data - Content for that specific element:</p> <p>✅ Element-specific content (title text, button link, image) Asset Requirements</p> <p>When components need default images, use this format:</p> <p>// Import in component import { heroImage } from './assets/defaultImages';</p> <p>// Usage <img src={heroImage} alt="Hero" /></p> <p>Asset specification format:</p> <p><imageUrlName> { "description": "Modern cityscape at sunset", "width": 1920, "height": 1088 } </imageUrlName></p> <p>Rules:</p> <p>Import as named export from './assets/defaultImages' Width/height: multiples of 64, between 128-2048px NEVER use external URLs Output Structure src/site/components/ └── {component-name}/ ├── manifest.json # Component manifest ├── component.tsx # React component ├── style.css # CSS styles ├── types.ts # TypeScript types └── assets/ # Optional assets └── defaultImages.ts</p> <p>Examples</p> <p>Complete working example: See EXAMPLE.md for a full production-ready site component with all patterns, including manifest, React component, CSS, and types.</p> <p>Product Card Component</p> <p>Request: "Create a product card component with image, title, price, and buy button"</p> <p>Output:</p> <p>Manifest with 4 elements (image, title, price, button) React component with sub-components for each element CSS with responsive grid layout and hover effects TypeScript types for all props and data structures Hero Section Component</p> <p>Request: "Build a hero section with background image, headline, subtitle, and CTA button"</p> <p>Output:</p> <p>Manifest with background image CSS property and 3 text elements React component with overlay design and typography hierarchy CSS with responsive text scaling and dramatic spacing Asset specifications for default hero images Feature List Component</p> <p>Request: "Create a features component with configurable number of items"</p> <p>Output:</p> <p>Manifest with arrayItems data type for feature collection React component mapping over features array with safety checks CSS with flexible grid layout adapting to item count Sub-components for feature icons, titles, and descriptions Extension Registration</p> <p>Extension registration is MANDATORY and has TWO required steps.</p> <p>Step 1: Create Component-Specific Extension File</p> <p>Each site component requires an extensions.ts file in its folder:</p> <p>import { extensions } from "@wix/astro/builders"; import manifest from "./site/components/my-component/manifest.json";</p> <p>export const sitecomponentMyComponent = extensions.siteComponent({ ...manifest, id: "{{GENERATE_UUID}}", description: "My Component", type: "platform.builder.{{GENERATE_UUID}}", resources: { client: { component: "./site/components/my-component/component.tsx", componentUrl: "./site/components/my-component/component.tsx", }, }, });</p> <p>Note: The id and type should use the same UUID.</p> <p>CRITICAL: UUID Generation</p> <p>The id must be a unique, static UUID v4 string. Generate a fresh UUID for each extension - do NOT use randomUUID() or copy UUIDs from examples.</p> <p>Step 2: Register in Main Extensions File</p> <p>CRITICAL: After creating the component-specific extension file, you MUST read ../../skills/references/EXTENSIONS.md and follow the "App Registration" section to update src/extensions.ts.</p> <p>Without completing Step 2, the site component will not be available in the Wix Editor.</p> <p>Code Quality Requirements</p> <p>Complete reference: See TYPESCRIPT_QUALITY.md for comprehensive type safety guidelines and code quality standards.</p> <p>TypeScript Standards Strict TypeScript with no any types Explicit return types for all functions Proper null/undefined handling with optional chaining No @ts-ignore or @ts-expect-error comments React Best Practices Functional components with hooks Proper dependency arrays in useEffect Component must react to prop changes SSR-safe code (no browser APIs at module scope) ESLint Compliance No unused vars/params/imports (@typescript-eslint/no-unused-vars) No external images: img src not https://... (allowed: local imports, wixstatic.com, variables) SSR-safe: No window/document at module scope/constructor, guard browser APIs in useEffect/handlers No dangerouslySetInnerHTML or inline <style> tags - use CSS variables or inline style prop for dynamic values No window.fetch (no-restricted-properties) Hooks exhaustive-deps: ALL values from component scope used inside useEffect/useCallback MUST be in dependency array Use const/let (no var), no unknown JSX properties Hard Constraints Do NOT invent or assume new types, modules, functions, props, events, or imports Use only entities explicitly present in the provided references or standard libraries already used in this project Do NOT add dependencies; do NOT use @wix/design-system or @wix/wix-ui-icons-common All user-facing content must come from props (no hardcoded text) Links/media from manifest only, never hardcode URLs NEVER use mocks, placeholders, or TODOs in any code ALWAYS implement complete, production-ready functionality Verification</p> <p>After implementation, use wix-cli-app-validation to validate TypeScript compilation, build, preview, and runtime behavior.</p> <p>Reference Documentation Complete Example - Full production-ready site component example with all patterns Component Manifest Guidelines - Detailed manifest structure and best practices React Patterns - Component architecture and coding patterns CSS Guidelines - Styling conventions and responsive design Design System - Visual design principles and creative guidelines TypeScript Quality - Type safety and code quality standards</p> </article> <a href="/" class="back-link">← <span data-i18n="detail.backToLeaderboard">返回排行榜</span></a> </div> <aside class="sidebar"> <section class="related-skills" id="relatedSkillsSection"> <h2 class="related-title" data-i18n="detail.relatedSkills">相关 Skills</h2> <div class="related-list" id="relatedSkillsList"> <div class="skeleton-card"></div> <div class="skeleton-card"></div> <div class="skeleton-card"></div> </div> </section> </aside> </div> </div> <script src="https://unpkg.com/i18next@23.11.5/i18next.min.js" defer></script> <script src="https://unpkg.com/i18next-browser-languagedetector@7.2.1/i18nextBrowserLanguageDetector.min.js" defer></script> <script defer> // Language resources - same pattern as index page const resources = { 'zh-CN': null, 'en': null, 'ja': null, 'ko': null, 'zh-TW': null, 'es': null, 'fr': null }; // Load language files (only current + fallback for performance) async function loadLanguageResources() { const savedLang = localStorage.getItem('i18nextLng') || 'en'; const langsToLoad = new Set([savedLang, 'en']); // current + fallback await Promise.all([...langsToLoad].map(async (lang) => { try { const response = await fetch(`/locales/${lang}.json`); if (response.ok) { resources[lang] = { translation: await response.json() }; } } catch (error) { console.warn(`Failed to load ${lang} language file:`, error); } })); } // Load a single language on demand (for language switching) async function loadLanguage(lang) { if (resources[lang]) return; try { const response = await fetch(`/locales/${lang}.json`); if (response.ok) { resources[lang] = { translation: await response.json() }; i18next.addResourceBundle(lang, 'translation', resources[lang].translation); } } catch (error) { console.warn(`Failed to load ${lang} language file:`, error); } } // Initialize i18next async function initI18n() { try { await loadLanguageResources(); // Filter out null values from resources const validResources = {}; for (const [lang, data] of Object.entries(resources)) { if (data !== null) { validResources[lang] = data; } } console.log('Loaded languages:', Object.keys(validResources)); console.log('zh-CN resource:', validResources['zh-CN']); console.log('detail.home in resource:', validResources['zh-CN']?.translation?.detail?.home); // 检查是否有保存的语言偏好 const savedLang = localStorage.getItem('i18nextLng'); // 如果没有保存的语言偏好,默认使用英文 const defaultLang = savedLang && ['zh-CN', 'en', 'ja', 'ko', 'zh-TW', 'es', 'fr'].includes(savedLang) ? savedLang : 'en'; await i18next .use(i18nextBrowserLanguageDetector) .init({ lng: defaultLang, // 强制设置初始语言 fallbackLng: 'en', supportedLngs: ['zh-CN', 'en', 'ja', 'ko', 'zh-TW', 'es', 'fr'], resources: validResources, detection: { order: ['localStorage'], // 只使用 localStorage,不检测浏览器语言 caches: ['localStorage'], lookupLocalStorage: 'i18nextLng' }, interpolation: { escapeValue: false } }); console.log('i18next initialized, language:', i18next.language); console.log('Test translation:', i18next.t('detail.home')); // Set initial language in selector const langSwitcher = document.getElementById('langSwitcher'); langSwitcher.value = i18next.language; // Update page language updatePageLanguage(); // Language switch event langSwitcher.addEventListener('change', async (e) => { await loadLanguage(e.target.value); // load on demand i18next.changeLanguage(e.target.value).then(() => { updatePageLanguage(); localStorage.setItem('i18nextLng', e.target.value); }); }); } catch (error) { console.error('i18next init failed:', error); } } // Translation helper function t(key, options = {}) { return i18next.t(key, options); } // Update all translatable elements function updatePageLanguage() { // Update HTML lang attribute document.documentElement.lang = i18next.language; // Update elements with data-i18n attribute document.querySelectorAll('[data-i18n]').forEach(el => { const key = el.getAttribute('data-i18n'); el.textContent = t(key); }); } // Copy command function function copyCommand() { const command = document.getElementById('installCommand').textContent; const btn = document.getElementById('copyBtn'); navigator.clipboard.writeText(command).then(() => { btn.textContent = t('copied'); btn.classList.add('copied'); setTimeout(() => { btn.textContent = t('copy'); btn.classList.remove('copied'); }, 2000); }).catch(() => { // Fallback for non-HTTPS const textArea = document.createElement('textarea'); textArea.value = command; textArea.style.position = 'fixed'; textArea.style.left = '-9999px'; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); btn.textContent = t('copied'); btn.classList.add('copied'); setTimeout(() => { btn.textContent = t('copy'); btn.classList.remove('copied'); }, 2000); }); } // Initialize document.getElementById('copyBtn').addEventListener('click', copyCommand); initI18n(); // 异步加载相关 Skills async function loadRelatedSkills() { const owner = 'wix'; const skillName = 'wix-cli-site-component'; const currentLang = 'ko'; const listContainer = document.getElementById('relatedSkillsList'); const section = document.getElementById('relatedSkillsSection'); try { const response = await fetch(`/api/related-skills/${encodeURIComponent(owner)}/${encodeURIComponent(skillName)}?limit=6`); if (!response.ok) { throw new Error('Failed to load'); } const data = await response.json(); const relatedSkills = data.related_skills || []; if (relatedSkills.length === 0) { // 没有相关推荐时隐藏整个区域 section.style.display = 'none'; return; } // 渲染相关 Skills listContainer.innerHTML = relatedSkills.map(skill => { const desc = skill.description || ''; const truncatedDesc = desc.length > 60 ? desc.substring(0, 60) + '...' : desc; return ` <a href="${currentLang === 'en' ? '' : '/' + currentLang}/skill/${skill.owner}/${skill.repo}/${skill.skill_name}" class="related-card"> <div class="related-name">${escapeHtml(skill.skill_name)}</div> <div class="related-meta"> <span class="related-owner">${escapeHtml(skill.owner)}</span> <span class="related-installs">${skill.installs}</span> </div> <div class="related-desc">${escapeHtml(truncatedDesc)}</div> </a> `; }).join(''); } catch (error) { console.error('Failed to load related skills:', error); // 加载失败时显示提示或隐藏 listContainer.innerHTML = '<div class="related-empty">暂无相关推荐</div>'; } } // HTML 转义 function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // 页面加载完成后异步加载相关 Skills if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', loadRelatedSkills); } else { loadRelatedSkills(); } </script> </body> </html>