@ flopay/ ui (英语)
共享的 FloPay 设计系统——用于设计脚本值的罐装表
由 CSS 和交易邮件共享, 一个 Tailwind v4 主题预设, 以及一个小
原生反应 使用时间 landing, (中文(简体) ), demo, (中文(简体) ), docs,以及 dashboard。 。 。 .
没错 符号层的框架不可知性: 相同的 CSS 自定义属性 驱动 Plain-CSS 应用程序(登陆、演示)和 Tailwind v4 应用程序(Docs, 仪表板。 一旦在这里更改一个符号,每个应用程序都会继承它.
安装
pnpm add @flopay/ui
react 财务报告和审定财务报表 react-dom (单位:千美元)^19)是同行依赖关系(只有在导入时才需要)
反应组件).
使用量
1. Plain-CSS应用程序(登陆、演示)
在您的顶端导入全部( 重置 + 令牌 + 共享组件类) 全局样式表 :
/* app/globals.css */
@import "@flopay/ui/styles";
/* ...your page-specific CSS, using var(--brand), var(--text), etc. */
或者选樱桃:
@import "@flopay/ui/styles/tokens.css"; /* :root design tokens only */
@import "@flopay/ui/styles/reset.css";
@import "@flopay/ui/styles/components.css"; /* .btn-primary, .nav, .footer, .card ... */
2. 尾风v4应用软件(文件、仪表板)
在 Tailwind 后添加预设 。 它将符号登记为主题值,所以
公用事业 bg-brand, (中文(简体) ), text-text-secondary, (中文(简体) ), border-border,以及
font-sans 对共享符号的解析:
/* app/globals.css */
@import "tailwindcss";
@import "@flopay/ui/styles/preset.css";
<button className="bg-brand text-bg-card rounded-lg px-6 py-3">Get started</button>
预设用途 @theme inline,所以请 .dark 覆盖在 tokens.css 应用
运行时不重编.
3. 原始反应
import { Button, Container, Section, SectionHeader, Badge, Card, cn } from "@flopay/ui";
<Container>
<Badge dot>Live</Badge>
<SectionHeader label="Pricing" title="Simple, usage-based" description="No surprises." />
<Card className={cn("p-10")}>…</Card>
<Button variant="primary">Start building</Button>
</Container>;
原始人制造了共享组件类,所以他们在Plain-CSS工作
应用程序( 导入 components.css在Tailwind应用软件中
他们通过 className + 键 cn()) (中文(简体) ).
核对清单和分步指示数
设置核对表的勾选列表和向导的步数指标。 他们不需要
提供商或仪表板代码,除 ChecklistSkeleton是因为
useId,因此它们也从服务器组件中渲染出.
import Link from "next/link";
import { Checklist, ChecklistRow, ChecklistSkeleton, StepIndicator } from "@flopay/ui";
{loading ? (
<ChecklistSkeleton rows={3} /> // role="status", announced as "Loading"
) : (
<Checklist>
<ChecklistRow label="Verify your domain" done status="Connected" />
<ChecklistRow label="Brand your emails" href="/setup/emails" linkAs={Link} />
<ChecklistRow label="Invite your team" optional />
</Checklist>
)}
<StepIndicator
steps={[
{ label: "Account", status: "done" },
{ label: "Domain", status: "current" },
{ label: "Emails", status: "upcoming" },
]}
/>;
A 类 ChecklistRow 显示在 success 当符号 done 还有一个
否则空出, 可选 status 右侧的文本,并带有 optional页:1
中性“可选” Badge与 href 整行是一个链接:平原 <a>, (中文(简体) ),
或您通过的组件 linkAs (它收到 href, (中文(简体) ), className 编辑
行内容),因此软件包从不导入路由器.
StepIndicator 是一个命令列表。 每一步都会经过自己的地步 status (单位:千美元)done, (中文(简体) ),
current 或者说 upcoming),因此步骤可以按顺序完成,以及
当前步骤携带 aria-current="step"。行和步表示它们的状态
视觉隐藏的文字,所以从未单独用颜色传达:"Done"/"Not"
完成"一行,"取"/"取"/"取"一行. 过
statusLabels 要么改变措辞.
确认拨号
同意或破坏性步骤的确认/取消提醒对话框。 打电话者拥有
open 并提供所有措辞; 对话框仅报告用户的方式
选择.
"use client";
import { useState } from "react";
import { Button, ConfirmDialog } from "@flopay/ui";
const [open, setOpen] = useState(false);
const [saving, setSaving] = useState(false);
<Button onClick={() => setOpen(true)}>Turn on reminders</Button>
<ConfirmDialog
open={open}
title="Send abandoned-cart emails?"
description={<p>Customers who leave a cart get a reminder. <a href="/terms">Terms</a></p>}
confirmLabel="Yes, send them"
cancelLabel="Not now"
confirming={saving}
onConfirm={async () => {
setSaving(true);
try {
await enableReminders();
setOpen(false);
} finally {
setSaving(false);
}
}}
onCancel={() => setOpen(false)}
/>;
清除 confirming 当请求也失败时( finally (上文):虽如此
设置对话框不能被确认、取消或解除,所以拒绝
离开它 true 将用户困入对话框.
虽然 open 它让 role="alertdialog" 与 aria-modal="true"标记
以 title 和描述 description (文字或节点);同时关闭
使无所事事。 确认通话 onConfirm; 取消、 退出和单击
背景说明 onCancel,在面板内单击不会拖动
从面板内部开始,到背景结束。 焦点移动到
在打开时取消, Tab 和 Shift+Tab 保留在对话框内, 并关闭焦点
回到打开它的元素。 虽然 confirming,两个按钮是
失能和逃逸的背景被忽略了 所以飞行中的请求不能
被开除。 它使(没有传送门)就位,所以它是服务器即位.
4. 销售铬(导航+脚)
import logo from "@flopay/ui/logo.png";
import { FlopayFooter, Nav } from "@flopay/ui/marketing";
<Nav
logo={{ src: logo, label: "FloPay" }}
links={[
{ href: "/pricing", label: "Pricing" },
{ href: "/contact", label: "Contact", cta: true },
]}
/>;
<FlopayFooter logo={logo} />; // on flopay.com itself
<FlopayFooter logo={logo} baseUrl="https://flopay.com" />; // any other portal
Nav 链接保留每个应用程序(演示点在它自己的路线上),但铬是
共享: 标记 cta: true 行动呼吁和每一项行动 其他项目 项获得一个
nav-link-item 类,哪个 components.css 隐藏在768px以下——如此狭窄
取景口倒塌为标志 + CTA 无所不在. 装有自己部件的应用软件
CSS 而不是导入 components.css 重新宣布一条规则 选择加入.
FlopayFooter 整个脚印, 包含的内容 ——站点地图,标记线
版权在 flopay-footer.tsx,所以添加页脚链接是一个变化
这里加上一个版本的凸起,每个传送门都会把它捡起. 关闭的门户
flopay.com 通过 baseUrl 使销售路线绝对化; demoUrl 财务报告和审定财务报表
contactUrl 覆盖两个条目,一个门户通常想要重新指向自己.
产品栏链接到 Card Vault, 充值预防和订阅
依此顺序回收,再取其余产品占地者.
需要一个不同的网站地图(合作伙伴或内部门户)? 使用下层
Footer 与你自己 columns编号 :legal,或从 flopayFooterContent()
并编辑结果. MarketingLink 选择 next/link 每名新塔布主播
href,所以列列表可以混合内部路由,绝对URL和 mailto:。 。 。 .
5. 注释层
手取标记笔记和箭取出UI,用于制导演示和 产品参观。 Opt-in, 因为箭头几何是 KB 的几何, 大部分的页面不是 需要:
/* app/globals.css */
@import "@flopay/ui/styles/annotations.css";
import { type Annotation, AnnotationStage } from "@flopay/ui/annotations";
const notes: readonly Annotation[] = [
{
id: "email",
text: "Pop your email in — we'll send your receipt here",
arrow: "squiggle-right",
pos: { top: "60%", left: "-56px" }, // the note, within the stage
posArrow: { left: "18px" }, // nudge the arrow alone
tilt: -5, // note + arrow, hand-placed feel
},
];
<AnnotationStage annotations={notes}>
<YourCheckoutForm />
</AnnotationStage>;
AnnotationStage 使治疗和选取一种:
边缘有空间 内容下方有一张可读的列表
没有 浮层是 aria-hidden,所以列表是帮助
科技宣布——说明从不丢失给屏幕阅读器.
22个箭头变体 - 六个方向平静的down-right, (中文(简体) ), up-left, (中文(简体) ),
right,.)外加一个表达集(loop-right, (中文(简体) ), squiggle-left, (中文(简体) ),
s-curve-down, (中文(简体) ), zigzag-right, (中文(简体) ), hook-left, (中文(简体) ), curl-down(中文(简体) ). 每个都填满了
带有磁带宽度剖面和固态头的外观,由
scripts/gen-arrow-paths.cjs 而不是手写——编辑控件
点数和运行 pnpm gen:arrow-paths; 使用 pnpm gen:arrow-paths:check
在不修改的情况下验证承诺模块。 不要编辑 arrow-paths.ts。 。 。 .
一个曲线覆盖许多方向: rotate 再来一次, flipH 编号 : flipV
照镜子,而且 arrowScale 它的伸展,只靠箭头,
留下字条的正文。 这些是视觉改造,所以是重塑
箭头上悬其布局框及其 pos 通常需要重新检查.
颜色和相貌来自 --annotation-ink (蓝蓝)和
--annotation-font (一个笔迹堆栈); 覆盖每个应用程序。 这个
浮动/列表开关是一个 @media (min-width: 1120px) ~720px 的规则大小
内容框——在受限制的布局下应用软件应当重新声明该块.
6. 交易电子邮件
@flopay/ui/email 使一个提供者中立的 HTML 字符串和匹配平原
语句。 它不发送消息或配置送货商.
import { renderTransactionalEmail } from "@flopay/ui/email";
const { html, text } = renderTransactionalEmail({
preheader: "Your FloPay payment receipt is ready",
heading: "Payment received",
body: [
"Thanks, Alex. Your payment was processed successfully.",
"Keep this receipt for your records.",
],
action: {
label: "View receipt",
href: "https://flopay.com/receipts/FP-2048",
},
detail: {
kind: "keyValue",
rows: [
{ label: "Reference", value: "FP-2048" },
{ label: "Amount", value: "$48.00 USD" },
],
},
footer: {
address: "FloPay LLC, 100 Payments Way, New York, NY",
},
});
固定档位是内容合同:一个简洁的前标,一个标题, 纯文本正文段落,可选动作,以及代码或密钥值 细节。 消费者提供内容而不是反应儿童或任意的HTML, 因此,新的账户和支付信息 重复同样的读取顺序和布局. 所有链接都是真实的锚,动作 URL 和标签细节仍然保留 可用于生成的纯文本 .
电子邮件客户端无法使用活的 CSS 令牌预设 。 因此,模板
插入共享颜色和半径 design-tokens.json; 构建捆绑
因此消费者不需要运行时间 JSON
文档。 只有电子邮件的选择——无障碍链接颜色,600px宽度,以及
首个字体堆放有Roboto/Arial倒置的相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接相接
src/email/theme.ts。布局是一张基于 600px 的表格卡,有 40px 桌面
平板和24px窄屏幕平板.
默认 FLOPAY_EMAIL_LOGO_URL 被托管的核定资产为
https://flopay.com/logo.png。标志是装饰性的(alt=""因为
相邻的文本品牌名称提供无障碍名称,并保持可见
图像关闭客户端。 一个服务可以通过一个绝对的 HTTPS logoUrl 当它
需要托管同一资产本身; 被包文件, data: URLs,以及
供应商专用的CID参考不是可移植的电子邮件来源.
共享交易页脚默认为 FloPay LLC, (中文(简体) ),
[email protected]隐私 术语 服务信息 没有
包括一个未订阅的营销. footer 可以覆盖公司,支持
标签/URL、合法URL、通知和可选地址
支助需求各不相同.
初始兼容基线是 MacOS/iOS 上的 Apple Mail; Gmail Web, iOS和Android;Outlook Windows桌面/MSO、Outlook.com和微软365 还有雅虎 标记使用布局表和内衬样式,并具有响应性 和暗模 CSS 作为渐进增强。 在发布之前 排版, 校验 Outlook Windows, 黑暗模式, 以及真实的图像关闭行为 除自动渲染外的客户端或电子邮件渲染服务 示意图.
7. 电子邮件编辑器控制
仪表板的电子邮件屏幕后面的控制器:每个电子邮件的开关,以及
电子邮件编辑屏幕的块。 它们是根出口 Input时
共享类 components.css (所以他们在平地CSS和Tailwind工作)
应用程序),不添加运行时间依赖,也不需要提供者或仪表板代码:数据
电线留在应用程序里.
Switch, (中文(简体) ), SlotTextField, (中文(简体) ), VariablePicker 财务报告和审定财务报表 BlockList 使用反应状态,
校对和效果,所以在Next.js App路由器的应用程序中从客户端获取它们
组件( 文件以 "use client") (中文(简体) ). 导入其他原始元素, 如
Button 从 @flopay/ui 在服务器组件中不受影响.
切换
可访问的开关:本地开关 <button role="switch">空间
并输入工作和屏幕阅读器,宣布其名称和 aria-checked 状态.
import { Switch } from "@flopay/ui";
<Switch
label="Payment receipt"
labelHidden // keeps the accessible name, hides the text (table rows)
checked={enabled}
loading={saving} // busy, still focusable, keeps its state, ignores toggles
onCheckedChange={(next) => save(next)}
/>;
使用 checked + 键 onCheckedChange (控制)或 defaultChecked (无所控制).
disabled 让它成为一个禁用按钮。 虽然 loading显示 Spinner
并留在原地; 翻转 checked 一旦保存决定。 过 name 提交
name=on 打开时的窗体; 像被禁用的复选框一样, 被禁用的开关
无所提交.
绘图文本字段
带有粗体/ 斜体/ Link 工具栏的标签字段
只写 Markdown-lite : **bold**, (中文(简体) ), _italic_, (中文(简体) ), [label](https://…) 财务报告和审定财务报表
空行段落。 没有标题、列表、图像或原始 HTML; 使用自定义 HTML
用于任何更丰富的模式.
import { SlotTextField } from "@flopay/ui";
<SlotTextField
label="Intro"
multiline // textarea; single-line input by default
value={intro}
onChange={(event) => setIntro(event.target.value)}
error={introError} // replaces the hint, as in Field
/>;
已打入文本 onChange ,工具栏编辑起火 onChange 我也一样.
粗体和斜体包入选择(或用光标插入标记)
之间; 链接将其包装为 [selection](https://) 选择 URL
占位符。 默认提示语法名称; 通过 hint 用来替换它.
代码
自定义 HTML / Liquid 的标签单空间文本区域 。 它没有编辑器 依赖性:拼写检查、自动资本化、自动完成和软包装关闭, 和 Tab 移动关注而不是被困.
import { CodeArea } from "@flopay/ui";
<CodeArea
label="Custom HTML"
value={source}
onChange={(event) => setSource(event.target.value)}
/>;
选择
贴有标签的本地人 <select> (例如电子邮件编辑器的语言)
开关者),被摆出像 Field(单位:千美元) field-label 链接到 htmlFor,则
选择 Input 看着field field-select) (中文(简体) ). 保持原生状态
控制,所以Tab,箭头键和取站台的工作,它保持
浏览器的chevron.
import { Select } from "@flopay/ui";
<Select
id="locale"
label="Language"
value={locale} // "" selects the Default option
options={[
{ value: "", label: "Default" },
{ value: "fr", label: "French" },
{ value: "de", label: "German" },
]}
onChange={(next) => setLocale(next)} // the chosen value, not the event
/>;
仅控制: value 选择选定的选项 onChange
以字符串的形式接收所选选项的值. disabled 财务报告和审定财务报表 name 通过
通到 <select>没有它们,它就没有属性.
变量选择器
“插入变量”菜单按钮附在 SlotTextField 或者说 CodeArea
通过其 ref。选择变量时,单击或使用箭头键和
输入,将字段的选择替换为 Liquid 标签 {{ name }} (或
如果球场没有焦点,就把它附着起来,发射球场 onChange,以及
返回标记后光标对字段的焦点。 逃跑关闭
列表并返回焦点到按钮;如果没有变量,则该按钮被禁用.
import { useRef } from "react";
import { CodeArea, VariablePicker } from "@flopay/ui";
const bodyRef = useRef<HTMLTextAreaElement>(null);
<CodeArea ref={bodyRef} label="Body" value={body} onChange={(event) => setBody(event.target.value)} />;
<VariablePicker
targetRef={bodyRef}
variables={[
{ name: "customer.first_name", label: "Customer first name" },
{ name: "order.total", label: "Order total" },
]}
/>;
变量列表为道具;取取取器不会取取出图案.
预览图像
一个在孤立的iframe中预览电子邮件 HTML 的容器: HTML
进去 srcdoc,以及 sandbox="" 块脚本、窗体、弹出和同源
访问,因此客户端授权的HTML永远无法到达仪表板会话.
import { PreviewFrame } from "@flopay/ui";
<PreviewFrame title="Email preview" html={renderedHtml} className="h-[720px]" />;
框架填充容器(至少高480px);尺寸为 className。 。 。 .
块列表
电子邮件块构建器的可重排列表: a ul 每件东西一行,
通过 renderItem。每个解锁行都有一个拖动手柄(本国)
HTML5 拖放, 无额外依赖性) 和本地“ 移动上”、“ 移动下” 和
"删除"按钮,所以Tab, Enter和Space工作. 手柄不突出重点
隐藏在屏幕阅读器中;键盘用户用按钮重排。 焦点
保留在行内:移动后,所移动行的按钮保留它(其另一个)
如果该按钮已禁用, 则移动该按钮, 删除后, 下一次解锁
行"删除"取出后,可以连续数回移动或删除块.
import { BlockList } from "@flopay/ui";
const [blocks, setBlocks] = useState([
{ id: "header", label: "Header", locked: true }, // never moves, no controls
{ id: "intro", label: "Intro" },
{ id: "button", label: "Button" },
{ id: "footer", label: "Footer", locked: true },
]);
<BlockList
items={blocks}
renderItem={(block) => <span>{block.label}</span>}
onChange={setBlocks} // new order, or the list without a deleted item
/>;
列表被控制:每次点击、按键或调用 onChange 一次
带有您自己项目对象的新数组,行在您通过时移动
新设 items证件必须是独一无二的。 锁好的物品永不移动,没有东西移动过去
一:第一件不能向上移动,最后一件不能向下移动,下一件
到一个被锁起的页眉或页脚不能移动过它。 这些按钮是 disabled, (中文(简体) ),
而这种滴滴被忽略。 把一行扔到另一行 移动到那一行
地点 它使服务器上没有浏览器 API 的静态标记 .
设计符号
design-tokens.json 十四种颜色和底部的真理来源
cSS和交易邮件共享半径. pnpm gen:design-tokens 写入
已运出 styles/tokens.css 确定; 不编辑生成的文件
直接说 CSS 只保留在生成器模板中而不是扩展
超过消费者的共享表。 CSS定义了地表,边界,
文本,口音, 品牌(--brand: #1785e0(中文(简体) ),状态颜色、半径、布局
(单位:千美元)--max-w, (中文(简体) ), --nav-h),并进行打字. A 类 .dark 块提供了暗主题
起点.
开发
pnpm install
pnpm dev # tsup --watch
pnpm build # emits dist/: esm + cjs (tsup), then d.ts + d.cts (tsc, bundled); CSS ships as-is
pnpm gen:design-tokens # regenerate styles/tokens.css from design-tokens.json
pnpm gen:design-tokens:check # fail without writing if generated CSS is stale
pnpm typecheck
pnpm knip # unused files / exports / dependencies, non-writing
pnpm knip:fix # strips unused exports and exported types (never deletes files or edits package.json)
pnpm lint # biome check, non-writing
pnpm lint:fix # biome check --write
pnpm verify # the gate: knip + lint + typecheck, non-writing; CI and the pre-commit hook run it
pnpm go # fix-then-check: knip:fix + lint:fix, then typecheck + test + build; run before pushing
释放
在Gitea上剪辑并出版发行版本(git.flopay.com/flopay/ui) (中文(简体) ). 没有GitHub推
镜子,所以 .github/workflows/publish.yml 从不运行 :
- 运输牵引请求含有放行意图,
.release/pending/pr-<number>.md(单位:千美元).release/README.md) Gitea号check工作流程对照拉出请求的标签进行验证. - 合并时,Gitea的
release工作流程(.gitea/workflows/release.yml运行检查,颠倒 版本一次, 承诺chore(release): vX.Y.Z标记vX.Y.Z并推进两者. - 同样的跑步
publish任务发布到 npm 每vX.Y.Z标记比最高版本更新 npm服务,最古老的第一,所以最新的结局是:latest。每个都用自己的标签建在 节点mise.toml针. 任务用兽人的秘密认证NPM_TOKEN(一个npm) 自动令牌),跳过 npm 版本已有,并等待 npm 服务每个版本. (无npm) 出处:这需要GitHub的或GitLab的OIDC,而这个repo是私人的. )
Gitea 的秘密和变量列于 .gitea/README.md。 。 。 .
CSS文件为生船(没有建造步骤);只有JS被tsup捆绑. TypeScript 7 无编译器
TypeScript 7 编译器发布声明
(单位:千美元)tsc -p tsconfig.build.json输入 dist/.types和 scripts/build-dts.mjs 捆绑每个条目
点的进入 dist/<entry>.d.ts 财务报告和审定财务报表 .d.cts 与滚出-插入-dt.