数据平台 · 新资源

Segment Evergreen 设计系统.

一份 Evergreen 参考示例,包含实际主题取值、受控标签输入、实时规则匹配、空状态恢复路径和原生审核对话框。

OpenDesign 指南8 个章节36 个参考条目原始应用指南
01

Segment Evergreen 设计系统界面预览

组合计划和必需标签,检查匹配记录,在没有匹配项时恢复,并查看本地快照。

构建并查看本地受众群体打开完整示例
原生 Evergreen 7.1.9 React 组件和 defaultTheme;原创本地受众群体规则。· 实现参考
02

Segment Evergreen 设计系统设计参考

原生 Evergreen 7.1.9 React 组件和 defaultTheme;原创本地受众群体规则。

圆角半径

来源
名称 / 规则值 / 配置用途
radii.00px使用按索引定义的主题圆角;选择一个角色,而不是逐层增加每个嵌套框的圆角。
radii.14px使用按索引定义的主题圆角;选择一个角色,而不是逐层增加每个嵌套框的圆角。
radii.28px使用按索引定义的主题圆角;选择一个角色,而不是逐层增加每个嵌套框的圆角。

字号

来源
名称 / 规则值 / 配置用途
fontSizes.010px已发布默认主题中按索引列出的字号值。
fontSizes.112px已发布默认主题中按索引列出的字号值。
fontSizes.214px已发布默认主题中按索引列出的字号值。
fontSizes.316px已发布默认主题中按索引列出的字号值。
fontSizes.418px已发布默认主题中按索引列出的字号值。
fontSizes.520px已发布默认主题中按索引列出的字号值。
fontSizes.624px已发布默认主题中按索引列出的字号值。
fontSizes.732px已发布默认主题中按索引列出的字号值。

行高

来源
名称 / 规则值 / 配置用途
lineHeights.016px根据预期的文本角色搭配行高,不要让每段文字都采用标题的行高节奏。
lineHeights.118px根据预期的文本角色搭配行高,不要让每段文字都采用标题的行高节奏。
lineHeights.220px根据预期的文本角色搭配行高,不要让每段文字都采用标题的行高节奏。
lineHeights.324px根据预期的文本角色搭配行高,不要让每段文字都采用标题的行高节奏。
lineHeights.428px根据预期的文本角色搭配行高,不要让每段文字都采用标题的行高节奏。
lineHeights.532px根据预期的文本角色搭配行高,不要让每段文字都采用标题的行高节奏。
lineHeights.640px根据预期的文本角色搭配行高,不要让每段文字都采用标题的行高节奏。

字重

来源
名称 / 规则值 / 配置用途
fontWeights.light300在此主题中,Evergreen 将 semibold 定义为 500,将 bold 定义为 600;不要擅自改用浏览器关键字对应的默认值。
fontWeights.normal400在此主题中,Evergreen 将 semibold 定义为 500,将 bold 定义为 600;不要擅自改用浏览器关键字对应的默认值。
fontWeights.semibold500在此主题中,Evergreen 将 semibold 定义为 500,将 bold 定义为 600;不要擅自改用浏览器关键字对应的默认值。
fontWeights.bold600在此主题中,Evergreen 将 semibold 定义为 500,将 bold 定义为 600;不要擅自改用浏览器关键字对应的默认值。

默认调色板参考

来源
名称 / 规则值 / 配置用途
colors.gray900#101840 主要深色文本。
colors.gray700#696f8c 辅助性文本。
colors.gray400#d8dae5 明确的边界。
colors.gray200#edeff5 细微的分隔线。
colors.gray100#F4F5F9 克制的背景。
colors.blue500#3366FF 主要蓝色参考。
colors.blue600#2952CC 更醒目的蓝色交互参考。
colors.red500#D14343 严重错误参考。
colors.green500#52BD95 成功状态参考。

布局函数与受控输入 API

来源
名称 / 规则值 / 配置用途
majorScale(3)24px用于区块间距的八像素主刻度。
minorScale(3)12px用于较小间距关系的四像素次刻度。
TagInput.valuescontrolled array由应用负责管理已接受的标签。
TagInput.onChangenew array; return false to retain input接受新增标签前先进行验证。
TagInput.separatorcomma and new line by default支持多个标签,无需自定义解析器。
03

Segment Evergreen 设计系统完整指南

实用指南,介绍如何为真实界面选择、应用和评审此系统。

DESIGN-segment-evergreen.md

完整指导、实现起点和源代码链接。

下载指南

Segment Evergreen 设计系统受众构建器结构

