@ohhwells/bridge 0.1.16-next.8 → 0.1.17-next.15

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/README.md CHANGED
@@ -30,7 +30,7 @@ npm install @ohhwells/bridge
30
30
  In your root layout file, import the bridge stylesheet **once**:
31
31
 
32
32
  ```ts
33
- import '@ohhwells/bridge/styles'
33
+ import "@ohhwells/bridge/styles";
34
34
  ```
35
35
 
36
36
  ### 2. Add the loader surface
@@ -38,18 +38,20 @@ import '@ohhwells/bridge/styles'
38
38
  The loader is a full-screen spinner shown while the bridge fetches personalised content. It hides itself once content is ready. Add this **before** your main content in the `<body>`:
39
39
 
40
40
  ```tsx
41
- import { OHW_LOADER_STYLE, OhwLoaderSpinner } from '@ohhwells/bridge'
41
+ import { OHW_LOADER_STYLE, OhwLoaderSpinner } from "@ohhwells/bridge";
42
42
 
43
43
  // Inside <body>:
44
44
  <div
45
45
  id="ohw-loader"
46
46
  suppressHydrationWarning
47
- style={{ ...OHW_LOADER_STYLE, display: 'none' }}
47
+ style={{ ...OHW_LOADER_STYLE, display: "none" }}
48
48
  >
49
49
  <OhwLoaderSpinner />
50
- </div>
50
+ </div>;
51
51
 
52
- {/* Inline script — shows the loader immediately on the client before React hydrates */}
52
+ {
53
+ /* Inline script — shows the loader immediately on the client before React hydrates */
54
+ }
53
55
  <script
54
56
  dangerouslySetInnerHTML={{
55
57
  __html: `(function(){try{
@@ -61,7 +63,7 @@ import { OHW_LOADER_STYLE, OhwLoaderSpinner } from '@ohhwells/bridge'
61
63
  if(e)e.style.display="flex"
62
64
  }catch(e){}})();`,
63
65
  }}
64
- />
66
+ />;
65
67
  ```
66
68
 
67
69
  The inline script detects whether the page is being loaded under a subdomain (personalised content mode) and shows the loader before React has a chance to hydrate, preventing a flash of the default content.
