@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,109 @@
|
|
|
1
|
+
# Commerce — `@sonordev/site-kit/commerce`
|
|
2
|
+
|
|
3
|
+
Products, services, classes, events, and checkout flows — all managed from the Sonor dashboard.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
| Component | Purpose |
|
|
8
|
+
|-----------|---------|
|
|
9
|
+
| `OfferingCard` | Card display for any offering type |
|
|
10
|
+
| `OfferingList` | Grid/list of offerings with filtering |
|
|
11
|
+
| `ProductPage` / `ProductDetail` | Full product page with gallery, sizes, variants |
|
|
12
|
+
| `ProductGrid` / `ProductEmbed` | Product showcase widgets |
|
|
13
|
+
| `SizeChart` | Clothing size chart display |
|
|
14
|
+
| `EventTile` / `UpcomingEvents` | Event display widgets |
|
|
15
|
+
| `EventCalendar` / `EventModal` | Calendar view + detail modal |
|
|
16
|
+
| `EventEmbed` / `EventsWidget` | Embeddable event components |
|
|
17
|
+
| `CheckoutForm` | Payment checkout flow |
|
|
18
|
+
| `RegistrationForm` | Event/class registration |
|
|
19
|
+
| `CalendarView` | Date-based calendar component |
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { OfferingList, ProductPage } from '@sonordev/site-kit/commerce'
|
|
25
|
+
|
|
26
|
+
// List all offerings
|
|
27
|
+
export default function ShopPage() {
|
|
28
|
+
return <OfferingList type="product" />
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Single product page
|
|
32
|
+
export default function Product({ params }) {
|
|
33
|
+
return <ProductPage slug={params.slug} />
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## API Functions
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import {
|
|
41
|
+
fetchOfferings, fetchOffering,
|
|
42
|
+
fetchProducts, fetchProductBySlug,
|
|
43
|
+
fetchUpcomingEvents, fetchNextEvent,
|
|
44
|
+
fetchCategories, fetchServices,
|
|
45
|
+
createCheckoutSession, createPaymentIntent,
|
|
46
|
+
validateDiscountCode, registerForEvent,
|
|
47
|
+
fetchShippingRates, validateAddress,
|
|
48
|
+
} from '@sonordev/site-kit/commerce'
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Offering Types
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
type OfferingType = 'product' | 'service' | 'class' | 'event' | 'subscription'
|
|
55
|
+
|
|
56
|
+
interface CommerceOffering {
|
|
57
|
+
name: string; slug: string; type: OfferingType;
|
|
58
|
+
description?: string; featured_image_url?: string;
|
|
59
|
+
price_type: 'fixed' | 'variable' | 'quote' | 'free';
|
|
60
|
+
price?: number; compare_at_price?: number; currency: string;
|
|
61
|
+
track_inventory?: boolean; inventory_count?: number;
|
|
62
|
+
is_clothing?: boolean; size_chart?: SizeChart;
|
|
63
|
+
duration_minutes?: number; capacity?: number;
|
|
64
|
+
location?: string; is_virtual?: boolean;
|
|
65
|
+
schedules?: CommerceSchedule[]; variants?: CommerceVariant[];
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Per-category styling (`data-category` / `data-offering-type`)
|
|
70
|
+
|
|
71
|
+
Event and offering surfaces expose the offering's category slug and type as
|
|
72
|
+
data attributes, so sites can theme categories with plain CSS — no custom
|
|
73
|
+
components needed. `data-category` is only present when the offering has a
|
|
74
|
+
category; `data-offering-type` is always present.
|
|
75
|
+
|
|
76
|
+
Elements carrying the attributes:
|
|
77
|
+
|
|
78
|
+
- `CalendarView` / `EventCalendar` — `.site-kit-calendar-event` chips (both
|
|
79
|
+
`title` and `image` display modes) and the image-mode wrapper
|
|
80
|
+
- `EventTile` (both variants — also covers `UpcomingEvents` and `EventEmbed`)
|
|
81
|
+
- `EventsWidget` — `.site-kit-event-card` in list and grid views
|
|
82
|
+
- `OfferingCard` (all variants — also covers `OfferingList` / `ProductGrid`)
|
|
83
|
+
|
|
84
|
+
```css
|
|
85
|
+
/* e.g. two color schemes on one calendar: PTO vs school district */
|
|
86
|
+
.site-kit-calendar-event[data-category="pto"] {
|
|
87
|
+
background: rgba(16, 185, 129, 0.15);
|
|
88
|
+
color: #059669;
|
|
89
|
+
}
|
|
90
|
+
.site-kit-calendar-event[data-category="school-district"] {
|
|
91
|
+
background: rgba(59, 130, 246, 0.15);
|
|
92
|
+
color: #2563eb;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Size Charts (clothing products)
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
interface SizeChart {
|
|
100
|
+
unit: 'inches' | 'cm'
|
|
101
|
+
fit_note?: string // e.g., "Runs small. Order one size up."
|
|
102
|
+
measurements: string[] // ['Chest', 'Length', 'Sleeve']
|
|
103
|
+
rows: Array<{
|
|
104
|
+
size: string // 'S', 'M', 'L', 'XL'
|
|
105
|
+
values: number[] // Primary unit values
|
|
106
|
+
values_alt?: number[] // Auto-converted alternate unit
|
|
107
|
+
}>
|
|
108
|
+
}
|
|
109
|
+
```
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# @sonordev/site-kit/cta-bar
|
|
2
|
+
|
|
3
|
+
The Liquid Glass mobile CTA bar (6.1.0). A floating frosted capsule that
|
|
4
|
+
keeps a site's one or two highest-intent actions a thumb away on phones.
|
|
5
|
+
|
|
6
|
+
It replaces every hand-rolled sticky mobile bar in the fleet. Those eleven
|
|
7
|
+
copies had each solved part of the same problem set; this one does all of it:
|
|
8
|
+
|
|
9
|
+
| Behaviour | Before | Here |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Hide while the form it points at is on screen | two sites, hand-built | `hideOver` |
|
|
12
|
+
| Stay off the hero until the hero CTA scrolls away | one site | `showAfter` |
|
|
13
|
+
| Get out from under the Echo launcher | two sites (`body:has`, a 72px dead corner) | automatic |
|
|
14
|
+
| Ride out the iOS toolbar collapsing | one site (a GSAP tween) | `--sk-vv-layout-gap` |
|
|
15
|
+
| Get out of the way of the keyboard | nobody | `hideWhileTyping` |
|
|
16
|
+
| Tell Sonor which action converts | one site (hand-wired) | `cta_click` event |
|
|
17
|
+
|
|
18
|
+
## Use it
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
// app/layout.tsx (a server component)
|
|
22
|
+
import Link from 'next/link'
|
|
23
|
+
import { Phone, ClipboardCheck } from 'lucide-react'
|
|
24
|
+
import { CtaBar, CtaBarAction } from '@sonordev/site-kit/cta-bar'
|
|
25
|
+
|
|
26
|
+
<SiteKitLayout>
|
|
27
|
+
<Header />
|
|
28
|
+
<main>{children}</main>
|
|
29
|
+
<Footer />
|
|
30
|
+
<CtaBar label="Call or request a quote" hideOver="#quote">
|
|
31
|
+
<CtaBarAction href="tel:+15135550100" variant="secondary" icon={<Phone />}>
|
|
32
|
+
Call now
|
|
33
|
+
</CtaBarAction>
|
|
34
|
+
<CtaBarAction as={Link} href="/quote" icon={<ClipboardCheck />}>
|
|
35
|
+
Free quote
|
|
36
|
+
</CtaBarAction>
|
|
37
|
+
</CtaBar>
|
|
38
|
+
</SiteKitLayout>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Rules:
|
|
42
|
+
|
|
43
|
+
- **Render it once per page:** at the layout root for a site-wide bar, or
|
|
44
|
+
inside the page for a page-specific one. It's a labelled `<aside>`, valid
|
|
45
|
+
at either depth; the kit's axe gate covers both placements.
|
|
46
|
+
- **Never inside a blurred header.** `backdrop-filter` on an ancestor becomes
|
|
47
|
+
the containing block for this fixed bar and clips it.
|
|
48
|
+
- **Delete the site's own bar and its compensating `padding-bottom`.** The
|
|
49
|
+
kit renders a spacer that reserves the bar's height at the end of the page.
|
|
50
|
+
- **Better: pad the footer instead of adding a strip after it.** A spacer
|
|
51
|
+
after a dark footer is a blank band in the page colour. Pass
|
|
52
|
+
`spacer={false}` and let the footer's own background run under the bar:
|
|
53
|
+
|
|
54
|
+
```css
|
|
55
|
+
footer { padding-bottom: calc(2rem + var(--sk-cta-bar-space, 0px)); }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`--sk-cta-bar-space` is set on `<html>` only while a bar is present and
|
|
59
|
+
below its breakpoint, so desktop and bar-less pages get `0px`.
|
|
60
|
+
- **Delete any `--sk-echo-offset-bottom` rule written for the old bar.** The
|
|
61
|
+
kit sets it while the bar is on screen.
|
|
62
|
+
|
|
63
|
+
`CtaBar` and `CtaBarAction` are plain components with no hooks, so a server
|
|
64
|
+
layout can pass `as={Link}` and Link children without crossing a client
|
|
65
|
+
boundary. The only client code is a childless behaviour island the bar
|
|
66
|
+
mounts itself (about 4.4 KB gzipped for the whole module, glass and
|
|
67
|
+
analytics included).
|
|
68
|
+
|
|
69
|
+
## `<CtaBar>`
|
|
70
|
+
|
|
71
|
+
| Prop | Default | |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `label` | `"Quick actions"` | Accessible name of the landmark. |
|
|
74
|
+
| `breakpoint` | `"lg"` | Hidden at this width and up: `sm` 640, `md` 768, `lg` 1024, `xl` 1280, `none` = every width. |
|
|
75
|
+
| `layout` | `"fill"` | `fill` stretches actions across the capsule. `fit` hugs a single action in a centred capsule (the old floating "Request a quote" button). |
|
|
76
|
+
| `showAfter` | | Selector. Hidden until that element scrolls off the top (the hero CTA). Server-rendered hidden, so it never slides in over the hero during hydration. No match = shown. |
|
|
77
|
+
| `hideOver` | | Selector or selectors. Steps aside while any match is on screen: the form it points at, the footer. |
|
|
78
|
+
| `hideWhileTyping` | `true` | Hidden while a text field has focus, so it never sits on the keyboard. |
|
|
79
|
+
| `compactOnScroll` | `true` | Tightens while scrolling down; an icon-bearing secondary action folds to its icon. Scrolling up restores it. |
|
|
80
|
+
| `echoClearance` | `true` | Lifts the Echo launcher above the bar while the bar is on screen, below the breakpoint only. |
|
|
81
|
+
| `spacer` | `true` | Reserves the bar's height at the end of the page. |
|
|
82
|
+
| `track` | `true` | Sends `cta_click` (`category: engagement`, `label`, `properties.href`, `properties.variant`, `properties.location = "cta_bar"`) through the standalone analytics dispatch. |
|
|
83
|
+
|
|
84
|
+
Client navigation re-finds `showAfter` and `hideOver` targets on each route.
|
|
85
|
+
|
|
86
|
+
## `<CtaBarAction>`
|
|
87
|
+
|
|
88
|
+
| Prop | Default | |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `as` | `a` with an `href`, else `button` | Any element or component, e.g. Next's `Link`. Polymorphic since 6.1.3: the action then takes that component's own props (required ones included), so `<CtaBarAction as={ScheduleTourButton} values={unit}>` type-checks and a missing required prop doesn't. |
|
|
91
|
+
| `variant` | `"primary"` | `primary`: solid brand. `secondary`: tinted glass. `plain`: layout only, bring your own classes. |
|
|
92
|
+
| `icon` | | Leading icon, hidden from assistive tech. Lucide icons are sized to 18px. |
|
|
93
|
+
| `collapse` | secondary + icon | Fold to icon-only while compact. The label stays in the accessible name. |
|
|
94
|
+
|
|
95
|
+
Everything else (`href`, `onClick`, `target`, `aria-*`, `data-*`) passes
|
|
96
|
+
through. A `button` without an explicit `type` gets `type="button"`.
|
|
97
|
+
|
|
98
|
+
## Theme it
|
|
99
|
+
|
|
100
|
+
Colours and geometry are custom properties. Set them on `:root` so the bar,
|
|
101
|
+
its spacer and the Echo clearance all read the same values.
|
|
102
|
+
|
|
103
|
+
```css
|
|
104
|
+
:root {
|
|
105
|
+
--sk-cta-primary-bg: var(--brand-primary); /* default: --sk-primary, then #2563eb */
|
|
106
|
+
--sk-cta-primary-text: #fff;
|
|
107
|
+
--sk-cta-bar-text: var(--ink-900); /* secondary label colour */
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**A dark bar: scope the tint to the bar, not `:root`.** The Echo window
|
|
112
|
+
reads the same `--sk-glass-tint`, and its text follows `--sk-text-primary`
|
|
113
|
+
(dark by default). A dark tint on `:root` puts that dark text on dark glass.
|
|
114
|
+
Either scope the bar's colours to the bar:
|
|
115
|
+
|
|
116
|
+
```css
|
|
117
|
+
.sk-cta-bar {
|
|
118
|
+
--sk-glass-tint: #0b0a09;
|
|
119
|
+
--sk-cta-bar-text: #fff;
|
|
120
|
+
--sk-cta-primary-bg: #fff;
|
|
121
|
+
--sk-cta-primary-text: #0b0a09;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
or theme the whole kit dark with `--sk-bg` and `--sk-text-primary` on
|
|
126
|
+
`:root`, which both surfaces (and site-kit forms) read.
|
|
127
|
+
|
|
128
|
+
| Token | Default |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `--sk-cta-bar-inset` | `12px` from the screen edges |
|
|
131
|
+
| `--sk-cta-bar-padding` | `6px` between glass and buttons |
|
|
132
|
+
| `--sk-cta-bar-action-height` | `48px` (44px tap target minimum; don't go lower) |
|
|
133
|
+
| `--sk-cta-bar-max-width` | `520px` |
|
|
134
|
+
| `--sk-cta-bar-z` | `40` |
|
|
135
|
+
| `--sk-cta-bar-text` | `--sk-text-primary`, then `#111827` |
|
|
136
|
+
| `--sk-cta-primary-bg` / `--sk-cta-primary-text` | `--sk-primary` / `#fff` |
|
|
137
|
+
| `--sk-cta-secondary-bg` / `--sk-cta-secondary-text` | 8% of the text colour / the text colour |
|
|
138
|
+
| `--sk-glass-*` | the shared glass recipe (`--sk-glass-tint`, `--sk-glass-opacity`, `--sk-glass-blur`, `--sk-glass-saturate`) |
|
|
139
|
+
|
|
140
|
+
## The glass
|
|
141
|
+
|
|
142
|
+
The surface is the kit's one Liquid Glass recipe (`src/shared/glass.tsx`),
|
|
143
|
+
the same material as the Echo launcher and chat window, so the bar and the
|
|
144
|
+
chat always match. The tint is mostly opaque (72%): the tint carries the
|
|
145
|
+
contrast, and the blur only softens what shows through. Browsers without
|
|
146
|
+
`backdrop-filter`, and visitors who ask their OS for reduced transparency
|
|
147
|
+
or more contrast, get the same surface solid. Reduced motion turns the
|
|
148
|
+
transitions off. JS-off visitors get the bar, visible, through
|
|
149
|
+
`@media (scripting: none)`.
|
|
150
|
+
|
|
151
|
+
On phones where the layout viewport runs taller than the visible one
|
|
152
|
+
(in-app browsers, toolbar animations), mount `VisualViewportGap` from
|
|
153
|
+
`@sonordev/site-kit/client`; the bar already adds `--sk-vv-layout-gap` to
|
|
154
|
+
its `bottom`.
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# Engage — `@sonordev/site-kit/engage`
|
|
2
|
+
|
|
3
|
+
Popups, nudges, bars, slide-ins, and chat widgets — all configured from the Sonor dashboard.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
Auto-included by `SiteKitLayout`. For standalone use:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
'use client'
|
|
11
|
+
import { EngageWidget } from '@sonordev/site-kit/engage'
|
|
12
|
+
|
|
13
|
+
export default function Layout({ children }) {
|
|
14
|
+
return (
|
|
15
|
+
<>
|
|
16
|
+
{children}
|
|
17
|
+
<EngageWidget />
|
|
18
|
+
</>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Props
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
interface EngageWidgetProps {
|
|
27
|
+
apiUrl?: string // Default: https://api.sonor.io
|
|
28
|
+
apiKey?: string // From SiteKitLayout or env
|
|
29
|
+
projectId?: string // For chat routing
|
|
30
|
+
position?: 'bottom-right' | 'bottom-left' // Default: 'bottom-right'
|
|
31
|
+
offsetBottom?: string | number // Default: '20px'. See "Launcher placement"
|
|
32
|
+
zIndex?: number // Default: 9999. Popups, nudges, bars and the chat. See "Stacking"
|
|
33
|
+
chatEnabled?: boolean // Default: true. See "The chat switch"
|
|
34
|
+
debug?: boolean
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Through `SiteKitLayout`, the same options go in `engage={{ ... }}`.
|
|
39
|
+
|
|
40
|
+
## The chat switch
|
|
41
|
+
|
|
42
|
+
Echo follows the project's **Enable Chat Widget** switch in Sonor (Engage,
|
|
43
|
+
Chat settings). A project that has never saved chat settings counts as on, so
|
|
44
|
+
Echo is on by default and only an owner who switches it off hides it. It also
|
|
45
|
+
needs the project's **Engage** module on (Project Settings); without it Sonor
|
|
46
|
+
answers off, since the chat has nowhere to take a visitor's message.
|
|
47
|
+
|
|
48
|
+
- The launcher appears once `GET /engage/widget/config` answers. Nothing
|
|
49
|
+
renders before that, so a switched-off site never flashes a launcher, and
|
|
50
|
+
it never starts Echo's availability polling.
|
|
51
|
+
- If the config can't be fetched, the launcher stays hidden. The chat can't
|
|
52
|
+
run without the API anyway.
|
|
53
|
+
- `chatEnabled: false` (or `engage={false}`) in code still turns chat off
|
|
54
|
+
whatever the switch says. Code can turn Echo off; it can't force it on over
|
|
55
|
+
the owner's switch.
|
|
56
|
+
|
|
57
|
+
## Liquid Glass (6.1.0)
|
|
58
|
+
|
|
59
|
+
Echo's launcher and chat window are made of the kit's shared Liquid Glass
|
|
60
|
+
recipe (`src/shared/glass.tsx`), the same material as the mobile CTA bar
|
|
61
|
+
(`@sonordev/site-kit/cta-bar`), so the two always match.
|
|
62
|
+
|
|
63
|
+
- **Launcher:** brand-tinted glass (86% brand over a blurred backdrop) with a
|
|
64
|
+
specular rim. The icon turns dark on a light brand colour; it used to stay
|
|
65
|
+
white and disappear.
|
|
66
|
+
- **Chat window:** a glass panel (86% tint, 28px blur) that grows out of the
|
|
67
|
+
launcher's corner. The header is part of the sheet, washed with the brand
|
|
68
|
+
at the top, and the brand sits on the avatar tile, the visitor's bubbles,
|
|
69
|
+
the send button and the launcher.
|
|
70
|
+
- **Bubbles and the composer stay near-opaque** (94%). The glass is depth,
|
|
71
|
+
never the surface text reads against.
|
|
72
|
+
- **Brand text always reads (6.1.2).** Where the brand is text on the panel
|
|
73
|
+
(quick-action chips, the phone link, link buttons, suggestion chips, "Talk
|
|
74
|
+
to a person"), Echo uses the brand as-is when it clears WCAG AA against the
|
|
75
|
+
panel, and otherwise pulls it toward black (light panel) or white (dark
|
|
76
|
+
panel) just far enough to clear it: `#d4af37` gold becomes `#887023`. Fills
|
|
77
|
+
(launcher, avatar, the visitor's bubbles, buttons) keep the raw brand. One
|
|
78
|
+
helper decides this, `src/engage/brand-color.ts`; route any new brand
|
|
79
|
+
foreground through it. `--sk-primary` can be hex, `rgb()` or `hsl()`.
|
|
80
|
+
- **Fields are 16px.** iOS Safari zooms the whole page into any focused field
|
|
81
|
+
under 16px; the chat input and the inline Echo forms were 13.5-14px.
|
|
82
|
+
- **Fallbacks:** no `backdrop-filter`, reduced transparency, or more contrast
|
|
83
|
+
all get the same surfaces solid. Reduced motion skips the open animation.
|
|
84
|
+
|
|
85
|
+
Tune it with the shared tokens, from your own stylesheet:
|
|
86
|
+
|
|
87
|
+
```css
|
|
88
|
+
:root {
|
|
89
|
+
--sk-glass-panel-opacity: 92%; /* denser chat window (default 86%) */
|
|
90
|
+
--sk-glass-brand-opacity: 100%; /* solid brand launcher (default 86%) */
|
|
91
|
+
--sk-glass-tint: #0b0b0c; /* dark glass; pair with --sk-text-primary */
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`--sk-glass-panel-opacity: 100%` gives the pre-6.1 solid window back, with the
|
|
96
|
+
glass header.
|
|
97
|
+
|
|
98
|
+
## Launcher placement
|
|
99
|
+
|
|
100
|
+
The Echo launcher is a 60px circle, fixed 20px from the side and 20px above
|
|
101
|
+
the bottom edge. Its placement is an inline style, so a stylesheet can't move
|
|
102
|
+
it without `!important`. Don't write that override; declare the offset instead.
|
|
103
|
+
|
|
104
|
+
**A fixed offset, every page:** pass `offsetBottom`.
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
<SiteKitLayout engage={{ offsetBottom: '88px' }}>{children}</SiteKitLayout>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Any CSS length works (`'5.5rem'`, `'calc(4rem + 8px)'`); a number is pixels.
|
|
111
|
+
The device's safe-area inset is added on top, so pass the clearance you want
|
|
112
|
+
above your own UI, not the inset.
|
|
113
|
+
|
|
114
|
+
**An offset that depends on the page or the breakpoint:** set the
|
|
115
|
+
`--sk-echo-offset-bottom` custom property from your stylesheet. The launcher
|
|
116
|
+
is portalled to `<body>`, so a declaration on `body` or `:root` reaches it,
|
|
117
|
+
and the property wins over `offsetBottom`. This clears a sticky mobile bar
|
|
118
|
+
only on the pages that render one:
|
|
119
|
+
|
|
120
|
+
```css
|
|
121
|
+
@media (max-width: 1023.98px) {
|
|
122
|
+
body:has(.sticky-cta) {
|
|
123
|
+
--sk-echo-offset-bottom: 5.5rem;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Pages without the bar, desktop, and any browser without `:has()` keep the
|
|
129
|
+
default. No `!important`, no selector on the kit's markup.
|
|
130
|
+
|
|
131
|
+
**Using the kit's `<CtaBar>`?** Write nothing. It sets
|
|
132
|
+
`--sk-echo-offset-bottom` itself while it's on screen, below its breakpoint,
|
|
133
|
+
and the launcher glides up and back down as the bar shows and hides. Delete
|
|
134
|
+
any rule like the one above that was written for a hand-rolled bar.
|
|
135
|
+
|
|
136
|
+
**The popup follows.** It opens 10px above the launcher and keeps 30px clear
|
|
137
|
+
of the top edge, wherever the offset puts the launcher. (An override that
|
|
138
|
+
moved only the `<button>` left the popup behind, with the launcher over its
|
|
139
|
+
input row.)
|
|
140
|
+
|
|
141
|
+
The resolved position is:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
launcher bottom = offset + env(safe-area-inset-bottom) + var(--sk-vv-layout-gap, 0px)
|
|
145
|
+
popup bottom = launcher bottom + 70px
|
|
146
|
+
popup maxHeight = 100dvh - launcher bottom - 100px
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Stacking
|
|
150
|
+
|
|
151
|
+
`zIndex` is the one layer everything Engage renders sits on: popups, nudges,
|
|
152
|
+
bars and the chat launcher. The chat popup sits one layer beneath the
|
|
153
|
+
launcher. The default is 9999, above almost anything a site draws, so lower it
|
|
154
|
+
when your own fixed UI (a mobile menu, a cookie banner) has to cover the chat:
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
<SiteKitLayout engage={{ zIndex: 40 }}>{children}</SiteKitLayout>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
launcher z-index = zIndex (default 9999)
|
|
162
|
+
popup z-index = zIndex - 1
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
At `zIndex` 0 or below the popup shares the launcher's layer instead, because
|
|
166
|
+
-1 would put it behind the page's own content. The launcher still paints on
|
|
167
|
+
top.
|
|
168
|
+
|
|
169
|
+
### Layout vs visual viewport on phones
|
|
170
|
+
|
|
171
|
+
On a phone the layout viewport can be taller than what the visitor can see
|
|
172
|
+
(browser chrome, in-app browsers, pinch zoom), and `position: fixed` is
|
|
173
|
+
measured against the layout viewport, so a bottom-fixed launcher can sit
|
|
174
|
+
below the fold. Mount `VisualViewportGap` to publish the difference as
|
|
175
|
+
`--sk-vv-layout-gap` on `<html>`. The launcher and popup already read it, and
|
|
176
|
+
your own fixed bottom UI can too:
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import { VisualViewportGap } from '@sonordev/site-kit/client'
|
|
180
|
+
|
|
181
|
+
<SiteKitLayout>
|
|
182
|
+
<VisualViewportGap />
|
|
183
|
+
{children}
|
|
184
|
+
</SiteKitLayout>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```css
|
|
188
|
+
.mobile-cta {
|
|
189
|
+
bottom: calc(24px + env(safe-area-inset-bottom, 0px) + var(--sk-vv-layout-gap, 0px));
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
It's opt-in: while a field has focus the gap grows to the keyboard's height,
|
|
194
|
+
so everything that reads it rides above the keyboard. `useVisualViewportGap()`
|
|
195
|
+
is the hook form for an existing client component.
|
|
196
|
+
|
|
197
|
+
## Element Types
|
|
198
|
+
|
|
199
|
+
| Type | Description |
|
|
200
|
+
|------|-------------|
|
|
201
|
+
| `popup` | Modal overlay with CTA |
|
|
202
|
+
| `nudge` | Small corner notification |
|
|
203
|
+
| `bar` | Top/bottom sticky bar |
|
|
204
|
+
| `slide-in` | Side panel |
|
|
205
|
+
| `chat` | AI/live chat widget |
|
|
206
|
+
|
|
207
|
+
## Targeting & Triggers
|
|
208
|
+
|
|
209
|
+
All configured in the Sonor dashboard — no code changes needed:
|
|
210
|
+
|
|
211
|
+
- **Page targeting** — include/exclude paths with wildcard support
|
|
212
|
+
- **Device targeting** — desktop, mobile, tablet
|
|
213
|
+
- **Visitor targeting** — new vs returning visitors
|
|
214
|
+
- **Triggers** — immediate, delay (seconds), scroll (%), exit-intent, click, custom
|
|
215
|
+
- **Frequency capping** — once, once-per-session, every N days
|
|
216
|
+
|
|
217
|
+
## Chat Widget
|
|
218
|
+
|
|
219
|
+
Supports AI mode (Echo), live mode, and hybrid (AI + human handoff):
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
interface ChatConfig {
|
|
223
|
+
position: 'bottom-right' | 'bottom-left'
|
|
224
|
+
offsetBottom?: string | number // See "Launcher placement"
|
|
225
|
+
zIndex?: number // Default: 9999. See "Stacking"
|
|
226
|
+
mode: 'ai' | 'live' | 'hybrid'
|
|
227
|
+
buttonColor?: string
|
|
228
|
+
aiSettings?: {
|
|
229
|
+
skillId?: string
|
|
230
|
+
handoffToLive?: boolean
|
|
231
|
+
handoffKeywords?: string[]
|
|
232
|
+
}
|
|
233
|
+
operatingHours?: { ... }
|
|
234
|
+
offlineMode?: 'form' | 'ai' | 'message'
|
|
235
|
+
offlineFormSlug?: string
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Tracking
|
|
240
|
+
|
|
241
|
+
Impressions and clicks are automatically tracked via the Sonor API. Shares visitor ID (`_sk_vid`) with Analytics for cross-module attribution.
|