@sonordev/site-kit 7.0.1 → 7.1.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/CHANGELOG.md +3606 -0
- package/README.md +12 -13
- package/agent-manifest.json +11 -5
- package/dist/{AnalyticsProvider-EMM2TKRE.js → AnalyticsProvider-ZMQUV33M.js} +4 -4
- package/dist/{ArticleViewTracker-RA64BGL6.js → ArticleViewTracker-V7NUXYBY.js} +3 -3
- package/dist/{BlocksPopup-D25RFNOV.js → BlocksPopup-52EU7OUY.js} +4 -4
- package/dist/{ChatWidget-RYI7BMJJ.js → ChatWidget-5BHMNR57.js} +5 -5
- package/dist/{EngageWidget-UKFCN33M.js → EngageWidget-PCGLX7SO.js} +4 -4
- package/dist/{FileField-MUHA7LZR.js → FileField-TSFGNAMY.js} +3 -3
- package/dist/{FormSpotlight-TCLPWPLL.js → FormSpotlight-XLBWEOTE.js} +1 -1
- package/dist/{FormStage-CNYLP6I6.js → FormStage-IJQ5Q2X6.js} +1 -1
- package/dist/{ManagedForm-7ZL5SKTO.js → ManagedForm-Z3PKOPIZ.js} +6 -6
- package/dist/{ManagedNewsletterForm-33B4JLX7.js → ManagedNewsletterForm-QAO3POLC.js} +4 -4
- package/dist/{SignalCore-L5FVDHFE.js → SignalCore-RBA3VDBL.js} +3 -3
- package/dist/{SiteDesignReporter-4JOFL4FP.js → SiteDesignReporter-C4LR5X2V.js} +5 -5
- package/dist/SitemapSync-XVMGKCF3.js +8 -0
- package/dist/_client/booking-widget.js +5 -5
- package/dist/affiliates/index.js +3 -3
- package/dist/analytics/index.js +4 -4
- package/dist/articles/index.js +1 -1
- package/dist/articles/server-ui.js +1 -1
- package/dist/chat/index.js +5 -5
- package/dist/{chunk-FYBZ5SNP.js → chunk-3G2SE2J4.js} +1 -1
- package/dist/{chunk-HGCK465A.js → chunk-3J2ERO3I.js} +1 -1
- package/dist/{chunk-KXPBMCFL.js → chunk-3QI26673.js} +3 -1
- package/dist/{chunk-BMO3VGMR.js → chunk-3XPJKZ6D.js} +30 -7
- package/dist/{chunk-QGHSMJKW.js → chunk-4JQQDCMO.js} +1 -1
- package/dist/{chunk-6HDT4G4A.js → chunk-4YTYGG2C.js} +2 -2
- package/dist/{chunk-N2UVOR3X.js → chunk-662ILEZ6.js} +2 -0
- package/dist/chunk-6G43IRWR.js +4 -0
- package/dist/{chunk-LVESVYCE.js → chunk-7MHHWZKC.js} +11 -117
- package/dist/{chunk-MV2MBTC3.js → chunk-7QTMMHUO.js} +1 -1
- package/dist/{chunk-KPAZG65P.js → chunk-CGWUXUYZ.js} +138 -46
- package/dist/{chunk-4IQ52CXL.js → chunk-DUAO4Q75.js} +2 -2
- package/dist/{chunk-4RMVXRBO.js → chunk-EIULXXUJ.js} +3 -3
- package/dist/{chunk-3KUUH2YP.js → chunk-EVFZ7KEW.js} +1 -1
- package/dist/{chunk-OFOAHPUV.js → chunk-F42R35NV.js} +1 -1
- package/dist/{chunk-QANVUXKH.js → chunk-FLR3EMK6.js} +1 -1
- package/dist/{chunk-P4GRY6QP.js → chunk-GIAOPEN6.js} +1 -1
- package/dist/{chunk-P5J7VMQ3.js → chunk-GWUKQ26F.js} +1 -1
- package/dist/{chunk-TT63HHIT.js → chunk-HAG4YIZY.js} +1 -1
- package/dist/{chunk-QZZIKMAT.js → chunk-HVH37YPX.js} +1 -1
- package/dist/{chunk-SSUQKA7L.js → chunk-J4D6ZXRW.js} +1 -1
- package/dist/{chunk-GYESATRY.js → chunk-L2DJD5Y4.js} +1 -1
- package/dist/{chunk-WATH55UY.js → chunk-LFXVE32I.js} +1 -1
- package/dist/chunk-LPH5FANE.js +169 -0
- package/dist/{chunk-V6LSQRTH.js → chunk-PLUP2KN5.js} +1 -1
- package/dist/chunk-RYVDGXC2.js +19 -0
- package/dist/{chunk-EGOD74PP.js → chunk-U35H2JIQ.js} +2 -2
- package/dist/chunk-VCJYLYJV.js +49 -0
- package/dist/{chunk-FL4EPUWA.js → chunk-W2CL2DB3.js} +2 -2
- package/dist/{chunk-UZN4ZYR2.js → chunk-XD3ZQET6.js} +1 -1
- package/dist/{chunk-CVTVNC2U.js → chunk-XNVSCQ2O.js} +2 -2
- package/dist/{chunk-T3MC4HOD.js → chunk-YLSEB32F.js} +1 -1
- package/dist/chunk-ZETJTCMV.js +118 -0
- package/dist/{chunk-5SEM2V4A.js → chunk-ZIMFQWGJ.js} +3 -3
- package/dist/client/index.js +3 -3
- package/dist/cms/CmsPage.d.ts +1 -0
- package/dist/cms/CmsPreview.d.ts +1 -0
- package/dist/cms/CmsSection.d.ts +1 -0
- package/dist/cms/index.d.ts +6 -0
- package/dist/cms/server-api.d.ts +3 -0
- package/dist/commerce/index.js +4 -4
- package/dist/config/index.js +1 -1
- package/dist/contracts/entries.d.ts +1 -1
- package/dist/contracts/site-cache.d.ts +55 -0
- package/dist/contracts/site-edit-param.d.ts +7 -0
- package/dist/contracts/site-edit.d.ts +77 -0
- package/dist/contracts/slot-content.d.ts +111 -0
- package/dist/contracts/slots.d.ts +39 -25
- package/dist/engage/index.js +6 -6
- package/dist/fleet/index.js +4 -4
- package/dist/forms/index.js +8 -8
- package/dist/forms/server.js +2 -2
- package/dist/forms/types.d.ts +3 -1
- package/dist/images/index.js +4 -4
- package/dist/index.js +1 -1
- package/dist/layout/client.js +8 -7
- package/dist/layout/index.js +9 -8
- package/dist/llms/index.js +4 -2
- package/dist/llms/seo-revalidate.d.ts +8 -1
- package/dist/maps/index.js +3 -3
- package/dist/mcp/sonor.js +6 -6
- package/dist/overlay-RXV6U6QC.js +353 -0
- package/dist/proxy/index.js +2 -2
- package/dist/proxy/securityHeaders.d.ts +4 -0
- package/dist/revalidate/index.d.ts +44 -0
- package/dist/revalidate/index.js +27 -0
- package/dist/seo/ManagedContent.d.ts +2 -0
- package/dist/seo/client.js +4 -4
- package/dist/seo/index.js +9 -8
- package/dist/seo/llms.js +4 -2
- package/dist/seo/register-sitemap-cli.js +1 -1
- package/dist/seo/server.js +3 -2
- package/dist/seo/sitemap.js +2 -2
- package/dist/server/index.js +2 -2
- package/dist/{server-api-GJJQZVG7.js → server-api-BVCBLJKL.js} +2 -1
- package/dist/shared/build-entries.d.ts +1 -0
- package/dist/shared/edit-bridge.d.ts +8 -0
- package/dist/shared/version.d.ts +1 -1
- package/dist/signal/index.js +2 -2
- package/dist/sitemap/index.js +2 -2
- package/dist/slots/ManagedLink.d.ts +31 -0
- package/dist/slots/ManagedList.d.ts +30 -0
- package/dist/slots/ManagedRichText.d.ts +31 -0
- package/dist/slots/contract.js +2 -1
- package/dist/slots/edit/locate.d.ts +30 -0
- package/dist/slots/edit/overlay.d.ts +18 -0
- package/dist/slots/index.d.ts +12 -4
- package/dist/slots/index.js +4 -2
- package/dist/slots/revalidate.d.ts +8 -3
- package/dist/slots/rich.d.ts +7 -0
- package/dist/slots/server-api.d.ts +6 -2
- package/dist/sync/index.js +5 -5
- package/dist/website/images.js +4 -4
- package/dist/website/index.js +5 -5
- package/dist/website/popups.js +4 -4
- package/dist/website/slots/contract.js +2 -1
- package/dist/website/slots.js +4 -2
- package/dist/{writeLLMsTxt-UMHKGNRR.js → writeLLMsTxt-QR23OQUE.js} +1 -1
- package/docs/MIGRATING-TO-7.md +146 -0
- package/docs.json +69 -0
- package/package.json +14 -4
- package/src/admin-auth/README.md +88 -0
- package/src/analytics/README.md +264 -0
- package/src/articles/README.md +325 -0
- package/src/commerce/README.md +109 -0
- package/src/cta-bar/README.md +154 -0
- package/src/engage/README.md +241 -0
- package/src/forms/README.md +219 -0
- package/src/images/README.md +74 -0
- package/src/layout/README.md +66 -0
- package/src/llms/README.md +723 -0
- package/src/mcp/README.md +376 -0
- package/src/motion/README.md +372 -0
- package/src/og/README.md +304 -0
- package/src/proxy/README.md +152 -0
- package/src/redirects/README.md +74 -0
- package/src/reputation/README.md +64 -0
- package/src/revalidate/README.md +82 -0
- package/src/seo/README.md +346 -0
- package/src/signal/README.md +115 -0
- package/src/sitemap/README.md +127 -0
- package/src/slots/README.md +168 -0
- package/src/sync/README.md +115 -0
- package/dist/SitemapSync-7WKY4HXI.js +0 -8
- package/dist/chunk-SS636UDN.js +0 -35
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Forms — `@sonordev/site-kit/forms`
|
|
2
|
+
|
|
3
|
+
Sonor-managed forms with multi-step support, conditional logic, validation, anti-bot protection, and automatic CRM routing.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
### Option 1: Managed (simplest)
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { ManagedForm } from '@sonordev/site-kit/forms'
|
|
11
|
+
|
|
12
|
+
export default function ContactPage() {
|
|
13
|
+
return <ManagedForm formId="contact-form" />
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Fetches form config from Sonor, renders fields, handles submission, routes to CRM.
|
|
18
|
+
|
|
19
|
+
## Experiences (4.0)
|
|
20
|
+
|
|
21
|
+
Since 4.0 that one line gets you the **spotlight** experience by default: the
|
|
22
|
+
familiar layout, alive. A glowing ring visits the field you're in, completed
|
|
23
|
+
fields earn a check, and each finished row collapses into a sentence the form
|
|
24
|
+
says back — "Nice to meet you, Jordan." / "We'll follow up at jordan@…" — with
|
|
25
|
+
an edit control to reopen it.
|
|
26
|
+
|
|
27
|
+
| Experience | What it is | How to get it |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `spotlight` | **Default.** Fields perform in place; answered rows become sentences | nothing to do |
|
|
30
|
+
| `stage` | Accordion: one beat at a time on a single surface, answered beats settle into filled rows | `experience="stage"` |
|
|
31
|
+
| `classic` | The plain grid form, no choreography | `experience="classic"` |
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<ManagedForm formId="contact-form" /> // spotlight
|
|
35
|
+
<ManagedForm formId="contact-form" experience="stage" /> // accordion
|
|
36
|
+
<ManagedForm formId="contact-form" experience="classic" /> // opt out
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Sonor can decide instead of the site, with no deploy on either side — set the
|
|
40
|
+
form's `layout` to `classic`, `stage`, or `spotlight`. An explicit
|
|
41
|
+
`experience` prop always wins over the config.
|
|
42
|
+
|
|
43
|
+
Three things hold for every experience:
|
|
44
|
+
|
|
45
|
+
- **The lead is always capturable.** SSR and no-JS render the plain classic
|
|
46
|
+
form; the experience layers on after hydration. If its chunk never arrives,
|
|
47
|
+
the form still works and still submits.
|
|
48
|
+
- **Nobody pays for what they don't use.** Experience code is loaded on
|
|
49
|
+
demand, so a `classic` form downloads none of it.
|
|
50
|
+
- **Same engine.** Identical validation, honeypot, reCAPTCHA, and CRM routing
|
|
51
|
+
— an experience is a rendering, not a fork.
|
|
52
|
+
|
|
53
|
+
### Editing what the form says back
|
|
54
|
+
|
|
55
|
+
The sentence for a row comes from the kit's built-in wording unless you write
|
|
56
|
+
your own in Sonor: set **`completion_message`** on a field (the first answered
|
|
57
|
+
field in the row that has one wins). Tokens:
|
|
58
|
+
|
|
59
|
+
| Token | Resolves to |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `{value}` | this field's answer |
|
|
62
|
+
| `{label}` | this field's label |
|
|
63
|
+
| `{first_name}` | the form's first-name answer, if any |
|
|
64
|
+
| `{any_field_slug}` | that field's answer |
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
Welcome aboard, {first_name}. We'll reach you at {email}.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Leave it empty to keep the built-in sentence. Unknown or unanswered tokens
|
|
71
|
+
render as nothing — never raw braces.
|
|
72
|
+
|
|
73
|
+
### Option 2: Headless Hook (full UI control)
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
'use client'
|
|
77
|
+
import { useForm } from '@sonordev/site-kit/forms'
|
|
78
|
+
|
|
79
|
+
export function ContactForm() {
|
|
80
|
+
const {
|
|
81
|
+
fields, values, errors, setFieldValue, submit, isSubmitting,
|
|
82
|
+
step, totalSteps, isMultiStep, nextStep, prevStep, isLastStep,
|
|
83
|
+
} = useForm('contact-form')
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<form onSubmit={(e) => { e.preventDefault(); submit() }}>
|
|
87
|
+
{fields.map(field => (
|
|
88
|
+
<div key={field.slug}>
|
|
89
|
+
<label>{field.label}</label>
|
|
90
|
+
<input
|
|
91
|
+
value={String(values[field.slug] || '')}
|
|
92
|
+
onChange={(e) => setFieldValue(field.slug, e.target.value)}
|
|
93
|
+
/>
|
|
94
|
+
{errors[field.slug] && <span className="error">{errors[field.slug]}</span>}
|
|
95
|
+
</div>
|
|
96
|
+
))}
|
|
97
|
+
{isMultiStep && !isLastStep && <button type="button" onClick={nextStep}>Next</button>}
|
|
98
|
+
{isLastStep && <button type="submit" disabled={isSubmitting}>Submit</button>}
|
|
99
|
+
</form>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Option 3: Render Prop
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
<ManagedForm formId="contact-form">
|
|
108
|
+
{({ fields, values, setFieldValue, submit }) => (
|
|
109
|
+
<MyCustomFormUI fields={fields} values={values} onChange={setFieldValue} onSubmit={submit} />
|
|
110
|
+
)}
|
|
111
|
+
</ManagedForm>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Option 4: Programmatic API
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { formsApi, field, configureFormsApi } from '@sonordev/site-kit/forms'
|
|
118
|
+
|
|
119
|
+
configureFormsApi({ baseUrl: 'https://api.sonor.io', apiKey: 'sonor_...' })
|
|
120
|
+
|
|
121
|
+
const form = await formsApi.create({
|
|
122
|
+
projectId: 'xxx',
|
|
123
|
+
slug: 'newsletter',
|
|
124
|
+
name: 'Newsletter Signup',
|
|
125
|
+
formType: 'newsletter',
|
|
126
|
+
fields: [
|
|
127
|
+
field.email('email', 'Email', { isRequired: true }),
|
|
128
|
+
field.text('name', 'Name'),
|
|
129
|
+
],
|
|
130
|
+
})
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## ManagedForm Props
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
interface ManagedFormProps {
|
|
137
|
+
formId: string // Form slug or ID
|
|
138
|
+
className?: string
|
|
139
|
+
onSuccess?: (submission: FormSubmitResult) => void
|
|
140
|
+
onError?: (error: Error) => void
|
|
141
|
+
children?: (renderProps: UseFormReturn) => ReactNode // Render prop override
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## useForm Return
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
interface UseFormReturn {
|
|
149
|
+
form: ManagedFormConfig | null
|
|
150
|
+
isLoading: boolean
|
|
151
|
+
fetchError: Error | null
|
|
152
|
+
allFields: FormField[]
|
|
153
|
+
fields: FormField[] // Current step's fields
|
|
154
|
+
visibleFields: FormField[] // After conditional logic
|
|
155
|
+
values: Record<string, unknown>
|
|
156
|
+
errors: Record<string, string>
|
|
157
|
+
setFieldValue: (key: string, value: unknown) => void
|
|
158
|
+
step: number
|
|
159
|
+
totalSteps: number
|
|
160
|
+
isMultiStep: boolean
|
|
161
|
+
progress: number // 0-100
|
|
162
|
+
nextStep: () => void
|
|
163
|
+
prevStep: () => void
|
|
164
|
+
goToStep: (n: number) => void
|
|
165
|
+
canGoNext: boolean
|
|
166
|
+
canGoPrev: boolean
|
|
167
|
+
isLastStep: boolean
|
|
168
|
+
validate: () => boolean
|
|
169
|
+
submit: () => Promise<void>
|
|
170
|
+
isSubmitting: boolean
|
|
171
|
+
isComplete: boolean
|
|
172
|
+
reset: () => void
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Field Types
|
|
177
|
+
|
|
178
|
+
`text`, `email`, `phone`, `tel`, `number`, `textarea`, `select`, `multi-select`, `checkbox`, `radio`, `date`, `time`, `datetime`, `file`, `signature`, `rating`, `slider`, `hidden`, `heading`, `section_header`, `paragraph`
|
|
179
|
+
|
|
180
|
+
## Field Builder
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
field.text(slug, label, options?)
|
|
184
|
+
field.email(slug, label, options?)
|
|
185
|
+
field.phone(slug, label, options?)
|
|
186
|
+
field.textarea(slug, label, options?)
|
|
187
|
+
field.select(slug, label, { options: [{ value, label }] })
|
|
188
|
+
field.date(slug, label, options?)
|
|
189
|
+
field.checkbox(slug, label, options?)
|
|
190
|
+
field.rating(slug, label, options?)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Form Routing
|
|
194
|
+
|
|
195
|
+
Submissions auto-route based on `form_type`:
|
|
196
|
+
|
|
197
|
+
| Form Type | Routes To | Use Case |
|
|
198
|
+
|-----------|-----------|----------|
|
|
199
|
+
| `prospect` / `lead-capture` / `contact` | CRM Contacts | Sales inquiries, quotes |
|
|
200
|
+
| `support` | Support Tickets | Help requests |
|
|
201
|
+
| `feedback` | Feedback entries | User feedback |
|
|
202
|
+
| `newsletter` | Email Subscribers | Newsletter signups |
|
|
203
|
+
| `custom` | Form Submissions only | Custom handling |
|
|
204
|
+
|
|
205
|
+
## Anti-Bot Protection
|
|
206
|
+
|
|
207
|
+
- **Honeypot fields** — hidden fields that bots fill (server-side rejection)
|
|
208
|
+
- **reCAPTCHA Enterprise** — optional Google reCAPTCHA v3 scoring
|
|
209
|
+
- **Submission timing** — forms that submit in < 3 seconds are flagged
|
|
210
|
+
- **Composite spam scoring** — name patterns, email domain, user-agent, IP rate, message content
|
|
211
|
+
- **NestJS ThrottlerGuard** — 10 requests/minute per IP at HTTP level
|
|
212
|
+
|
|
213
|
+
## Styles
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
import '@sonordev/site-kit/forms/styles.css' // Optional default styles
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
All components use `.sk-form__*` class names. Override with `className` prop or CSS variables.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Images — `@sonordev/site-kit/images`
|
|
2
|
+
|
|
3
|
+
Sonor-managed image slots with dev-mode editing, focal point support, and image picker.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import { ManagedImage } from '@sonordev/site-kit/images'
|
|
9
|
+
|
|
10
|
+
export default function HeroSection() {
|
|
11
|
+
return (
|
|
12
|
+
<ManagedImage
|
|
13
|
+
slotId="hero-background"
|
|
14
|
+
alt="Hero background"
|
|
15
|
+
width={1920}
|
|
16
|
+
height={1080}
|
|
17
|
+
objectFit="cover"
|
|
18
|
+
fallback="/images/default-hero.jpg"
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Props
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
interface ManagedImageProps {
|
|
28
|
+
slotId: string // Unique slot identifier (e.g., 'hero-background')
|
|
29
|
+
pagePath?: string // Page-specific slots (defaults to current path)
|
|
30
|
+
alt?: string // Fallback alt text
|
|
31
|
+
className?: string
|
|
32
|
+
width?: number | string
|
|
33
|
+
height?: number | string
|
|
34
|
+
objectFit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down'
|
|
35
|
+
fallback?: string // Fallback image URL when slot is empty
|
|
36
|
+
placeholder?: React.ReactNode // Custom placeholder component
|
|
37
|
+
onLoad?: () => void
|
|
38
|
+
onError?: (error: Error) => void
|
|
39
|
+
priority?: boolean // Eager loading (Next.js Image priority)
|
|
40
|
+
style?: React.CSSProperties
|
|
41
|
+
forceDevMode?: boolean // Enable dev picker outside localhost
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Dev Mode
|
|
46
|
+
|
|
47
|
+
Dev mode is on when `NODE_ENV` is `development`, on `localhost` / `127.0.0.1`, on any host with `?sonor_dev=true` in the URL, or with `forceDevMode`:
|
|
48
|
+
- Click any `ManagedImage` to open the image picker modal
|
|
49
|
+
- Browse files by folder, search, upload new images
|
|
50
|
+
- Set alt text and focal point
|
|
51
|
+
- Shows slot ID in empty states for debugging
|
|
52
|
+
|
|
53
|
+
## Focal Point
|
|
54
|
+
|
|
55
|
+
When an image has `focal_point_x` / `focal_point_y` set in Sonor, the component auto-calculates `object-position` so the focal area stays visible during cropping.
|
|
56
|
+
|
|
57
|
+
## API Functions
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { fetchManagedImage, uploadImage, assignImageToSlot } from '@sonordev/site-kit/images'
|
|
61
|
+
|
|
62
|
+
const image = await fetchManagedImage(slotId, pagePath)
|
|
63
|
+
const uploaded = await uploadImage(file, { folder: 'heroes', category: 'background' })
|
|
64
|
+
await assignImageToSlot(slotId, uploaded.id, pagePath)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Favicon
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { ManagedFavicon } from '@sonordev/site-kit/images'
|
|
71
|
+
|
|
72
|
+
// In layout <head>:
|
|
73
|
+
<ManagedFavicon /> // Fetches Sonor logo as favicon link tags
|
|
74
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Layout — `@sonordev/site-kit/layout`
|
|
2
|
+
|
|
3
|
+
RSC-compatible master layout that auto-composes all site-kit features.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
// app/layout.tsx
|
|
9
|
+
import { SiteKitLayout } from '@sonordev/site-kit/layout'
|
|
10
|
+
|
|
11
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
12
|
+
return (
|
|
13
|
+
<html lang="en">
|
|
14
|
+
<body>
|
|
15
|
+
<SiteKitLayout>{children}</SiteKitLayout>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Zero-config: reads `SONOR_API_KEY` from env, injects it into client modules automatically.
|
|
23
|
+
|
|
24
|
+
## Props
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
interface SiteKitLayoutProps {
|
|
28
|
+
children: React.ReactNode
|
|
29
|
+
apiKey?: string // Defaults to SONOR_API_KEY env var
|
|
30
|
+
apiUrl?: string // Defaults to SONOR_API_URL, then https://api.sonor.io
|
|
31
|
+
projectId?: string // For Engage chat routing (auto-resolved if omitted)
|
|
32
|
+
analytics?: boolean | AnalyticsConfig // Default: true
|
|
33
|
+
engage?: boolean | EngageConfig // Default: true
|
|
34
|
+
signal?: boolean | SignalConfig // Default: false
|
|
35
|
+
sitemapSync?: boolean // Default: false (build-time + server reconciler own this)
|
|
36
|
+
fleet?: boolean // Default: true (once-per-session kit version heartbeat)
|
|
37
|
+
defer?: boolean // Default: true (client modules wait for load + idle)
|
|
38
|
+
favicon?: boolean // Default: true
|
|
39
|
+
managedScripts?: boolean // Default: true
|
|
40
|
+
debug?: boolean // Default: false
|
|
41
|
+
showLlmsTxtFooterLink?: boolean // Default: false (prefer middleware discovery headers)
|
|
42
|
+
speculation?: boolean | { mode?: 'prerender' | 'prefetch'; exclude?: string[] } // Default: false
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Module options live with each module: [Analytics](../analytics/README.md) (`trackPageViews`, `excludePaths`, `site`, `allowInFrame`, `allowLocalhost`) and [Engage](../engage/README.md) (`position`, `chatEnabled`, and launcher placement).
|
|
47
|
+
|
|
48
|
+
## What It Composes
|
|
49
|
+
|
|
50
|
+
**Server-side (RSC):**
|
|
51
|
+
- `ManagedFavicon` — Sonor logo as `<link>` tags
|
|
52
|
+
- `ManagedScripts` — tracking pixels/analytics tags in `<head>` and body-end positions
|
|
53
|
+
- API preconnect/dns-prefetch hints
|
|
54
|
+
|
|
55
|
+
**Client-side (lazy-loaded island):**
|
|
56
|
+
- `AnalyticsProvider` — page views, scroll depth, heatmap clicks, Web Vitals
|
|
57
|
+
- `EngageWidget` — popups, nudges, chat
|
|
58
|
+
- `SignalBridge` — A/B experiments, behavior tracking (opt-in)
|
|
59
|
+
- `SitemapSync` — parses `/sitemap.xml` and syncs to Sonor (opt-in)
|
|
60
|
+
- `FleetHeartbeat` — reports the kit version and enabled modules once per session
|
|
61
|
+
|
|
62
|
+
Since 4.0.0 none of these wrap your page. `{children}` renders first and every module mounts after it as a childless sibling, so `SiteKitLayout` never pushes a route to client rendering. Analytics, Engage, SitemapSync and the heartbeat also wait for window load + idle (or the first interaction) unless you pass `defer={false}`. `SignalBridge` isn't deferred, so experiment variants apply early. Visitor and session IDs come from a shared storage singleton rather than a provider.
|
|
63
|
+
|
|
64
|
+
## Note
|
|
65
|
+
|
|
66
|
+
`SiteKitProvider` was **removed in 4.0.0**. It wrapped the whole tree client-side, which broke RSC. Migrate an older layout with `npx sonor-setup codemod --only provider-to-layout --write`.
|