@ohhwells/bridge 0.1.16 → 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
@@ -4,138 +4,301 @@ The OhhWells canvas editor bridge — a standalone npm package that enables inli
4
4
 
5
5
  ## What it does
6
6
 
7
- When a studio owner opens their site in the OhhWells dashboard canvas editor, the bridge:
7
+ When a studio owner opens their site in the OhhWells canvas editor, the bridge:
8
8
 
9
9
  - Connects the iframe (the live site) to the parent canvas editor via `postMessage`
10
10
  - Enables click-to-edit for text, images, and background images
11
11
  - Handles draft saving and content hydration
12
- - Provides state toggle UI for editing hidden content (hover states, form success/error views)
12
+ - Shows the "Add Section" insert line between sections in the canvas editor
13
+ - Provides state toggle UI for editing hidden content (hover states, form views)
13
14
  - Injects scoped styles that never leak into the host template
14
15
 
16
+ ---
17
+
15
18
  ## Installation
16
19
 
17
20
  ```bash
18
21
  npm install @ohhwells/bridge
19
22
  ```
20
23
 
21
- Import the styles once in your template's root layout:
24
+ ---
25
+
26
+ ## Template setup (required steps)
27
+
28
+ ### 1. Import styles
29
+
30
+ In your root layout file, import the bridge stylesheet **once**:
22
31
 
23
32
  ```ts
24
- import '@ohhwells/bridge/styles'
33
+ import "@ohhwells/bridge/styles";
25
34
  ```
26
35
 
27
- ## Usage
36
+ ### 2. Add the loader surface
28
37
 
29
- Add `OhhwellsBridge` to your template's root layout:
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>`:
30
39
 
31
40
  ```tsx
32
- import { OhhwellsBridge } from '@ohhwells/bridge'
41
+ import { OHW_LOADER_STYLE, OhwLoaderSpinner } from "@ohhwells/bridge";
42
+
43
+ // Inside <body>:
44
+ <div
45
+ id="ohw-loader"
46
+ suppressHydrationWarning
47
+ style={{ ...OHW_LOADER_STYLE, display: "none" }}
48
+ >
49
+ <OhwLoaderSpinner />
50
+ </div>;
51
+
52
+ {
53
+ /* Inline script — shows the loader immediately on the client before React hydrates */
54
+ }
55
+ <script
56
+ dangerouslySetInnerHTML={{
57
+ __html: `(function(){try{
58
+ var p=location.hostname.split(".");
59
+ var fromHost=p.length>=3&&p[0]!=="www"?p[0]:"";
60
+ var fromQuery=new URLSearchParams(location.search).get("subdomain")||"";
61
+ if(!fromHost&&!fromQuery)return;
62
+ var e=document.getElementById("ohw-loader");
63
+ if(e)e.style.display="flex"
64
+ }catch(e){}})();`,
65
+ }}
66
+ />;
67
+ ```
68
+
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.
70
+
71
+ ### 3. Mount `OhhwellsBridge`
72
+
73
+ Add `<OhhwellsBridge />` inside a `<Suspense>` boundary in your root layout. It must be in `<Suspense>` because it calls `useSearchParams()` internally.
74
+
75
+ ```tsx
76
+ import { Suspense } from "react";
77
+ import { OhhwellsBridge } from "@ohhwells/bridge";
33
78
 
34
79
  export default function RootLayout({ children }) {
35
80
  return (
36
- <html>
81
+ <html lang="en">
37
82
  <body>
83
+ {/* loader + inline script here (see step 2) */}
84
+
85
+ <Suspense>
86
+ <OhhwellsBridge />
87
+ </Suspense>
88
+
89
+ {/* rest of your layout */}
38
90
  {children}
39
- <OhhwellsBridge />
40
91
  </body>
41
92
  </html>
42
- )
93
+ );
43
94
  }