@@ -71,8 +73,8 @@ The inline script detects whether the page is being loaded under a subdomain (pe
71
73
  Add `<OhhwellsBridge />` inside a `<Suspense>` boundary in your root layout. It must be in `<Suspense>` because it calls `useSearchParams()` internally.
72
74
 
73
75
  ```tsx
74
- import { Suspense } from 'react'
75
- import { OhhwellsBridge } from '@ohhwells/bridge'
76
+ import { Suspense } from "react";
77
+ import { OhhwellsBridge } from "@ohhwells/bridge";
76
78
 
77
79
  export default function RootLayout({ children }) {
78
80
  return (
@@ -88,7 +90,7 @@ export default function RootLayout({ children }) {
88
90
  {children}
89
91
  </body>
90
92
  </html>
91
- )
93
+ );
92
94
  }
93
95
  ```
94
96
 
@@ -97,6 +99,7 @@ The bridge activates automatically when the page is loaded inside the OhhWells c
97
99
  ### 4. Mark sections
98
100
 
99
101
  Every top-level section on each page must have a unique `data-ohw-section` attribute. This is what the canvas editor uses to:
102
+
100
103
  - Show the "Add Section" insert line between sections
101
104
  - Persist widget insertions (saving which section a widget was inserted after)
102
105
 
@@ -113,6 +116,7 @@ Every top-level section on each page must have a unique `data-ohw-section` attri
113
116
  ```
114
117
 
115
118
  **Naming rules:**
119
+
116
120
  - Use `kebab-case`
117
121
  - Must be unique across the entire page
118
122
  - Must be stable — if a section is renamed, any saved widget insertions referencing that name will break
@@ -129,7 +133,7 @@ export default function HomePage() {
129
133
  <section data-ohw-section="testimonials">...</section>
130
134
  <section data-ohw-section="plan-form">...</section>
131
135
  </>
132
- )
136
+ );
133
137
  }
134
138
  ```
135
139
 
@@ -138,39 +142,42 @@ export default function HomePage() {
138
142
  Add `data-ohw-editable` and `data-ohw-key` to any element the studio owner should be able to edit:
139
143
 
140
144
  ```tsx
141
- {/* Editable rich text (bold, italic, etc.) */}
142
- <h1
143
- data-ohw-editable="text"
144
- data-ohw-key="hero-heading"
145
- >
145
+ {
146
+ /* Editable rich text (bold, italic, etc.) */
147
+ }
148
+ <h1 data-ohw-editable="text" data-ohw-key="hero-heading">
146
149
  Welcome to the studio
147
- </h1>
150
+ </h1>;
148
151
 
149
- {/* Editable plain text (no formatting) */}
150
- <p
151
- data-ohw-editable="plain"
152
- data-ohw-key="hero-subtitle"
153
- >
152
+ {
153
+ /* Editable plain text (no formatting) */
154
+ }
155
+ <p data-ohw-editable="plain" data-ohw-key="hero-subtitle">
154
156
  Book your first class
155
- </p>
157
+ </p>;
156
158
 
157
- {/* Editable image */}
159
+ {
160
+ /* Editable image */
161
+ }
158
162
  <img
159
163
  data-ohw-editable="image"
160
164
  data-ohw-key="hero-image"
161
165
  src="/hero.jpg"
162
166
  alt="Hero"
163
- />
167
+ />;
164
168
 
165
- {/* Editable background image */}
169
+ {
170
+ /* Editable background image */
171
+ }
166
172
  <div
167
173
  data-ohw-editable="bg-image"
168
174
  data-ohw-key="hero-bg"
169
- style={{ backgroundImage: 'url(/bg.jpg)' }}
170
- />
175
+ style={{ backgroundImage: "url(/bg.jpg)" }}
176
+ />;
171
177
  ```
172
178
 
173
179
  **Key naming rules:**
180
+
174
181
  - Must be globally unique across all pages
175
182
  - Use `kebab-case`
176
183
  - Must be stable — changing a key orphans any saved content for that element
@@ -186,15 +193,9 @@ For elements with multiple display states (e.g. a contact form with default/succ
186
193
  data-ohw-editable-state="default,success,error"
187
194
  data-ohw-key="contact-form"
188
195
  >
189
- <div data-ohw-state-view="default">
190
- {/* default form UI */}
191
- </div>
192
- <div data-ohw-state-view="success">
193
- {/* success message */}
194
- </div>
195
- <div data-ohw-state-view="error">
196
- {/* error message */}
197
- </div>
196
+ <div data-ohw-state-view="default">{/* default form UI */}</div>
197
+ <div data-ohw-state-view="success">{/* success message */}</div>
198
+ <div data-ohw-state-view="error">{/* error message */}</div>
198
199
  </div>
199
200
  ```
200
201
 
@@ -240,23 +241,57 @@ npm publish --access public
240
241
 
241
242
  ---
242
243
 
244
+ ## Link popover (Canvas Editor)
245
+
246
+ `LinkPopover` is rendered inside `OhhwellsBridge` (iframe portal) when editing link destinations. It implements the Figma shadcn kit panel (nodes 8365-7616 / 8382-4161) — anchored popover, not a centered dialog.
247
+
248
+ ```tsx
249
+ // Used internally by OhhwellsBridge — external consumers rarely mount this directly.
250
+ import { LinkPopover } from "@ohhwells/bridge";
251
+ ```
252
+
253
+ The iframe bridge reports page sections on `ow:ready`:
254
+
255
+ ```json
256
+ { "type": "ow:ready", "version": "1", "nodes": [...], "sections": [{ "id": "hero", "label": "Hero" }] }
257
+ ```
258
+
259
+ Templates must mark sections with `data-ohw-section` (and optional `data-ohw-section-label`).
260
+
261
+ ### Local link-editor testing (dev fixtures)
262
+
263
+ Without the canvas editor parent, add `ohw-fixtures=1` to the edit URL. This preloads Re:Bound pages and sections into the link popover:
264
+
265
+ ```
266
+ http://localhost:3000 /?mode=edit&ohw-fixtures=1
267
+ ```
268
+
269
+ 1. Click a nav link label (e.g. **Pricing**) → toolbar → chain icon
270
+ 2. Pick **About** in Destination → **Choose a section** → **Personal training**
271
+ 3. **Save** — href becomes `/about#personal-training`
272
+ 4. Open `http://localhost:3001/about#personal-training` (without `mode=edit`) to verify scroll
273
+
274
+ In edit mode link clicks are blocked; check `href` in DevTools or test on a normal page load.
275
+
276
+ ---
277
+
243
278
  ## Design tokens
244
279
 
245
280
  The package ships the full OhhWells design token set, scoped to `[data-ohw-bridge-root]` so styles never leak into the host template.
246
281
 
247
282
  ### Semantic colors (CSS variables on `[data-ohw-bridge-root]`)
248
283
 
249
- | Token | Light | Dark |
250
- |---|---|---|
251
- | `primary` | `#0f172a` | `#f8fafc` |
284
+ | Token | Light | Dark |
285
+ | -------------------- | --------- | --------- |
286
+ | `primary` | `#0f172a` | `#f8fafc` |
252
287
  | `primary-foreground` | `#f8fafc` | `#0f172a` |
253
- | `background` | `#ffffff` | `#020617` |
254
- | `foreground` | `#020617` | `#f8fafc` |
255
- | `muted` | `#f1f5f9` | `#1e293b` |
256
- | `muted-foreground` | `#64748b` | `#94a3b8` |
257
- | `border` | `#e2e8f0` | `#334155` |
258
- | `destructive` | `#dc2626` | `#7f1d1d` |
259
- | `success` | `#16a34a` | `#22c55e` |
288
+ | `background` | `#ffffff` | `#020617` |
289
+ | `foreground` | `#020617` | `#f8fafc` |
290
+ | `muted` | `#f1f5f9` | `#1e293b` |
291
+ | `muted-foreground` | `#64748b` | `#94a3b8` |
292
+ | `border` | `#e2e8f0` | `#334155` |
293
+ | `destructive` | `#dc2626` | `#7f1d1d` |
294
+ | `success` | `#16a34a` | `#22c55e` |
260
295
 
261
296
  ### Brand palette
262
297