@wix/agent-skills 1.16.3 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +134 -127
- package/skills/wix-app/references/SITE_PLUGIN.md +2 -2
- package/skills/wix-app/references/backend-event/COMMON-EVENTS.md +2 -2
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +182 -99
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +92 -48
- package/skills/wix-app/references/editor-react-component/BRANDED-COMPONENTS.md +15 -6
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +275 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +29 -42
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +153 -449
- package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +53 -29
- package/skills/wix-app/references/editor-react-component/DIRECTIONALITY.md +53 -158
- package/skills/wix-app/references/editor-react-component/EDITOR-EXTENSION-CONFIGURATION.md +78 -0
- package/skills/wix-app/references/editor-react-component/FUNCTION-HANDLERS.md +42 -134
- package/skills/wix-app/references/editor-react-component/MANIFEST-ERRORS.md +11 -3
- package/skills/wix-app/references/editor-react-component/PARTS.md +46 -24
- package/skills/wix-app/references/editor-react-component/PROPS-VS-CSS.md +63 -179
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +79 -163
- package/skills/wix-app/references/editor-react-component/SITE-CONTEXT-HOOKS.md +36 -21
- package/skills/wix-app/references/editor-react-component/SSR.md +30 -99
- package/skills/wix-base44-connector/SKILL.md +34 -17
- package/skills/wix-base44-connector/scripts/utils.js +33 -25
- package/skills/wix-headless/entry/bootstrap.mjs +9 -2
- package/skills/wix-headless/references/SDK_HANDOFF.md +1 -1
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +42 -23
- package/skills/wix-headless/references/inline-recipes/how-to-code-pricing-plans.md +10 -8
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-orders.md +27 -24
- package/skills/wix-headless/references/managed/AUTHENTICATION.md +5 -0
- package/skills/wix-manage/SKILL.md +11 -1
- package/skills/wix-manage/references/bookings/booking-system-integration-gaps.md +20 -13
- package/skills/wix-manage/references/bookings/end-to-end-booking-flow.md +24 -11
- package/skills/wix-manage/references/cms/cms-ecommerce-catalog-integration.md +5 -5
- package/skills/wix-manage/references/seo/manage-seo-tags.md +315 -0
- package/skills/wix-manage/references/seo/manage-url-redirects.md +250 -0
- package/skills/wix-manage/references/sites/query-sites.md +72 -161
- package/skills/wix-replatform/resources/rp-mapper/wix-business-solutions-taxonomy.md +5 -6
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +13 -9
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +10 -7
- package/skills/wix-vibe-headless/platforms/base44.md +13 -10
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +2 -0
- package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-checkout.js +9 -8
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +4 -1
- package/skills/wix-vibe-headless/references/restaurants/app/components/OrderCartDrawer.jsx +23 -11
- package/skills/wix-vibe-headless/references/restaurants/app/context/OrderCartContext.jsx +1 -1
- package/skills/wix-vibe-headless/references/restaurants/app/rest/wix-restaurants-ordering.js +29 -31
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +15 -8
- package/skills/wix-vibe-headless/references/storefront/app/components/CartDrawer.jsx +37 -38
- package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +2 -2
- package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-cart.js +35 -35
- package/skills/wix-app/references/editor-react-component/A11Y-REVIEW-REFERENCE.md +0 -172
- package/skills/wix-app/references/editor-react-component/A11Y-REVIEW.md +0 -143
- package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +0 -516
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONFIGURATION.md +0 -225
- package/skills/wix-app/references/editor-react-component/EDIT-FLOW.md +0 -6
- package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +0 -95
|
@@ -1,516 +0,0 @@
|
|
|
1
|
-
# Component API
|
|
2
|
-
|
|
3
|
-
Rules and patterns for props structure, elementProps, data types, file structure, containers, data-driven components, and array props.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Should This Use elementProps or CSS?
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
Is this element a named part (carries a global class string)?
|
|
11
|
-
│
|
|
12
|
-
├─ YES
|
|
13
|
-
│ └─ ✅ It gets an elementProps entry — spread it + merge its className.
|
|
14
|
-
│ Add config fields inside the entry only as needed
|
|
15
|
-
│ (data, direction, event handlers).
|
|
16
|
-
│
|
|
17
|
-
└─ NO → purely visual/decorative non-part
|
|
18
|
-
(icons, separators, decorations, layout wrappers,
|
|
19
|
-
elements only hidden/shown via CSS)
|
|
20
|
-
└─ ❌ CSS class only — no elementProps entry
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Rules
|
|
26
|
-
|
|
27
|
-
### Component Props Structure
|
|
28
|
-
|
|
29
|
-
```typescript
|
|
30
|
-
interface ComponentProps {
|
|
31
|
-
// Identity
|
|
32
|
-
id?: string;
|
|
33
|
-
className?: string;
|
|
34
|
-
|
|
35
|
-
// Mandatory features (from @wix/editor-react-types)
|
|
36
|
-
direction?: Direction;
|
|
37
|
-
a11y?: A11y; // ALL ARIA attributes come through this (ariaLabel, role, etc.)
|
|
38
|
-
|
|
39
|
-
// Component-specific props (NO children unless container-type - see container rules below)
|
|
40
|
-
label?: string;
|
|
41
|
-
items?: Array<ItemType>;
|
|
42
|
-
// ... from specification
|
|
43
|
-
// NEVER add: ariaLabel, ariaDescribedBy, role, etc. - use a11y prop instead
|
|
44
|
-
|
|
45
|
-
// Sub-component configuration (only if needed - see elementProps rules)
|
|
46
|
-
elementProps?: {
|
|
47
|
-
[partName]?: {
|
|
48
|
-
[configProp]?: string | boolean | number; // Only include if element needs config beyond className
|
|
49
|
-
direction?: Direction;
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### Propagate `elementProps` to inner elements
|
|
56
|
-
|
|
57
|
-
`elementProps` is a map keyed by the manifest's inner-element keys; each entry
|
|
58
|
-
carries that element's `className` (the editor sets it — this is how design
|
|
59
|
-
states reach the element) plus any data.
|
|
60
|
-
|
|
61
|
-
**Every named inner element — every element that carries a global class
|
|
62
|
-
string — gets an `elementProps` entry, and you spread that entry onto the
|
|
63
|
-
element.** This is mandatory, even when the element needs no other config:
|
|
64
|
-
without it the editor cannot style that element or drive its states.
|
|
65
|
-
|
|
66
|
-
How `className` gets merged depends on what the element is:
|
|
67
|
-
|
|
68
|
-
- **Raw HTML element** (`<button>`, `<li>`, `<div>`): merge
|
|
69
|
-
`elementProps?.<key>.className` inline, alongside the global string and module
|
|
70
|
-
class — there's no component to do it.
|
|
71
|
-
- **Sub-component built with this skill** (`<Inner/>`): the spread alone is
|
|
72
|
-
enough. `className` rides along inside it, and the sub-component merges it onto
|
|
73
|
-
its own root (every skill component merges its incoming `className`). Don't
|
|
74
|
-
merge it again at the call site.
|
|
75
|
-
|
|
76
|
-
```tsx
|
|
77
|
-
interface PlanCardProps {
|
|
78
|
-
className?: string;
|
|
79
|
-
elementProps?: {
|
|
80
|
-
cta?: { className?: string; href?: string };
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Raw HTML element — spread + merge className inline.
|
|
85
|
-
// Note: the elementProps key is the short part name (`cta`), while the global
|
|
86
|
-
// class is prefixed with the component name (`plan-card-cta`). Keep them as-is.
|
|
87
|
-
<button {...elementProps?.cta} className={classNames('plan-card-cta', styles.cta, elementProps?.cta?.className)}>
|
|
88
|
-
{label}
|
|
89
|
-
</button>
|
|
90
|
-
|
|
91
|
-
// Sub-component built with this skill — spread is enough (it merges className itself):
|
|
92
|
-
<PlanRow {...elementProps?.planRow} />
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
**Non-parts** (module-class-only elements: icons, decorations, separators,
|
|
96
|
-
layout wrappers — no global class) get no entry. Style them with CSS.
|
|
97
|
-
|
|
98
|
-
**Inside each entry**, add only the config the element needs (data, direction,
|
|
99
|
-
event handlers). Never add a bare `className?` as the *only* field to decide
|
|
100
|
-
whether an element qualifies — `className` always flows through; the entry
|
|
101
|
-
exists because the element is a named part.
|
|
102
|
-
|
|
103
|
-
### What Qualifies as a Part
|
|
104
|
-
|
|
105
|
-
See [`PARTS.md`](PARTS.md) for root election (Step 0), the mandatory filter, and full rules. The root gets no `elementProps` entry — its `className`, `a11y`, and `direction` arrive as top-level props.
|
|
106
|
-
|
|
107
|
-
### Derived values are not props
|
|
108
|
-
|
|
109
|
-
If a displayed value can be computed with a simple pure function from
|
|
110
|
-
other props and/or internal state, compute it internally — don't
|
|
111
|
-
expose it as a prop. Expose only the source inputs (use numeric types
|
|
112
|
-
when arithmetic is needed: `price: number`, not `price: string`).
|
|
113
|
-
|
|
114
|
-
**Example:** subtotal = `price × quantity` → computed inside the component, not a prop.
|
|
115
|
-
|
|
116
|
-
### Data-Driven Components (NO children in exported props)
|
|
117
|
-
|
|
118
|
-
- Component's **exported interface** must NOT accept `children` prop
|
|
119
|
-
- ALL content MUST come through explicit named props (see "Derived values" above for what stays internal):
|
|
120
|
-
- Text (labels, placeholders, messages) → `label`, `text`, `placeholder`, etc.
|
|
121
|
-
- Media (images, videos, icons) → `imageSrc`, `videoUrl`, `iconName`, etc.
|
|
122
|
-
- Links (URLs, hrefs) → `link`, `href`, `url`, etc.
|
|
123
|
-
- Collections (list items, options, menu items) → `items`, `options`, `menuItems`, etc.
|
|
124
|
-
- **Internal implementation** can use children for composition between sub-components
|
|
125
|
-
- Hardcoded values are ONLY for fallback defaults when props are undefined
|
|
126
|
-
|
|
127
|
-
**Exception — Container-type components:** Components whose purpose is to wrap arbitrary child elements (e.g., BoxContainer) MAY accept `children: React.ReactNode`. This applies only to structural containers — data-driven leaf components (Button, Tabs, Accordion, etc.) must NOT use `children`.
|
|
128
|
-
|
|
129
|
-
### Array Props: Data on Parent Only
|
|
130
|
-
|
|
131
|
-
When the parent component defines an array prop (e.g., `items`), child/item components receive a single item directly. They do NOT redeclare the data structure in their own props.
|
|
132
|
-
|
|
133
|
-
### Active-item components (one body visible at a time)
|
|
134
|
-
|
|
135
|
-
Apply when the UI shows **one array item body at a time** (tabs, slideshow,
|
|
136
|
-
steps). Skip for always-visible lists, multi-select, or multi-expand accordion.
|
|
137
|
-
|
|
138
|
-
- Each item needs **`name: string`** — Studio hat labels (not `label` or other keys).
|
|
139
|
-
- Pair the array with **`ActiveItemIndex<'arrayPropName'>`** from
|
|
140
|
-
`@wix/react-component-utils`; the type param must match the array prop name.
|
|
141
|
-
Default the index to `0` in `defaultProps`.
|
|
142
|
-
- Render **all** item bodies (`.map`); toggle visibility with `--active` +
|
|
143
|
-
`display: none` on inactive panels (functional visibility — see
|
|
144
|
-
[`PROPS-VS-CSS.md`](PROPS-VS-CSS.md)), plus `aria-hidden` and `inert` on
|
|
145
|
-
inactive bodies.
|
|
146
|
-
- Keyboard navigation between triggers; ARIA roles per
|
|
147
|
-
[`ACCESSIBILITY.md`](ACCESSIBILITY.md).
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
import type { ActiveItemIndex } from '@wix/react-component-utils';
|
|
151
|
-
|
|
152
|
-
export type ItemType = { name: string; body: string };
|
|
153
|
-
|
|
154
|
-
export type MyComponentProps = {
|
|
155
|
-
items: Array<ItemType>;
|
|
156
|
-
activeItem: ActiveItemIndex<'items'>;
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
export const defaultProps = {
|
|
160
|
-
items: [{ name: 'Item 1', body: '…' }],
|
|
161
|
-
activeItem: 0,
|
|
162
|
-
} satisfies Omit<MyComponentProps, 'id' | 'className'>;
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### Array Element Types: Always Objects with Named Keys
|
|
166
|
-
|
|
167
|
-
Array elements MUST be objects with named keys. This enables stable item identity (each item can carry its own `id`/`key`), non-breaking extension (new fields can be added later without changing the prop signature), and semantic naming (each value has meaning instead of being an opaque scalar).
|
|
168
|
-
|
|
169
|
-
**Allowed forms:**
|
|
170
|
-
|
|
171
|
-
- Inline object literal: `Array<{ key: ValueType, ... }>`
|
|
172
|
-
- Named interface where the interface itself is an object with named keys (e.g. `Array<AccordionItem>` is OK because `AccordionItem` is `{ name, content }`)
|
|
173
|
-
|
|
174
|
-
**Never allowed as the array element:**
|
|
175
|
-
|
|
176
|
-
- Primitives: `Array<string>`, `Array<number>`, `Array<boolean>`
|
|
177
|
-
- Leaf data types from `@wix/editor-react-types`: `Array<Image>`, `Array<Link>`, `Array<Video>`, `Array<Audio>`, `Array<VectorArt>`, `Array<RichText>`. Wrap them in an object instead.
|
|
178
|
-
- Nested arrays: `Array<Array<{cover: image, caption: string}>>` `Array<{items: Array<image>, caption: string}>`
|
|
179
|
-
|
|
180
|
-
**❌ Wrong:**
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
tags: Array<string>;
|
|
184
|
-
prices: Array<number>;
|
|
185
|
-
flags: Array<boolean>;
|
|
186
|
-
images: Array<Image>;
|
|
187
|
-
links: Array<Link>;
|
|
188
|
-
nestedArrays: Array<Array<any>>
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
**✅ Correct:**
|
|
192
|
-
|
|
193
|
-
```typescript
|
|
194
|
-
tags: Array<{ label: string }>;
|
|
195
|
-
prices: Array<{ amount: number }>;
|
|
196
|
-
flags: Array<{ enabled: boolean }>;
|
|
197
|
-
gallery: Array<{ image: Image, caption?: string }>;
|
|
198
|
-
links: Array<{ link: Link, label: string }>;
|
|
199
|
-
items: Array<AccordionItem>; // AccordionItem is { name, content }
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
### Container Components (Blackbox Content)
|
|
203
|
-
|
|
204
|
-
When a component specification indicates a "container" or "slot" area where users can add nested content, use `React.ReactNode` for that content prop.
|
|
205
|
-
|
|
206
|
-
**When to use `React.ReactNode` for a content prop:**
|
|
207
|
-
|
|
208
|
-
- The specification describes a "container" or "content area"
|
|
209
|
-
- Users should be able to add arbitrary nested components
|
|
210
|
-
- The component doesn't control what goes inside that area
|
|
211
|
-
|
|
212
|
-
**RTL Support:** Elements that render `React.ReactNode` content MUST have `dir="ltr"` to prevent RTL inheritance from the parent component. See [`DIRECTIONALITY.md`](DIRECTIONALITY.md).
|
|
213
|
-
|
|
214
|
-
### Component Data Types
|
|
215
|
-
|
|
216
|
-
When creating TypeScript interfaces for component props, use types from `@wix/editor-react-types` for more complex types, to see the allowed type from this library, look at the following file `node_modules/@wix/react-component-schema/dist/editor-react-types.d.ts`:
|
|
217
|
-
|
|
218
|
-
```typescript
|
|
219
|
-
import type { Link } from "@wix/editor-react-types"; // Reference at node_modules/@wix/react-component-schema/dist/editor-react-types.d.ts
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
For **function event handler props** (`onClick`, `onDblClick`, `onChange`, `onFocus`, `onBlur`, `onMouseIn`, `onMouseOut`) and custom callbacks, follow [`FUNCTION-HANDLERS.md`](FUNCTION-HANDLERS.md). Key rules:
|
|
223
|
-
|
|
224
|
-
- Use the exact SDK prop names from that file — the manifest system recognizes them
|
|
225
|
-
- Wire them to the correct DOM events (note: `onDblClick` → `onDoubleClick`, `onMouseIn` → `onMouseEnter`, `onMouseOut` → `onMouseLeave`)
|
|
226
|
-
- Custom callbacks not tied to DOM events use `() => void` (no React event parameter)
|
|
227
|
-
|
|
228
|
-
### External resources are forbidden
|
|
229
|
-
|
|
230
|
-
All resources rendered or fetched by the component (images, icons, fonts,
|
|
231
|
-
videos, audio, JSON data, etc.) MUST come from Wix services. Never reference
|
|
232
|
-
or call external (non-Wix) hosts — no `unsplash.com`, `placehold.co`,
|
|
233
|
-
`picsum.photos`, third-party CDN icon sets, or any custom backend the user
|
|
234
|
-
hasn't asked for. This rule covers both `src`/`href` attributes and any
|
|
235
|
-
runtime fetching.
|
|
236
|
-
|
|
237
|
-
Allowed image hosts: `static.wixstatic.com` (and other `*.wixstatic.com`
|
|
238
|
-
subdomains). Allowed data: values supplied through props (populated by the
|
|
239
|
-
editor) or imported local assets bundled with the component.
|
|
240
|
-
|
|
241
|
-
### Default values for `Image` props
|
|
242
|
-
|
|
243
|
-
Image defaults belong in the `<componentName>.props.ts` file's exported
|
|
244
|
-
`defaultProps` constant (consumed by `withDefaults(Component, defaultProps)` in
|
|
245
|
-
`component.tsx`). Use a Wix-hosted image from the Free-from-Wix public
|
|
246
|
-
media catalog and populate **only** `uri`, `url`, and `alt` — leave
|
|
247
|
-
`width`, `height`, `focalPoint`, etc. unset so the editor fills them when
|
|
248
|
-
the user picks a real image.
|
|
249
|
-
|
|
250
|
-
By default, use the canonical fallback URL from the examples below for
|
|
251
|
-
every `Image` default (`url` and `uri` derived from the same `fileName`).
|
|
252
|
-
**Only when the user explicitly asks for different/better default images**,
|
|
253
|
-
fetch candidates from the Wix Free-from-Wix catalog:
|
|
254
|
-
|
|
255
|
-
```
|
|
256
|
-
GET https://publicmedia.wix.com/public/light_items?guid=bca5cb9f-45d2-4b11-8d6c-c9a7e7bd2873%3Aglobal%3Awix&pageSize=20&pageNumber=1&language=en&tags=free&mediaType=picture
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
The endpoint is unauthenticated. Pick an item whose `displayTags` /
|
|
260
|
-
`title` fit the component's purpose, then build the `Image` default from
|
|
261
|
-
its `fileName`:
|
|
262
|
-
|
|
263
|
-
- `url` → `` `https://static.wixstatic.com/media/${fileName}` ``
|
|
264
|
-
- `uri` → `fileName`
|
|
265
|
-
- `alt` → a short human description (use the item's `title` or
|
|
266
|
-
`displayTags`)
|
|
267
|
-
|
|
268
|
-
Do not call this endpoint when the user hasn't asked for it — the
|
|
269
|
-
canonical fallback URL is fine for unattended scaffolds.
|
|
270
|
-
|
|
271
|
-
**Default image pool — use a different image for each slot:**
|
|
272
|
-
|
|
273
|
-
| # | fileName | Description |
|
|
274
|
-
|---|----------|-------------|
|
|
275
|
-
| 1 | `11062b_2f97b87dcea2446fa48e9ad9c5457ae1~mv2.jpg` | Tropical beach aerial |
|
|
276
|
-
| 2 | `11062b_73f31c7e7d3544c69dc8ecd8d34c5717~mv2.jpg` | Dead Sea landscape |
|
|
277
|
-
| 3 | `11062b_3682ebfcb08e4da5b3168b62819a1e68~mv2.jpg` | Palm tree sunset |
|
|
278
|
-
| 4 | `11062b_45e67783d39c4963ab9e4fc418173233~mv2.jpg` | Abstract pink waves |
|
|
279
|
-
| 5 | `11062b_4c11f014b0d04948b2e6f554076bc40a~mv2.jpg` | Coastal village aerial |
|
|
280
|
-
|
|
281
|
-
When a component needs **more than one** image default, cycle through
|
|
282
|
-
the pool above so every slot gets a visually distinct image. For a
|
|
283
|
-
single image, use image #1.
|
|
284
|
-
|
|
285
|
-
**Single `Image` prop:**
|
|
286
|
-
|
|
287
|
-
```typescript
|
|
288
|
-
export const defaultProps = {
|
|
289
|
-
image: {
|
|
290
|
-
url: "https://static.wixstatic.com/media/11062b_2f97b87dcea2446fa48e9ad9c5457ae1~mv2.jpg",
|
|
291
|
-
uri: "11062b_2f97b87dcea2446fa48e9ad9c5457ae1~mv2.jpg",
|
|
292
|
-
alt: "Default image",
|
|
293
|
-
},
|
|
294
|
-
} as const satisfies Omit<ExampleComponentProps, "id" | "className">;
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
**Array of objects with an `Image` field:**
|
|
298
|
-
|
|
299
|
-
```typescript
|
|
300
|
-
export const defaultProps = {
|
|
301
|
-
cards: [
|
|
302
|
-
{
|
|
303
|
-
title: "First card",
|
|
304
|
-
image: {
|
|
305
|
-
url: "https://static.wixstatic.com/media/11062b_2f97b87dcea2446fa48e9ad9c5457ae1~mv2.jpg",
|
|
306
|
-
uri: "11062b_2f97b87dcea2446fa48e9ad9c5457ae1~mv2.jpg",
|
|
307
|
-
alt: "First card image",
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
{
|
|
311
|
-
title: "Second card",
|
|
312
|
-
image: {
|
|
313
|
-
url: "https://static.wixstatic.com/media/11062b_73f31c7e7d3544c69dc8ecd8d34c5717~mv2.jpg",
|
|
314
|
-
uri: "11062b_73f31c7e7d3544c69dc8ecd8d34c5717~mv2.jpg",
|
|
315
|
-
alt: "Second card image",
|
|
316
|
-
},
|
|
317
|
-
},
|
|
318
|
-
],
|
|
319
|
-
} as const satisfies Omit<ExampleComponentProps, "id" | "className">;
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
**Inline fallback for HTML `src` / `href`:** when rendering a raw HTML
|
|
323
|
-
attribute that takes a URL (`<img src>`, `<source src>`, `<video poster>`,
|
|
324
|
-
`<a href>` for an image link, etc.) and the value could be empty, fall
|
|
325
|
-
back to the same Wix-hosted URL — never to an external host or a relative
|
|
326
|
-
path that doesn't exist:
|
|
327
|
-
|
|
328
|
-
```tsx
|
|
329
|
-
<img
|
|
330
|
-
src={
|
|
331
|
-
image?.url ||
|
|
332
|
-
"https://static.wixstatic.com/media/11062b_2f97b87dcea2446fa48e9ad9c5457ae1~mv2.jpg"
|
|
333
|
-
}
|
|
334
|
-
alt={image?.alt || "Default image"}
|
|
335
|
-
/>
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
### Component File Splitting
|
|
339
|
-
|
|
340
|
-
Split logical UI pieces into separate named components. Never write inline JSX blocks for distinct complex UI pieces.
|
|
341
|
-
|
|
342
|
-
**File structure for complex components (many subcomponents):**
|
|
343
|
-
|
|
344
|
-
```
|
|
345
|
-
ComponentName/
|
|
346
|
-
├── components/ # Subcomponents folder
|
|
347
|
-
│ ├── SubComponent1/
|
|
348
|
-
│ │ ├── SubComponent1.tsx
|
|
349
|
-
│ │ ├── SubComponent1.module.scss
|
|
350
|
-
│ │ └── index.ts
|
|
351
|
-
│ └── SubComponent2/
|
|
352
|
-
│ ├── SubComponent2.tsx
|
|
353
|
-
│ └── index.ts
|
|
354
|
-
├── hooks/ # Custom hooks folder
|
|
355
|
-
│ ├── index.ts
|
|
356
|
-
│ └── useCustomHook.ts
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
**When to extract a sub-component:**
|
|
360
|
-
|
|
361
|
-
- JSX block represents a logical complex unit (button, control, section, item)
|
|
362
|
-
- Component has its own props interface
|
|
363
|
-
- Logic is reusable or testable independently
|
|
364
|
-
- Block has more than 15-20 lines of JSX
|
|
365
|
-
|
|
366
|
-
---
|
|
367
|
-
|
|
368
|
-
## Patterns
|
|
369
|
-
|
|
370
|
-
### Data-Driven Pattern
|
|
371
|
-
|
|
372
|
-
**❌ Wrong:**
|
|
373
|
-
|
|
374
|
-
```typescript
|
|
375
|
-
export interface ButtonProps {
|
|
376
|
-
children: React.ReactNode; // ❌
|
|
377
|
-
}
|
|
378
|
-
<Button>Click Me</Button>
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
**✅ Correct:**
|
|
382
|
-
|
|
383
|
-
```typescript
|
|
384
|
-
export interface ButtonProps {
|
|
385
|
-
label?: string;
|
|
386
|
-
icon?: string;
|
|
387
|
-
}
|
|
388
|
-
<Button label={label} icon={icon} />
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
Component controls rendering internally:
|
|
392
|
-
|
|
393
|
-
```typescript
|
|
394
|
-
const List = ({ items }) => (
|
|
395
|
-
<div>
|
|
396
|
-
{items.map(item => (
|
|
397
|
-
<ListItem key={item.id}>{item.label}</ListItem>
|
|
398
|
-
))}
|
|
399
|
-
</div>
|
|
400
|
-
);
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
### Array Props Pattern
|
|
404
|
-
|
|
405
|
-
Parent defines array structure, child receives single item:
|
|
406
|
-
|
|
407
|
-
```typescript
|
|
408
|
-
// Parent
|
|
409
|
-
interface ParentProps {
|
|
410
|
-
items: Array<AccordionItem>; // Array defined here
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
interface AccordionItem {
|
|
414
|
-
name: string;
|
|
415
|
-
content: React.ReactNode;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Child receives single item
|
|
419
|
-
interface ChildProps {
|
|
420
|
-
item: AccordionItem & { id: string }; // Single item
|
|
421
|
-
isOpen?: boolean;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// Parent maps
|
|
425
|
-
items.map((item, index) => (
|
|
426
|
-
<Child key={index} item={{ ...item, id: index.toString() }} />
|
|
427
|
-
))
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
### Container Components Pattern
|
|
431
|
-
|
|
432
|
-
For "container" or "slot" areas, use `React.ReactNode`:
|
|
433
|
-
|
|
434
|
-
```typescript
|
|
435
|
-
interface AccordionItem {
|
|
436
|
-
name: string;
|
|
437
|
-
content: React.ReactNode; // Users add any content here
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Render
|
|
441
|
-
{items.map((item, index) => (
|
|
442
|
-
<div key={index} dir="ltr">
|
|
443
|
-
{typeof item.content === 'function' ? item.content({}) : item.content}
|
|
444
|
-
</div>
|
|
445
|
-
))}
|
|
446
|
-
```
|
|
447
|
-
|
|
448
|
-
---
|
|
449
|
-
|
|
450
|
-
## Common Mistakes
|
|
451
|
-
|
|
452
|
-
### Using children prop in exported interface
|
|
453
|
-
|
|
454
|
-
**❌ Wrong:**
|
|
455
|
-
|
|
456
|
-
```typescript
|
|
457
|
-
export interface CardProps {
|
|
458
|
-
children: React.ReactNode; // ❌ children + React.ReactNode both forbidden
|
|
459
|
-
}
|
|
460
|
-
<Card><CardHeader>Title</CardHeader></Card>
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
**✅ Correct:**
|
|
464
|
-
|
|
465
|
-
```typescript
|
|
466
|
-
export interface CardProps {
|
|
467
|
-
title: string;
|
|
468
|
-
content: React.ReactNode;
|
|
469
|
-
}
|
|
470
|
-
<Card title="Title" content={<div>Content</div>} />
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
### Adding elementProps for CSS-only elements
|
|
474
|
-
|
|
475
|
-
**❌ Wrong:**
|
|
476
|
-
|
|
477
|
-
```typescript
|
|
478
|
-
elementProps?: {
|
|
479
|
-
icon?: { className?: string; } // ❌ Only className
|
|
480
|
-
separator?: { className?: string; } // ❌ Only className
|
|
481
|
-
}
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
**✅ Correct:**
|
|
485
|
-
|
|
486
|
-
```typescript
|
|
487
|
-
// No elementProps for purely visual elements (including conditionally displayed)
|
|
488
|
-
// Style via CSS classes directly
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
**Why:** Don't declare a `className?:` field — `className` already flows through each named inner element's `elementProps` entry (see "Propagate `elementProps` to inner elements"). Conditional display is controlled via CSS, not React props.
|
|
492
|
-
|
|
493
|
-
### Using T[] array syntax
|
|
494
|
-
|
|
495
|
-
Use `Array<T>` over `T[]`.
|
|
496
|
-
|
|
497
|
-
**❌ Wrong:**
|
|
498
|
-
|
|
499
|
-
```typescript
|
|
500
|
-
interface ComponentProps {
|
|
501
|
-
items: Item[]; // ❌ Wrong syntax
|
|
502
|
-
tags: string[]; // ❌ Wrong syntax
|
|
503
|
-
tags: Array<string>; // ❌ Element must be an object with named keys
|
|
504
|
-
}
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
**✅ Correct:**
|
|
508
|
-
|
|
509
|
-
```typescript
|
|
510
|
-
interface ComponentProps {
|
|
511
|
-
items: Array<Item>; // ✅ Correct syntax
|
|
512
|
-
tags: Array<{ label: string }>; // ✅ Element is an object with named keys
|
|
513
|
-
}
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
See also: [Array Element Types: Always Objects with Named Keys](#array-element-types-always-objects-with-named-keys) — items must be objects with named keys.
|