让规则与其影响一目了然

示例将规则面板与相匹配的虚构记录并列展示。计划选项和必需标签与结果数量同时可见,因此更改规则后会产生怎样的影响一目了然。用户无需保存或打开对话框,就能查看当前条件组合是否匹配任何记录。

摘自本 OpenDesign 指南

使用有限且透明的示例数据

五条命名示例记录展示了各自的计划和标签。页面不会假装在查询客户数据仓库,也不会估算真实受众规模。结果为零也是示例中有意义的一部分,并提供了一个操作来恢复完整的示例数据。

Segment Evergreen 设计系统主题值与语义

读取实际的默认主题

Evergreen 7.1.9 除了命名颜色和组件样式,还提供按索引列出的圆角、字号和行高。表格保留了这些导出的原始值。尤其值得留意的是字重名称:semibold 和 bold 分别对应 500 和 600,与常见预期不同。

让自定义组合保持一致

ThemeProvider 提供原生输入框、标签、徽章、按钮和对话框。自定义面板采用了同样克制的冷灰与蓝色搭配。示例不会使用宽泛的 CSS 选择器覆盖原生组件内部样式,因此提供的交互样式得以保留。

Segment Evergreen 规则编辑中的设计系统排版

为任务建立清晰的层级

不同标题层级用于区分受众标题、面板标题和各条结果名称。辅助说明解释了标签输入方式和虚构数据的范围。计数徽章放在结果标题旁边,而不是取代标题,让用户先识别所在区域,再理解数字的含义。

保持元数据易读

计划和标签信息有助于解释匹配结果,并非纯装饰内容。在窄屏手机上,布局支持标签换行,并将每条记录的文本和徽章纵向排列。若把所有标签都缩小到极难辨认,用户就无法看清理解规则所需的依据。

Segment Evergreen 设计系统主刻度与次刻度

使用函数实现可复用的间距

majorScale 和 minorScale 分别表示八像素和四像素的间距步长。组件示例使用 majorScale(3) 设置面板标题下方 24px 的间距,自定义样式表则负责响应式布局。请明确区分两者:网格断点是应用层的决策,并非间距函数的计算结果。

在移动设备上保持字段关系

Segment Evergreen 设计系统受控标签与匹配

规范化后再接受输入

onChange 处理函数会移除字符串首尾空白并转换为小写、删除重复项,并拒绝超过四个不重复标签的输入。若拒绝新增标签,返回 false 可保留输入值。此规则属于受众应用的业务逻辑;TagInput 提供受控交互,但不会自行设定业务限制。

明确采用全标签匹配

记录必须同时匹配所选计划和所有必需标签。清空标签即可移除这一条件;选择 Any 则会移除计划条件。当前结果数量来自屏幕上实际渲染的同一筛选数组,避免数量与可见卡片不一致。

Segment Evergreen 设计系统审核对话框与本地快照

保存前审核当前规则

对话框打开前,受众名称不能为空。对话框会再次显示名称、数量、方案和标签,然后提供“在本地保存”选项。这个额外步骤展示了如何围绕具体快照使用原生浮层,而不是只显示一句“确定吗?”的空洞确认提示。

规则变更时清除旧的成功状态

更改名称、方案或标签会清除已保存快照的指示。因此,之前的本地保存成功状态不会被误认为是对新编辑规则的确认。应用不会将快照描述为 Segment 受众,也不会向客户平台发送请求。

Segment Evergreen 设计系统键盘输入与对话框恢复

在同一流程中测试标签、结果和审核

可以使用文档中说明的输入按键添加标签,输入框通过 id 获得可见标签。达到标签上限时会显示原生危险提示。审核界面使用原生 Dialog 的焦点行为,而不是自行构建一个无法管理焦点的浮层。

  1. 将 newsletter 添加到初始激活标签,并比较数量。
  2. 添加一个不匹配的标签,然后清除规则以恢复全部五条记录。
  3. 审核并在本地保存,然后更改一条规则,确认旧的成功状态已消失。

在当前任务中保留空状态恢复操作

清除规则会同时移除方案和标签限制,并关闭过期的已保存状态。随后,结果区域会重新显示全部五条记录。用户可以立即继续编辑;无需隐藏式导航或重新加载页面。

Segment Evergreen 设计系统 React 集成

使用完整的受控示例