44
95
  ```
45
96
 
46
- The bridge activates automatically when the page is loaded inside the OhhWells canvas editor. It has no effect in production (live site) mode.
97
+ The bridge activates automatically when the page is loaded inside the OhhWells canvas editor. It does nothing in production (live site) mode.
47
98
 
48
- ## Template attributes
99
+ ### 4. Mark sections
49
100
 
50
- Mark elements as editable using `data-ohw-*` attributes. See [VIBECODER.md](https://github.com/TheFlowOps-Eng/rebound-template/blob/main/VIBECODER.md) for the full authoring guide.
101
+ Every top-level section on each page must have a unique `data-ohw-section` attribute. This is what the canvas editor uses to:
51
102
 
52
- | Attribute | Value | Description |
53
- |---|---|---|
54
- | `data-ohw-key` | unique string | Identifier for storing/hydrating content |
55
- | `data-ohw-editable` | `text` \| `plain` \| `image` \| `bg-image` | Edit mode |
56
- | `data-ohw-editable-state` | `"hover,focus"` etc. | Declares available states on a container |
57
- | `data-ohw-state-view` | `"default"` \| `"success"` \| `"error"` \| `"hover"` | Wraps each state's content |
103
+ - Show the "Add Section" insert line between sections
104
+ - Persist widget insertions (saving which section a widget was inserted after)
58
105
 
59
- ## Design tokens
106
+ ```tsx
107
+ // Good — section element is the direct content root
108
+ <section data-ohw-section="hero">
109
+ ...
110
+ </section>
111
+
112
+ // Also fine — any element type works
113
+ <div data-ohw-section="testimonials">
114
+ ...
115
+ </div>
116
+ ```
60
117
 
61
- The package ships with the full OhhWells design token set, scoped to `[data-ohw-bridge]` so styles never leak into the host template.
118
+ **Naming rules:**
62
119
 
63
- ### Semantic colors
120
+ - Use `kebab-case`
121
+ - Must be unique across the entire page
122
+ - Must be stable — if a section is renamed, any saved widget insertions referencing that name will break
64
123
 
65
- All colors are defined as CSS variables on `[data-ohw-bridge-root]` and override-able per host:
124
+ **Example a page with multiple sections:**
66
125
 
67
- | Token | Light | Dark |
68
- |---|---|---|
69
- | `background` | `#ffffff` | `#020617` |
70
- | `foreground` | `#020617` | `#f8fafc` |
71
- | `primary` | `#0f172a` | `#f8fafc` |
72
- | `primary-foreground` | `#f8fafc` | `#0f172a` |
73
- | `secondary` | `#f1f5f9` | `#1e293b` |
74
- | `muted` | `#f1f5f9` | `#1e293b` |
75
- | `muted-foreground` | `#64748b` | `#94a3b8` |
76
- | `accent` | `#f1f5f9` | `#1e293b` |
77
- | `border` | `#e2e8f0` | `#334155` |
78
- | `destructive` | `#dc2626` | `#7f1d1d` |
79
- | `success` | `#16a34a` | `#22c55e` |
80
-
81
- ### Typography
82
-
83
- Standard Tailwind scale with Figma-specified tracking:
84
-
85
- | Class | Size | Line height | Tracking |
86
- |---|---|---|---|
87
- | `text-9xl` | 128px | 1 | -0.025em |
88
- | `text-8xl` | 96px | 1 | -0.025em |
89
- | `text-7xl` | 72px | 1 | -0.025em |
90
- | `text-6xl` | 60px | 1 | -0.025em |
91
- | `text-5xl` | 48px | 1 | -0.025em |
92
- | `text-4xl` | 36px | 40px | -0.025em |
93
- | `text-3xl` | 30px | 36px | -0.025em |
94
- | `text-2xl` | 24px | 32px | -0.025em |
95
- | `text-xl` | 20px | 28px | -0.025em |
96
- | `text-lg` | 18px | 28px | 0 |
97
- | `text-base` | 16px | 24px | 0 |
98
- | `text-sm` | 14px | 20px | 0 |
99
- | `text-xs` | 12px | 16px | 0 |
126
+ ```tsx
127
+ export default function HomePage() {
128
+ return (
129
+ <>
130
+ <section data-ohw-section="hero">...</section>
131
+ <section data-ohw-section="lagree-intro">...</section>
132
+ <section data-ohw-section="classes-strip">...</section>
133
+ <section data-ohw-section="testimonials">...</section>
134
+ <section data-ohw-section="plan-form">...</section>
135
+ </>
136
+ );
137
+ }
138
+ ```
100
139
 
101
- ### Brand palette
140
+ ### 5. Mark editable elements
102
141
 
103
- Re:Bound brand colors are available as named tokens: `bone`, `ivory`, `sand`, `linen`, `stone`, `clay`, `umber`, `umber-deep`, `espresso`, `clove`, `ink`, `ash`, `carbon`.
142
+ Add `data-ohw-editable` and `data-ohw-key` to any element the studio owner should be able to edit:
104
143
 
