@wix/agent-skills 1.16.0 → 1.16.1
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 +18 -3
- package/skills/wix-app/references/editor-react-component/A11Y-REVIEW.md +12 -7
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONFIGURATION.md +60 -0
- package/skills/wix-app/references/editor-react-component/DIRECTIONALITY.md +6 -6
- package/skills/wix-app/references/editor-react-component/SITE-CONTEXT-HOOKS.md +187 -0
- package/skills/wix-app/scripts/scan-a11y-code.cjs +777 -0
- package/skills/wix-app/scripts/scan-a11y-eslint.cjs +201 -0
- package/skills/wix-headless/references/CAPABILITIES.md +16 -3
- package/skills/wix-headless/references/SDK_HANDOFF.md +1 -1
- package/skills/wix-headless/references/SEED.md +1 -1
- package/skills/wix-headless/references/inline-recipes/how-to-code-forms.md +560 -102
- package/skills/wix-headless/references/inline-recipes/setup-forms.md +210 -143
- package/skills/wix-manage/SKILL.md +8 -1
- package/skills/wix-manage/references/accessibility/scan-site-accessibility.md +98 -0
- package/skills/wix-manage/references/analytics/analytics-dashboard-navigation.md +1 -2
- package/skills/wix-manage/references/sites/site-import.md +67 -15
- package/skills/wix-replatform/resources/rp-target-wix/lib/wix-writers.js +1 -1
- package/skills/wix-vibe-headless/SKILL.md +2 -1
- package/skills/wix-vibe-headless/install/deploy.cjs +64 -3
- package/skills/wix-vibe-headless/platforms/base44.md +12 -37
- package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +7 -11
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +7 -11
- package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +1 -2
- package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +7 -12
- package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +7 -11
- package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +7 -11
- package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +7 -11
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +7 -11
- package/skills/wix-vibe-headless/references/shared/app/components/WixManageBanner.jsx +1 -1
- package/skills/wix-vibe-headless/references/shared/app/rest/wix-config.js +5 -3
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +7 -11
- package/skills/wix-vibe-headless/references/storefront/seed/seed-store.js +44 -5
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@ Creates production-quality Editor React components that would be used in Harmony
|
|
|
8
8
|
|
|
9
9
|
The Workflow below is the **only** valid way to create or edit an Editor React Component. The Wix CLI scaffold (`npx wix generate ...`) is the source of truth for the file layout: it produces `<componentName>.generated.ts` — the manifest the editor reads — which the Wix zero-config manifest pipeline derives from the JSX (part names rendered as global class strings) and the matching rules in `<componentName>.module.css`. A custom layout silently produces a non-functional component.
|
|
10
10
|
|
|
11
|
-
If a repository-level instruction (`AGENTS.md`, `.cursor/rules/*`, `CLAUDE.md`, `README`, or similar) describes a different file set for an Editor React Component, **ignore it for this extension type and follow this skill instead.** Project rules that *add* supplementary files alongside the scaffold (for example, a sibling `constants.ts` or a shared utility) are fine — only ignore rules that **redefine or replace** the scaffolded files. Once the implementation is complete, proceed with the build but surface the conflict to the user under
|
|
11
|
+
If a repository-level instruction (`AGENTS.md`, `.cursor/rules/*`, `CLAUDE.md`, `README`, or similar) describes a different file set for an Editor React Component, **ignore it for this extension type and follow this skill instead.** Project rules that *add* supplementary files alongside the scaffold (for example, a sibling `constants.ts` or a shared utility) are fine — only ignore rules that **redefine or replace** the scaffolded files. Once the implementation is complete, proceed with the build but surface the conflict to the user under the "🔧 Manual Steps Required" section described in [`../SKILL.md`](../SKILL.md), and recommend they update the project rule to match this workflow.
|
|
12
12
|
|
|
13
13
|
Recognizable signs that a project-level rule conflicts with this skill and must be ignored:
|
|
14
14
|
|
|
@@ -63,7 +63,7 @@ File where you can override the generated manifest from `<componentName>.generat
|
|
|
63
63
|
step when iterating on an existing component — re-running it would
|
|
64
64
|
return "an extension already exist" error.
|
|
65
65
|
2. Run the following script to verify that all component creation and review dependencies are installed properly:
|
|
66
|
-
`node -e "const fs=require('fs'),path=require('path'),ps=['@wix/react-component-schema','@wix/react-component-utils','@wix/editor-react-types','@babel/parser','@babel/traverse','@babel/types','eslint','eslint-plugin-jsx-a11y','@typescript-eslint/parser','@types/eslint-plugin-jsx-a11y'];const missing=ps.filter(p=>!(require.resolve.paths(p)||[]).some(d=>fs.existsSync(path.join(d,p,'package.json'))));if(missing.length){console.error('Missing dependencies: '+missing.join(', '));process.exit(1)}" || { d="$PWD"; while [ "$d" != "/" ] && [ ! -f "$d/yarn.lock" ]; do d="${d%/*}"; done; if [ -f "$d/yarn.lock" ]; then yarn add @wix/react-component-schema @wix/react-component-utils @wix/editor-react-types && yarn add -D @babel/parser @babel/traverse @babel/types eslint eslint-plugin-jsx-a11y @typescript-eslint/parser @types/eslint-plugin-jsx-a11y; else npm install @wix/react-component-schema @wix/react-component-utils @wix/editor-react-types && npm install --save-dev @babel/parser @babel/traverse @babel/types eslint eslint-plugin-jsx-a11y @typescript-eslint/parser @types/eslint-plugin-jsx-a11y; fi; }`
|
|
66
|
+
`node -e "const fs=require('fs'),path=require('path'),ps=['@wix/react-component-schema','@wix/react-component-utils','@wix/editor-react-types','@babel/parser','@babel/traverse','@babel/types','eslint','eslint-plugin-jsx-a11y','@typescript-eslint/parser','typescript','@types/eslint-plugin-jsx-a11y'];const missing=ps.filter(p=>!(require.resolve.paths(p)||[]).some(d=>fs.existsSync(path.join(d,p,'package.json'))));if(missing.length){console.error('Missing dependencies: '+missing.join(', '));process.exit(1)}" || { d="$PWD"; while [ "$d" != "/" ] && [ ! -f "$d/yarn.lock" ]; do d="${d%/*}"; done; if [ -f "$d/yarn.lock" ]; then yarn add @wix/react-component-schema @wix/react-component-utils @wix/editor-react-types && yarn add -D @babel/parser @babel/traverse @babel/types eslint eslint-plugin-jsx-a11y @typescript-eslint/parser typescript @types/eslint-plugin-jsx-a11y; else npm install @wix/react-component-schema @wix/react-component-utils @wix/editor-react-types && npm install --save-dev @babel/parser @babel/traverse @babel/types eslint eslint-plugin-jsx-a11y @typescript-eslint/parser typescript @types/eslint-plugin-jsx-a11y; fi; }`
|
|
67
67
|
3. Edit the generated React and CSS files in
|
|
68
68
|
`src/extensions/site/components/component-name/`. Apply
|
|
69
69
|
[`editor-react-component/DESIGN-STATES.md`](editor-react-component/DESIGN-STATES.md) while
|
|
@@ -88,6 +88,21 @@ animated GIF/SVG, canvas/WebGL loop, video-like surface), or has an `autoPlay`
|
|
|
88
88
|
generated `component.preview.tsx` MUST be modified to suppress autoplay in the
|
|
89
89
|
editor — follow [`editor-react-component/ANIMATED-COMPONENTS.md`](editor-react-component/ANIMATED-COMPONENTS.md).
|
|
90
90
|
|
|
91
|
+
If the component needs **runtime site context** — something no prop can carry —
|
|
92
|
+
read [`editor-react-component/SITE-CONTEXT-HOOKS.md`](editor-react-component/SITE-CONTEXT-HOOKS.md)
|
|
93
|
+
before writing the code. Triggers, and the only cases that warrant it:
|
|
94
|
+
|
|
95
|
+
- The component reflects **where the visitor is** in the site (breadcrumbs, "you
|
|
96
|
+
are here" indicators) — not navigation menus, whose items the site owner
|
|
97
|
+
authors and which take a `MenuItems` prop.
|
|
98
|
+
- The component needs the page's **own URL** (share, copy-link, canonical).
|
|
99
|
+
- The site's language direction drives **JavaScript** (keyboard navigation,
|
|
100
|
+
transform math, conditional rendering) — not just CSS.
|
|
101
|
+
- Behavior must differ while rendering inside the editor.
|
|
102
|
+
|
|
103
|
+
Otherwise, do not reach for a hook at all — data the site owner can type or pick
|
|
104
|
+
belongs in props.
|
|
105
|
+
|
|
91
106
|
Reference: when modifying an _existing_ component, follow
|
|
92
107
|
[`editor-react-component/EDIT-FLOW.md`](editor-react-component/EDIT-FLOW.md).
|
|
93
108
|
|
|
@@ -105,6 +120,7 @@ Topic-focused references (rules + patterns + common mistakes in one place):
|
|
|
105
120
|
- [`editor-react-component/COMPONENT-API.md`](editor-react-component/COMPONENT-API.md) — Props structure, elementProps, data types, file splitting, containers, array props
|
|
106
121
|
- [`editor-react-component/ANIMATED-COMPONENTS.md`](editor-react-component/ANIMATED-COMPONENTS.md) — Play/pause control and autoplay for animated/playable components
|
|
107
122
|
- [`editor-react-component/COMPONENT-PREVIEW.md`](editor-react-component/COMPONENT-PREVIEW.md) — Editor-specific entry point (`component.preview.tsx`), `useIsEditMode()`, when to modify
|
|
123
|
+
- [`editor-react-component/SITE-CONTEXT-HOOKS.md`](editor-react-component/SITE-CONTEXT-HOOKS.md) — Hooks for runtime site context (site pages, current URL, language direction, editor mode) — read only when a trigger above applies
|
|
108
124
|
- [`editor-react-component/REACT-PATTERNS.md`](editor-react-component/REACT-PATTERNS.md) — SSR-safe patterns, CSS rules, remaining common mistakes
|
|
109
125
|
|
|
110
126
|
## CSS guidelines
|
|
@@ -112,4 +128,3 @@ Topic-focused references (rules + patterns + common mistakes in one place):
|
|
|
112
128
|
Reference: [`editor-react-component/CSS-GUIDELINES.md`](editor-react-component/CSS-GUIDELINES.md).
|
|
113
129
|
|
|
114
130
|
For branded or themed components, also apply: [`editor-react-component/BRANDED-COMPONENTS.md`](editor-react-component/BRANDED-COMPONENTS.md).
|
|
115
|
-
|
|
@@ -2,18 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
Use this reference to audit and fix accessibility issues in an Editor React Component. This complements [`ACCESSIBILITY.md`](ACCESSIBILITY.md), which covers ARIA prop conventions and patterns, with an automated scan and triage workflow over the component's runtime, manifest extension, and shared code.
|
|
4
4
|
|
|
5
|
-
This is not a separate skill. It runs as part of the Editor React Component workflow described in [`../
|
|
5
|
+
This is not a separate skill. It runs as part of the Editor React Component workflow described in [`../EDITOR_REACT_COMPONENT.md`](../EDITOR_REACT_COMPONENT.md). Run it after editing the React/CSS sources and before `npx wix build && npx wix generate manifest`.
|
|
6
6
|
|
|
7
7
|
## Scanner Invocation
|
|
8
8
|
|
|
9
|
-
Two scanners
|
|
9
|
+
Two scanners are bundled under `<SKILL_ROOT>/scripts`, where `<SKILL_ROOT>` is
|
|
10
|
+
the absolute path to the directory containing the active skill's `SKILL.md`:
|
|
10
11
|
|
|
11
12
|
```bash
|
|
12
|
-
node scripts/scan-a11y-eslint.cjs <file1> [file2] ...
|
|
13
|
-
node scripts/scan-a11y-code.cjs <file1> [file2] ...
|
|
13
|
+
node <SKILL_ROOT>/scripts/scan-a11y-eslint.cjs <file1> [file2] ...
|
|
14
|
+
node <SKILL_ROOT>/scripts/scan-a11y-code.cjs <file1> [file2] ...
|
|
14
15
|
```
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
Replace `<SKILL_ROOT>` with that absolute path, but run the commands from the
|
|
18
|
+
consumer project root (the Wix CLI app's working directory) so dependencies
|
|
19
|
+
resolve from the project's `package.json`. Pass target paths relative to that
|
|
20
|
+
cwd. The dependency preflight is part of the workflow in
|
|
21
|
+
[`../EDITOR_REACT_COMPONENT.md`](../EDITOR_REACT_COMPONENT.md).
|
|
17
22
|
|
|
18
23
|
The ESLint scanner uses the explicitly declared `eslint-plugin-jsx-a11y` dev dependency. The semantic scanner follows imports up to four levels deep and adds resolution evidence and confidence to its findings.
|
|
19
24
|
|
|
@@ -52,7 +57,7 @@ Execute every phase inline while editing. Do not pause for approval or add a sep
|
|
|
52
57
|
### Phase 2a: ESLint JSX A11y Scan (Tier 0)
|
|
53
58
|
|
|
54
59
|
```bash
|
|
55
|
-
node scripts/scan-a11y-eslint.cjs <file1> [file2] ...
|
|
60
|
+
node <SKILL_ROOT>/scripts/scan-a11y-eslint.cjs <file1> [file2] ...
|
|
56
61
|
```
|
|
57
62
|
|
|
58
63
|
Use each finding's rule ID, message, and location as the rule-specific context. Validate the finding against the component implementation before editing.
|
|
@@ -60,7 +65,7 @@ Use each finding's rule ID, message, and location as the rule-specific context.
|
|
|
60
65
|
### Phase 2b: Semantic Scan (Tier 1)
|
|
61
66
|
|
|
62
67
|
```bash
|
|
63
|
-
node scripts/scan-a11y-code.cjs <file1> [file2] ...
|
|
68
|
+
node <SKILL_ROOT>/scripts/scan-a11y-code.cjs <file1> [file2] ...
|
|
64
69
|
```
|
|
65
70
|
|
|
66
71
|
Use the emitted resolution path, evidence, and confidence to trace each finding. The scanner output is the source of rule-specific details; the reference only defines how to evaluate and act on those details.
|
|
@@ -163,3 +163,63 @@ const componentExtension = extensions.editorReactComponent({
|
|
|
163
163
|
},
|
|
164
164
|
});
|
|
165
165
|
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 5. Adding range constraints for numeric props
|
|
170
|
+
|
|
171
|
+
The zero-config pipeline reads `@min` and `@max` JSDoc tags on numeric props and
|
|
172
|
+
emits them as `number: { min, max }` in the generated manifest. Add these tags
|
|
173
|
+
directly on the prop in the component's props interface — no extension file
|
|
174
|
+
override is needed.
|
|
175
|
+
|
|
176
|
+
### Heuristic — does this number have a fixed domain?
|
|
177
|
+
|
|
178
|
+
| Scenario | Action |
|
|
179
|
+
|----------|--------|
|
|
180
|
+
| Rating 0–5, column count 1–6, items per row 1–4 | Add `@min` / `@max` |
|
|
181
|
+
| Volume 0–100, playback speed 0.5–5, autoplay delay 0.5–30 | Add `@min` / `@max` |
|
|
182
|
+
| Geo coordinates, zoom level | Add `@min` / `@max` |
|
|
183
|
+
| Arbitrary count, free-form price, unconstrained input | Omit — leave as `number` |
|
|
184
|
+
| Semantic index (active tab, active slide, active item) | Omit — upper bound is driven by the component's own item count, not a fixed domain |
|
|
185
|
+
|
|
186
|
+
### How to write the tags
|
|
187
|
+
|
|
188
|
+
Add `@min` and `@max` inline in the JSDoc comment for the prop in `<componentName>.props.ts`:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
export interface RatingWidgetProps {
|
|
192
|
+
/** @min 0 @max 5 */
|
|
193
|
+
rating: number;
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Zero-config picks these up during `npx wix build && npx wix generate manifest`
|
|
198
|
+
and emits:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
rating: {
|
|
202
|
+
dataType: 'number',
|
|
203
|
+
number: { min: '0', max: '5' },
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Examples
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
// Star rating 0–5
|
|
211
|
+
/** @min 0 @max 5 */
|
|
212
|
+
rating: number;
|
|
213
|
+
|
|
214
|
+
// Volume 0–100
|
|
215
|
+
/** @min 0 @max 100 */
|
|
216
|
+
volume: number;
|
|
217
|
+
|
|
218
|
+
// Playback speed 0.5–5
|
|
219
|
+
/** @min 0.5 @max 5 */
|
|
220
|
+
speed: number;
|
|
221
|
+
|
|
222
|
+
// Column count 1–6
|
|
223
|
+
/** @min 1 @max 6 */
|
|
224
|
+
columns: number;
|
|
225
|
+
```
|
|
@@ -98,19 +98,19 @@ const labelDirection = elementProps?.label?.direction;
|
|
|
98
98
|
|
|
99
99
|
### Runtime Direction Checking
|
|
100
100
|
|
|
101
|
-
**
|
|
101
|
+
**Site direction hook:**
|
|
102
102
|
|
|
103
|
-
Use
|
|
103
|
+
Use the hook **only** when direction drives JavaScript behavior (keyboard navigation, animation logic, conditional rendering). For purely visual RTL, use logical CSS properties instead — see below.
|
|
104
104
|
|
|
105
105
|
```typescript
|
|
106
|
-
import {
|
|
107
|
-
import { EnvironmentDefinition } from '@wix/environment-service/definition';
|
|
106
|
+
import { useLanguageDirection } from '@wix/react-component-utils';
|
|
108
107
|
|
|
109
|
-
const
|
|
110
|
-
const siteDirection = environmentService.getLanguageDirection();
|
|
108
|
+
const siteDirection = useLanguageDirection();
|
|
111
109
|
const isRTL = siteDirection === 'rtl';
|
|
112
110
|
```
|
|
113
111
|
|
|
112
|
+
See [`SITE-CONTEXT-HOOKS.md`](SITE-CONTEXT-HOOKS.md) for the full set of site context hooks.
|
|
113
|
+
|
|
114
114
|
**Fallback chain (for child components):**
|
|
115
115
|
|
|
116
116
|
```typescript
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Site Context Hooks
|
|
2
|
+
|
|
3
|
+
Rules and patterns for reading **runtime site context** in Editor React
|
|
4
|
+
components — the site's page tree, the current URL, the language direction, and
|
|
5
|
+
editor mode. None of these can be carried by a prop.
|
|
6
|
+
|
|
7
|
+
All of them come from hooks in `@wix/react-component-utils`, already a base
|
|
8
|
+
dependency. No additional install is needed, but the hooks require
|
|
9
|
+
`@wix/react-component-utils` ≥ 1.12.0.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## When this applies
|
|
14
|
+
|
|
15
|
+
Apply only when the component genuinely needs live site context:
|
|
16
|
+
|
|
17
|
+
- **The component reflects where the visitor is in the site** — breadcrumbs,
|
|
18
|
+
"you are here" indicators. Not navigation menus: the site owner authors those,
|
|
19
|
+
and their items need not necessarily be site pages, so they take a
|
|
20
|
+
`MenuItems` prop
|
|
21
|
+
- **The component needs the page's own URL** — share buttons, copy-link,
|
|
22
|
+
canonical links, QR codes
|
|
23
|
+
- **Language direction drives JavaScript** — keyboard navigation, transform or
|
|
24
|
+
animation math, conditional rendering
|
|
25
|
+
- **Behavior must differ inside the editor** — suppressing autoplay, network
|
|
26
|
+
calls, or timers while the site owner is designing
|
|
27
|
+
|
|
28
|
+
The test is whether the site owner **authors** the data. A hook reports state the
|
|
29
|
+
owner cannot write — which page is being rendered, what the site's URL is. Data
|
|
30
|
+
the owner chooses belongs in props, even when it happens to point at site
|
|
31
|
+
pages — see [`PROPS-VS-CSS.md`](PROPS-VS-CSS.md) and
|
|
32
|
+
[`COMPONENT-API.md`](COMPONENT-API.md). Purely visual RTL needs no hook
|
|
33
|
+
either — use logical CSS properties, see
|
|
34
|
+
[`DIRECTIONALITY.md`](DIRECTIONALITY.md).
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Rules
|
|
39
|
+
|
|
40
|
+
### The Available Hooks
|
|
41
|
+
|
|
42
|
+
| Hook | Returns | Use for |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| `usePages()` | `Readonly<Record<string, PageConfig>>` | Every page on the site, keyed by page id |
|
|
45
|
+
| `useCurrentPageId()` | `string` | Id of the page being rendered |
|
|
46
|
+
| `useMainPageId()` | `string` | Id of the site's homepage |
|
|
47
|
+
| `useSiteUrl()` | `string` | The site's public base URL |
|
|
48
|
+
| `useCurrentUrl()` | `string` | Full URL of the page being rendered |
|
|
49
|
+
| `useLanguageDirection()` | `'ltr' \| 'rtl'` | The site's language direction |
|
|
50
|
+
| `useIsEditMode()` | `boolean` | `true` in editor design mode — see [`COMPONENT-PREVIEW.md`](COMPONENT-PREVIEW.md) |
|
|
51
|
+
| `useReducedMotion()` | `boolean` | `true` when the OS requests reduced motion — see [`ANIMATED-COMPONENTS.md`](ANIMATED-COMPONENTS.md) |
|
|
52
|
+
|
|
53
|
+
All import from the same place:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { usePages, useCurrentPageId, useSiteUrl } from '@wix/react-component-utils';
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Each returns a plain value, so there is nothing to unwrap or guard. They are
|
|
60
|
+
React hooks — call them at the top of the component or hook body, never at module
|
|
61
|
+
scope, inside a callback, or conditionally.
|
|
62
|
+
|
|
63
|
+
### Read Values During Render
|
|
64
|
+
|
|
65
|
+
Hook values are read on the server too, so their results are part of the server
|
|
66
|
+
markup — see [`SSR.md`](SSR.md). Use the value directly.
|
|
67
|
+
|
|
68
|
+
**✅ Correct:**
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
const siteUrl = useSiteUrl();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**❌ Wrong — empty on the server, then a hydration mismatch:**
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
const resolvedUrl = useSiteUrl();
|
|
78
|
+
const [siteUrl, setSiteUrl] = useState('');
|
|
79
|
+
useEffect(() => setSiteUrl(resolvedUrl), []);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Patterns
|
|
85
|
+
|
|
86
|
+
### Walking the Page Tree
|
|
87
|
+
|
|
88
|
+
`usePages()` returns a map keyed by page id. Three things to get right:
|
|
89
|
+
|
|
90
|
+
- **The map key is the page id** — the values carry no `id` field.
|
|
91
|
+
- **`parentPageId` is absent for top-level pages** — treat those as children of
|
|
92
|
+
`useMainPageId()` when walking upward.
|
|
93
|
+
- **`popup: true` pages are lightboxes**, not navigable pages. Filter them out of
|
|
94
|
+
breadcrumbs, menus, and page pickers.
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
import { usePages, useCurrentPageId, useMainPageId } from '@wix/react-component-utils';
|
|
98
|
+
|
|
99
|
+
export const useBreadcrumbTrail = (): Array<{ id: string; label: string }> => {
|
|
100
|
+
const pages = usePages();
|
|
101
|
+
const currentPageId = useCurrentPageId();
|
|
102
|
+
const mainPageId = useMainPageId();
|
|
103
|
+
|
|
104
|
+
const trail: Array<{ id: string; label: string }> = [];
|
|
105
|
+
const visited = new Set<string>();
|
|
106
|
+
let pageId: string | undefined = currentPageId;
|
|
107
|
+
|
|
108
|
+
while (pageId && !visited.has(pageId) && pages[pageId]) {
|
|
109
|
+
visited.add(pageId);
|
|
110
|
+
const page = pages[pageId]!;
|
|
111
|
+
if (!page.popup) {
|
|
112
|
+
trail.push({ id: pageId, label: page.title });
|
|
113
|
+
}
|
|
114
|
+
// Pages without parentPageId are top-level children of the homepage.
|
|
115
|
+
pageId = pageId === mainPageId ? undefined : (page.parentPageId ?? mainPageId);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return trail.reverse();
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The `visited` set is not optional — a misconfigured site can produce a parent
|
|
123
|
+
cycle, and an unguarded walk hangs the render.
|
|
124
|
+
|
|
125
|
+
### Building a Link to a Page
|
|
126
|
+
|
|
127
|
+
Join a page's `path` onto `useSiteUrl()`. The base URL may or may not carry a
|
|
128
|
+
trailing slash, so normalize before joining:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
const buildPageUrl = (path: string, siteUrl: string): string => {
|
|
132
|
+
const base = siteUrl.replace(/\/$/, '');
|
|
133
|
+
return path ? `${base}/${path}` : base;
|
|
134
|
+
};
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Direction-Dependent Logic
|
|
138
|
+
|
|
139
|
+
Resolve the component's own `direction` prop first, then fall back to the site:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import type { Direction } from '@wix/editor-react-types';
|
|
143
|
+
import { useLanguageDirection } from '@wix/react-component-utils';
|
|
144
|
+
|
|
145
|
+
export const useResolvedDirection = (direction?: Direction): Direction => {
|
|
146
|
+
// Call unconditionally — `direction ?? useLanguageDirection()` would
|
|
147
|
+
// short-circuit and skip the hook whenever the prop is set.
|
|
148
|
+
const siteDirection = useLanguageDirection() as Direction;
|
|
149
|
+
return direction ?? siteDirection;
|
|
150
|
+
};
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Use the resolved value for **logic only** — arrow-key handling, transform sign,
|
|
154
|
+
scroll direction:
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
const isRTL = useResolvedDirection(direction) === 'rtl';
|
|
158
|
+
|
|
159
|
+
const onKeyDown = (event: React.KeyboardEvent) => {
|
|
160
|
+
const forward = isRTL ? 'ArrowLeft' : 'ArrowRight';
|
|
161
|
+
if (event.key === forward) {
|
|
162
|
+
focusNext();
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Common Mistake: Direction Logic Instead of Logical CSS
|
|
170
|
+
|
|
171
|
+
Calling `useLanguageDirection()` to pick between physical CSS properties.
|
|
172
|
+
|
|
173
|
+
**❌ Wrong:**
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
const isRTL = useLanguageDirection() === 'rtl';
|
|
177
|
+
|
|
178
|
+
<div style={{ paddingLeft: isRTL ? 0 : 8, paddingRight: isRTL ? 8 : 0 }} />
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**✅ Correct:**
|
|
182
|
+
|
|
183
|
+
```scss
|
|
184
|
+
.element {
|
|
185
|
+
padding-inline-start: 8px; // Auto RTL/LTR, no hook needed
|
|
186
|
+
}
|
|
187
|
+
```
|