原生入口会导入 ThemeProvider、defaultTheme 以及所渲染的组件。状态包含规则、错误、对话框可见性和本地保存的快照。下方的完整 JSX 和 CSS 包含虚构数据样例,因此无需外部 API 即可复现结果计算。

  • ThemeProvider 使用固定版本的 defaultTheme。
  • TagInput 的值由应用控制。
  • Dialog 审核的规则与结果列表使用的规则相同。

将数据访问作为单独的应用层添加

Segment Evergreen 设计系统组件示例

main.jsx — 完整可运行示例

构建并审核本地受众。下面的源码是完整的 React 入口,包含所有状态和验证逻辑。

jsx
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import {
  ThemeProvider,
  defaultTheme,
  Heading,
  Text,
  Paragraph,
  Button,
  TagInput,
  TextInputField,
  SelectField,
  Alert,
  Dialog,
  Badge,
  majorScale,
} from "evergreen-ui";
import "./evergreen.css";
const people = [
  { name: "Avery Stone", plan: "Pro", tags: ["active", "newsletter"] },
  { name: "Taylor Reed", plan: "Free", tags: ["newsletter"] },
  { name: "Jordan Park", plan: "Pro", tags: ["active"] },
  { name: "Morgan Lee", plan: "Team", tags: ["active", "newsletter"] },
  { name: "Casey Chen", plan: "Free", tags: ["trial"] },
];
function App() {
  const [tags, setTags] = useState(["active"]),
    [plan, setPlan] = useState("Any"),
    [name, setName] = useState("Active customers"),
    [error, setError] = useState(""),
    [open, setOpen] = useState(false),
    [saved, setSaved] = useState(null);
  const matches = people.filter(
    (p) =>
      (plan === "Any" || p.plan === plan) &&
      tags.every((t) => p.tags.includes(t)),
  );
  function changeTags(values) {
    const unique = [
      ...new Set(values.map((v) => v.trim().toLowerCase()).filter(Boolean)),
    ];
    if (unique.length > 4) {
      setError("Use at most four tags.");
      return false;
    }
    setTags(unique);
    setError("");
    setSaved(null);
  }
  return (
    <ThemeProvider value={defaultTheme}>
      <main className="audience">
        <header>
          <Text size={300}>WORKSPACE / AUDIENCES</Text>
          <Heading size={700} marginTop={8}>
            Build a focused audience
          </Heading>
          <Paragraph marginTop={8}>
            Combine a plan and tags against five fictional records. No customer
            data is loaded.
          </Paragraph>
        </header>
        <div className="audience-grid">
          <section className="rule-panel">
            <Heading size={500} marginBottom={majorScale(3)}>
              Audience rules
            </Heading>
            <TextInputField
              label="Audience name"
              value={name}
              onChange={(e) => {
                setName(e.target.value);
                setSaved(null);
              }}
            />
            <SelectField
              label="Plan"
              value={plan}
              onChange={(e) => {
                setPlan(e.target.value);
                setSaved(null);
              }}
            >
              <option>Any</option>
              <option>Free</option>
              <option>Pro</option>
              <option>Team</option>
            </SelectField>
            <label htmlFor="audience-tags">Required tags</label>
            <Paragraph size={300} marginBottom={8}>
              Press Enter or comma to add a tag. Every tag must match.
            </Paragraph>
            <TagInput
              values={tags}
              onChange={changeTags}
              width="100%"
              inputProps={{ id: "audience-tags", placeholder: "Add tag…" }}
            />
            <Paragraph size={300} marginTop={8}>
              Available sample tags: active, newsletter, trial.
            </Paragraph>
            {error && <Alert intent="danger" title={error} marginTop={16} />}
            <Button
              appearance="primary"
              marginTop={24}
              onClick={() => {
                if (!name.trim()) {
                  setError("Name the audience before reviewing.");
                  return;
                }
                setError("");
                setOpen(true);
              }}
            >
              Review audience
            </Button>
          </section>
          <section className="results-panel">
            <div className="results-title">
              <Heading size={500}>Matching sample records</Heading>
              <Badge color="blue">
                {matches.length} of {people.length}
              </Badge>
            </div>
            {matches.length ? (
              <div className="record-list">
                {matches.map((p) => (
                  <article key={p.name}>
                    <div>
                      <Heading size={400}>{p.name}</Heading>
                      <Text size={300}>{p.plan} plan</Text>
                    </div>
                    <div className="tag-list">
                      {p.tags.map((t) => (
                        <Badge key={t} color="neutral">
                          {t}
                        </Badge>
                      ))}
                    </div>
                  </article>
                ))}
              </div>
            ) : (
              <div className="empty">
                <Heading size={500}>No matching sample records</Heading>
                <Paragraph marginTop={8}>
                  Remove a tag or change the plan to broaden the selection.
                </Paragraph>
                <Button
                  marginTop={16}
                  onClick={() => {
                    setTags([]);
                    setPlan("Any");
                    setSaved(null);
                  }}
                >
                  Clear rules
                </Button>
              </div>
            )}
            {saved && (
              <Alert
                intent="success"
                title="Local audience saved"
                marginTop={20}
              >
                {saved.name} · {saved.count} sample records. Nothing was sent to
                Segment.
              </Alert>
            )}
          </section>
        </div>
        <Dialog
          isShown={open}
          title="Review local audience"
          onCloseComplete={() => setOpen(false)}
          confirmLabel="Save locally"
          onConfirm={() => {
            setSaved({ name: name.trim(), count: matches.length });
            setOpen(false);
          }}
          width={560}
        >
          <Paragraph>
            <strong>{name}</strong> matches {matches.length} of {people.length}{" "}
            fictional records.
          </Paragraph>
          <Paragraph marginTop={12}>
            Plan: {plan}. Required tags: {tags.join(", ") || "none"}.
          </Paragraph>
          <Paragraph marginTop={12}>
            This saves only a local snapshot; it does not create a Segment
            audience.
          </Paragraph>
        </Dialog>
        <footer>
          Native Evergreen 7.1.9 React components. Original audience logic; no
          analytics or customer service connection.
        </footer>
      </main>
    </ThemeProvider>
  );
}
createRoot(document.getElementById("root")).render(<App />);