105
- ## Dark mode
144
+ ```tsx
145
+ {
146
+ /* Editable rich text (bold, italic, etc.) */
147
+ }
148
+ <h1 data-ohw-editable="text" data-ohw-key="hero-heading">
149
+ Welcome to the studio
150
+ </h1>;
106
151
 
107
- Add the `dark` class to `[data-ohw-bridge-root]` to activate dark token overrides:
152
+ {
153
+ /* Editable plain text (no formatting) */
154
+ }
155
+ <p data-ohw-editable="plain" data-ohw-key="hero-subtitle">
156
+ Book your first class
157
+ </p>;
108
158
 
109
- ```html
110
- <div data-ohw-bridge-root class="dark">...</div>
159
+ {
160
+ /* Editable image */
161
+ }
162
+ <img
163
+ data-ohw-editable="image"
164
+ data-ohw-key="hero-image"
165
+ src="/hero.jpg"
166
+ alt="Hero"
167
+ />;
168
+
169
+ {
170
+ /* Editable background image */
171
+ }
172
+ <div
173
+ data-ohw-editable="bg-image"
174
+ data-ohw-key="hero-bg"
175
+ style={{ backgroundImage: "url(/bg.jpg)" }}
176
+ />;
111
177
  ```
112
178
 
113
- ## Development
179
+ **Key naming rules:**
114
180
 
115
- ```bash
116
- # Install dependencies
117
- npm install
181
+ - Must be globally unique across all pages
182
+ - Use `kebab-case`
183
+ - Must be stable — changing a key orphans any saved content for that element
184
+
185
+ ---
186
+
187
+ ## Editable states (advanced)
188
+
189
+ For elements with multiple display states (e.g. a contact form with default/success/error views), wrap each state in a `data-ohw-state-view` and mark the container with `data-ohw-editable-state`:
190
+
191
+ ```tsx
192
+ <div
193
+ data-ohw-editable-state="default,success,error"
194
+ data-ohw-key="contact-form"
195
+ >
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>
199
+ </div>
200
+ ```
201
+
202
+ The bridge shows a state toggle in the canvas editor to switch between states.
203
+
204
+ ---
205
+
206
+ ## Local development workflow
118
207
 
119
- # Build (TypeScript + CSS)
208
+ When iterating on the bridge package itself:
209
+
210
+ ```bash
211
+ # 1. Build the package
212
+ cd ohhwells-bridge
120
213
  npm run build
121
214
 
122
- # Watch mode
123
- npm run dev
215
+ # 2. Link it globally (one-time setup)
216
+ npm link
217
+
218
+ # 3. In your template repo, use the local build instead of the npm version
219
+ cd rebound-template
220
+ npm link @ohhwells/bridge
221
+ ```
222
+
223
+ After that, every `npm run build` in `ohhwells-bridge` is picked up by the template immediately (no re-link needed). To go back to the npm version:
224
+
225
+ ```bash
226
+ cd rebound-template
227
+ npm unlink @ohhwells/bridge
228
+ npm install
124
229
  ```
125
230
 
126
- Output is written to `dist/`:
127
- - `dist/index.js` — CJS
128
- - `dist/index.mjs` — ESM
129
- - `dist/index.d.ts` — TypeScript declarations
130
- - `dist/styles.css` — Scoped Tailwind CSS
231
+ ---
131
232
 
132
233
  ## Publishing
133
234
 
235
+ The package publishes automatically via GitHub Actions on push to `main` (production tag) or `staging` (next tag). To publish manually:
236
+
134
237
  ```bash
135
238
  npm run build
136
239
  npm publish --access public
137
240
  ```
138
241
 
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
+
278
+ ## Design tokens
279
+
280
+ The package ships the full OhhWells design token set, scoped to `[data-ohw-bridge-root]` so styles never leak into the host template.
281
+
282
+ ### Semantic colors (CSS variables on `[data-ohw-bridge-root]`)
283
+
284
+ | Token | Light | Dark |
285
+ | -------------------- | --------- | --------- |
286
+ | `primary` | `#0f172a` | `#f8fafc` |
287
+ | `primary-foreground` | `#f8fafc` | `#0f172a` |
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` |
295
+
296
+ ### Brand palette
297
+
298
+ Re:Bound brand colors: `bone`, `ivory`, `sand`, `linen`, `stone`, `clay`, `umber`, `umber-deep`, `espresso`, `clove`, `ink`, `ash`, `carbon`.
299
+
300
+ ---
301
+
139
302
  ## License
140
303
 
141
304
  MIT