@aftership/ads-renderer-react
v0.4.1
Published
Pure React renderers for AfterShip Ads self-render channel. Web: single-offer card + carousel per the Figma universal ad styles. Email: satori-safe creative template consumed by the render service. No data fetching, no tracking side effects — offers in, U
Readme
@aftership/ads-renderer-react
用于标准化 AfterShip Ads decision 的纯 React renderer。
边界
本 package 负责展示:
- Web
AdsSection的单 offer 和 carousel layout; - desktop/mobile 响应式渲染;
- theme 解析;
- 空结果 fail-safe;
- 通过
./emailexport 提供兼容 satori 的 Email creative JSX。
它不请求 decision、不拥有认证、不持久化数据,也不调用 backend Event API。Web 交互 event 只发送给可选 host callback。
Web 接口
import { AdsSection } from "@aftership/ads-renderer-react";
<AdsSection
decision={decision}
variant="auto"
layout="auto"
onEvent={hostCallback}
/>;空 offer array 不渲染内容。可选字段缺失时直接省略,不虚构广告主文案或 URL。target_url 可能携带供应商 attribution,必须原样使用。
section 自己不带外边距(也没有 padding):纵向节奏归 host——BTP 已经用 --btp-s-section-padding 包了一层,
SDK 再加一层就是双份。需要的 host 通过 style 传。
创意图取 images[0],等比缩放(object-fit: contain,不裁切广告主素材)进一个固定尺寸的图块
(desktop 220×160、mobile 满宽×120,圆角取 borderRadius)。图块不涂底色:contain 下任何不是该
比例的素材都会留边,涂了底色,白底方图(真实数据里 612×612 很常见)就会变成「灰|白|灰」三段;不涂,
留边就是 section 背景。编辑器画布和 onboarding 预览里那块灰色占位块来自 mock 自带的占位图,不是 renderer
画的。高度固定而非跟随图片长宽比:广告主素材从方形 logo 到宽横幅
都有,高度浮动会让 carousel 每张 slide 高度不同,decline 翻页时版面跳动。决策里没有图、或图加载失败
(404)时整块不渲染,连同旁边的间距一起消失,退化成纯文字版式——不会留下浏览器碎图标或空白条。
多 offer 没有左右箭头也没有 dots(2026-07-27 产品决策):切换靠 offer 自己的 decline 按钮
(文案取 cta.decline_copy,缺失时兜底 "No thanks")——点一次换下一个,点到最后一个即关闭整个 section,
这也是用户关闭广告的入口。单个 offer 同样展示该按钮,点击即关闭。accept 与 decline 放得下就并排、各自按内容宽度;
放不下换行后每个各占一整行——按实际是否换行决定,不看 desktop/mobile,因为按钮文案由广告主下发、长度不可控。
关闭状态只存在于组件内,刷新后广告照常出现。编辑器/预览态传 closable={false}:最后一个 decline 回到第一个而不是消失,
避免商家在画布里点几下就把版面点空。decline 不产生上报事件(POST /v1/events 没有对应类型);
切到下一个 offer 时照常发 offer_display。
主题
theme(AdsTheme)的每个值都是原样写进 inline style 的 CSS 值,可以直接传 var(...)——BTP 就是把
--btpb-ads-*(定义在 C 端 adaptor/CustomizationStyleProvider.tsx,兜底链与 recommendation 同构)传进来,
让广告位跟随店铺主题。数字按 px 处理。
粗粒度字段单独用就够:primaryColor / textColor / fontFamily / borderRadius。需要拆的 host 再用细粒度字段:
buttonRadius buttonFontFamily buttonFontSize、descriptionColor advertiserColor。创意图的圆角跟
borderRadius,图块不涂底色,所以没有单独的图片主题字段。
两个按钮的 hover 由 primaryHoverColor buttonTextHoverColor buttonHoverRadius buttonHoverFontFamily
buttonHoverFontSize 控制,缺省回落到对应静态字段。inline style 写不了 :hover,所以 hover 是组件内部的
pointer 状态(touch pointer 会被忽略,避免点一下就留在 hover 态)——与 BTP 自己的按钮同构(astra 也是用
react-aria 的 data-hovered 而不是 CSS)。primaryHoverColor 写进 CSS background 简写,可以是渐变
(BTP 传的 --btp-btn-hover-bgi-color 就是「半透明遮罩 + hover 底色」的 linear-gradient);host 不传时
SDK 自己叠一层 rgba(0,0,0,0.12) 压暗,保证任何 host 都有 hover 反馈。decline 是无边框按钮,hover 底色由
primaryColor 经 color-mix 派生 12% 淡色(color-mix 不支持的浏览器整条声明失效,退回透明底)。
⚠️ 描述与广告主标签的颜色默认从 textColor 按透明度派生,而 var() 字符串派生不了——传 CSS 变量的 host
必须显式给 descriptionColor 和 advertiserColor,否则三级文字会挤成同一个颜色。
callback(onEvent)发送 widget_load、widget_display、offer_display、offer_click 与 host-local 的 no_fill,并携带 report_token(已签发时)。host 负责把有 token 的事件经自身 BFF 转发到 POST /v1/events(no_fill 与无 token 事件不转发);事件模型详见 @aftership/ads-core。
Email 接口
import { EmailCreative } from "@aftership/ads-renderer-react/email";EmailCreative 面向 satori CSS 子集,只渲染一个 offer。apps/render 负责图片拉取、SVG/PNG 转换、上传、缓存、redirect 和错误降级。
当前 Email runtime 见 Email rendering 架构。
开发
pnpm --filter @aftership/ads-renderer-react build
pnpm --filter @aftership/ads-renderer-react typecheck
pnpm --filter @aftership/ads-renderer-react test交互式视觉验证使用 apps/renderer-demo。