代码参考

  • 创建 Vite React 项目,保留 <div id="root"></div>,并将以下内容保存为 src/main.jsx。

evergreen.css — 原始响应式布局

围绕原生组件构建的应用布局。

css
* {
  box-sizing: border-box;
}
body {
  margin: 0;
  background: #f4f5f9;
  color: #101840;
  font:
    14px/1.5 system-ui,
    sans-serif;
}
.audience {
  padding: 32px;
}
.audience header {
  max-width: 800px;
  margin-bottom: 28px;
}
.audience-grid {
  display: grid;
  grid-template-columns: minmax(260px, 0.85fr) minmax(0, 1.5fr);
  gap: 24px;
}
.rule-panel,
.results-panel {
  padding: 24px;
  background: white;
  border: 1px solid #d8dae5;
  border-radius: 8px;
  min-width: 0;
}
.rule-panel > label {
  font-weight: 600;
  display: block;
  margin-bottom: 8px;
}
.results-title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  margin-bottom: 24px;
}
.record-list article {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 20px 0;
  border-bottom: 1px solid #edeff5;
}
.record-list article:first-child {
  padding-top: 0;
}
.record-list article:last-child {
  border: 0;
}
.tag-list {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.empty {
  padding: 36px 12px;
  text-align: center;
  background: #f4f5f9;
  border-radius: 4px;
}
.audience footer {
  margin-top: 24px;
  font-size: 12px;
  color: #696f8c;
}
.audience input {
  max-width: 100%;
}
[role="dialog"] {
  max-width: calc(100vw - 32px);
}
@media (max-width: 850px) {
  .audience-grid {
    grid-template-columns: 1fr;
  }
}
@media (max-width: 550px) {
  .audience {
    padding: 16px;
  }
  .rule-panel,
  .results-panel {
    padding: 20px;
  }
  .record-list article {
    align-items: flex-start;
    flex-direction: column;
  }
  .results-title {
    align-items: flex-start;
  }
}

代码参考

  • 将其保存在 main.jsx 旁边。这些自定义组合规则有别于库的设计令牌参考。
04

Segment Evergreen 设计系统 Agent 设置

实现起点

已打包固定的原生示例,并在本地浏览器中完成检查。该示例仅使用本地应用状态。

sh
npm create vite@latest evergreen-audience -- --template react
cd evergreen-audience
npm install
npm install [email protected] [email protected] [email protected]
# Replace src/main.jsx and add src/evergreen.css.
npm run dev

适用于你项目的提示词

Agent 提示词
使用实际主题和受控的 TagInput 构建 Evergreen 受众编辑器。规范化并去重标签,明确采用全标签匹配,确保结果与计数一致,在原生 Dialog 中进行审核,并在规则变更时清除过期的成功状态。不要声称已连接 Segment 服务。
05

Segment Evergreen 设计系统来源与范围

OpenDesign Evergreen 7.1.9 应用指南。五条虚构记录用于演示本地筛选和快照审核。未连接任何 Segment 客户数据或受众 API。