# B.C. 设计系统

## B.C. 设计系统 界面预览

在电子邮件和电话之间切换并保留输入，然后验证并查看本地联系方式偏好。

## B.C. 设计系统 设计参考

原生 @bcgov/design-system-react-components 0.8.1、设计令牌 5.0.0 和 BC Sans 2.1.2。

### 界面表面与文本角色

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| --surface-color-primary-button-default | #013366 | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --surface-color-primary-button-hover | #1e5189 | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --surface-color-primary-button-disabled | #edebe9 | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --surface-color-background-white | #ffffff | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --surface-color-background-light-gray | #faf9f8 | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --surface-color-background-light-blue | #f1f8fe | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --surface-color-border-default | #d8d8d8 | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --surface-color-border-medium | #898785 | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --surface-color-border-active | #2e5dd7 | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --typography-color-primary | #2d2d2d | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --typography-color-secondary | #474543 | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --typography-color-link | #255a90 | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --typography-color-danger | #ce3e39 | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --typography-color-primary-invert | #ffffff | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
### 布局节奏

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| --layout-padding-hair | 0.125rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --layout-padding-xsmall | 0.25rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --layout-padding-small | 0.5rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --layout-padding-medium | 1rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --layout-padding-large | 1.5rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --layout-padding-xlarge | 2rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --layout-margin-medium | 1rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --layout-margin-large | 1.5rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --layout-margin-xlarge | 2rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --layout-margin-xxxlarge | 3rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --layout-border-radius-small | 2px | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --layout-border-radius-medium | 4px | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --layout-border-radius-large | 6px | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
### 字体阶梯

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| --typography-font-size-small-body | 0.875rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --typography-font-size-body | 1rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --typography-font-size-large-body | 1.125rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --typography-font-size-h5 | 1.25rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --typography-font-size-h4 | 1.5rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --typography-font-size-h3 | 1.75rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --typography-font-size-h2 | 2rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --typography-font-size-h1 | 2.25rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
| --typography-font-size-display | 3rem | 已发布的 B.C. 角色；应按用途使用，而不是替换成原始颜色或尺寸值。 |
## B.C. 设计系统 完整指南

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

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

## B.C. 设计系统 当前 React 库与设计令牌

### 选择受维护的实现

> 当前的 B.C. Design System 提供基于 React Aria 构建的组件。此示例固定使用 react-components 0.8.1 及其依赖 design-tokens 5.0.0。较早的 B.C. Web Components 仓库是独立的旧版实现；导入它并不能复现当前的 React 库。复制示例前，请先确认实现与文档相符。

### 导入实际字体

这些组件使用带空格的 BC Sans 字体系列名称。2.1.2 版本提供 css/BC_Sans.css；旧版 BCSans.css 条目已标记为弃用，且声明了不同的字体系列名称。如果开发人员的电脑上已安装 BC Sans，可能会掩盖字体未导入的问题。此示例内置了已发布的字体，因此显示效果不依赖本地安装。

## B.C. 设计系统语义颜色与间距角色

### 先看角色，再选择对应值

主按钮使用深蓝色主按钮填充色，辅助内容使用 typography-color-secondary，错误消息使用 typography-color-danger。表格列出了当前 CSS 的导出名称；即使未来主题更改底层值，名称表达的用途仍保持稳定。请勿将非活动控件颜色与普通辅助文本颜色混淆。

### 有意识地规划布局尺寸

表单字段之间相距 24px，主要任务与辅助说明之间则留出更宽的 48px 间距。这些数值与库的间距节奏相符，而整体双栏布局是为此示例单独设计的。在移动端，布局会合并为单栏，同时保留表单标签的字号，也不会将说明文字变成工具提示。

## B.C. 设计系统 条件字段与保留值

### 仅验证所选的联系方式

原生 RadioGroup 用于控制显示“电子邮件地址”还是“电话号码”TextField。切换方式时，两个值都会保留在 React 状态中，但只有当前选中的字段会参与审核校验。这样既能保留有用的输入，也能避免不可见字段阻碍当前任务。

### 提供清晰易懂的本地错误提示

电子邮件字段会检查地址格式是否基本正确，电话号码示例则要求至少包含十位数字。这些明确只是示例规则，并非完整的国际联系方式验证。提交审核时，如果输入无效，会设置 isInvalid 并提供针对该字段的 errorMessage。未勾选同意框时会显示消息；焦点会移至第一个无效控件。

## B.C. 设计系统 React Aria 组件约定

### 使用受控值和文档规定的处理器

TextField 接收 value 和 onChange；RadioGroup 接收 value 和 onChange；Checkbox 使用 isSelected 和 onChange。这些处理器接收文档规定的值，而不是必须通过 event.target 读取的浏览器事件对象。Button 使用 type=submit，并依靠原生表单的提交事件，使键盘提交也遵循相同的验证流程。

### 让审核反馈保持同步

偏好审核区域会根据所选联系方式显示本地摘要。任何更改都会清除旧结果，并重置已提交的错误状态。此示例不会声称已发送电子邮件或安排提醒。若要发送真实通知，必须先另行明确同意、数据持久化和发送机制，才能作出此类说明。

## B.C. 设计系统 无障碍名称与响应式布局

### 确保标签、说明和错误提示彼此关联

原生组件提供 React Aria 对标签、帮助文本和错误消息的关联支持。应用仍须提供有意义的标签、清晰的分组说明和易于理解的验证文本。测试时应点击可见的单选项标签并使用键盘操作；内部视觉隐藏的输入框不是可见的交互目标。

### 将辅助说明与表单分开

旁边的说明框解释了本地操作流程，并使用原生 details 展示实现背景。在窄屏布局中，它会按阅读顺序排在表单之后。信息图标仅作装饰，含义由标题表达。研究这些组件无需使用 B.C. 官方标志，也不必声称这是政府服务。

## B.C. 设计系统 完整的 React 实现

### 复现经验证的源代码

创建 Vite React 项目，并安装固定版本的组件、令牌和字体包。使用提供的 main.jsx 和 bc.css，并添加根挂载元素。此代码包包含原生单选框、字段、复选框和按钮行为，以及完整的本地验证和状态转换。无需私有的 API 或凭据。

1. 安装固定版本的软件包，并导入 css/BC_Sans.css。

2. 保存 main.jsx 和 bc.css，并保留根挂载元素。

3. 提交空表单，然后修正可见字段中的内容。

4. 切换联系方式，并确认先前输入的值仍然保留。

### 让条件验证与所选联系方式保持一致

RadioGroup 和 TextField 的 onChange 处理器接收值，Checkbox 的 onChange 处理器则接收所选布尔值。直接存储这些值，不要对所有组件一概使用通用的 event.target.value 处理器。即使 React Aria 在视觉上隐藏了原生输入框，可见标签仍是交互目标。

更改联系方式时，保留两项草稿值，但清除旧的审核结果，并且只验证当前选中的方式。提交后，将焦点移至第一个可见的无效控件。这样一来，用户可以比较不同联系方式，而不会丢失电子邮件地址，也不会被隐藏的电话字段阻挡。示例中的十位数字电话规则仅适用于本地场景，应替换为服务实际要求的国际电话号码规则。

- 仅验证所选的联系方式字段。

- 更改任一选项都会清除旧的审核反馈。

- 结果不会声称已安排提醒。

## B.C. 设计系统组件示例

### main.jsx — 完整可运行示例

选择并审核一种联系方式。以下源代码是完整的 React 入口文件，包含全部状态和验证逻辑。

```
import React, { useState, useRef } from "react";
import { createRoot } from "react-dom/client";
import {
  Button,
  Radio,
  RadioGroup,
  TextField,
  Checkbox,
} from "@bcgov/design-system-react-components";
import "@bcgov/bc-sans/css/BC_Sans.css";
import "./bc.css";
function App() {
  const [method, setMethod] = useState("email"),
    [email, setEmail] = useState(""),
    [phone, setPhone] = useState(""),
    [consent, setConsent] = useState(false),
    [submitted, setSubmitted] = useState(false),
    [result, setResult] = useState("");
  const panel = useRef(null);
  const invalidEmail = !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
    invalidPhone = phone.replace(/\D/g, "").length < 10;
  function change(fn, value) {
    fn(value);
    setResult("");
    setSubmitted(false);
  }
  function review(e) {
    e.preventDefault();
    setSubmitted(true);
    if ((method === "email" ? invalidEmail : invalidPhone) || !consent) {
      requestAnimationFrame(() =>
        panel.current
          .querySelector(
            '[aria-invalid="true"] input,input[aria-invalid="true"],input[type=checkbox]:not(:checked)',
          )
          ?.focus(),
      );
      return;
    }
    setResult(
      `Local preference ready: ${method === "email" ? "Email to " + email : "Phone call to " + phone}. No reminder has been scheduled.`,
    );
  }
  return (
    <main>
      <header>
        <div className="eyebrow">
          SERVICE REMINDER / ORIGINAL APPLICATION STUDY
        </div>
        <h1>Choose how to hear from us</h1>
        <p>
          Set a sample reminder preference. Use fictional contact details in
          this local example.
        </p>
      </header>
      <div className="columns">
        <form onSubmit={review} noValidate ref={panel}>
          <RadioGroup
            label="Contact method"
            value={method}
            onChange={(v) => change(setMethod, v)}
            description="Choose one way to receive a reminder."
          >
            <Radio value="email">Email</Radio>
            <Radio value="phone">Phone call</Radio>
          </RadioGroup>
          <div className="field">
            {method === "email" ? (
              <TextField
                label="Email address"
                type="email"
                value={email}
                onChange={(v) => change(setEmail, v)}
                isRequired
                isInvalid={submitted && invalidEmail}
                errorMessage="Enter an email address in the format name@example.com."
                description="Use a sample address, such as alex@example.com."
              />
            ) : (
              <TextField
                label="Phone number"
                type="tel"
                value={phone}
                onChange={(v) => change(setPhone, v)}
                isRequired
                isInvalid={submitted && invalidPhone}
                errorMessage="Enter a phone number with at least 10 digits."
                description="Sample validation only; a real service needs a jurisdiction-aware phone rule."
              />
            )}
          </div>
          <div className="field">
            <Checkbox
              isSelected={consent}
              onChange={(v) => change(setConsent, v)}
              isInvalid={submitted && !consent}
            >
              I agree to use these details for this sample reminder
            </Checkbox>
            {submitted && !consent && (
              <p className="error" role="alert">
                Confirm the reminder preference before continuing.
              </p>
            )}
          </div>
          <Button type="submit" variant="primary">
            Review preference
          </Button>
          <p className="result" role="status">
            {result}
          </p>
        </form>
        <aside>
          <div className="status-mark" aria-hidden="true">
            i
          </div>
          <h2>What happens next</h2>
          <ol>
            <li>Choose a contact method.</li>
            <li>Review the sample details.</li>
            <li>Connect a real notification service before scheduling.</li>
          </ol>
          <p>
            This example keeps both fields while switching methods, but only
            reviews the selected method.
          </p>
          <details>
            <summary>About this specimen</summary>
            <p>
              Native B.C. React Aria components with BC Sans. The form is
              original and is not an official government service.
            </p>
          </details>
        </aside>
      </div>
    </main>
  );
}
createRoot(document.getElementById("root")).render(<App />);
```

代码参考

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

### bc.css — 原始响应式布局

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

```
* {
  box-sizing: border-box;
}
body {
  margin: 0;
  font:
    16px/1.5 "BC Sans",
    sans-serif;
  color: var(--typography-color-primary, #2d2d2d);
  background: white;
}
main {
  padding: 32px;
}
header {
  border-top: 6px solid #fcba19;
  border-bottom: 1px solid #d8d8d8;
  padding: 24px 0;
  margin-bottom: 32px;
}
.eyebrow {
  font-size: 13px;
  color: #474543;
}
h1 {
  font-size: 32px;
  line-height: 1.25;
  margin: 8px 0 16px;
}
h2 {
  font-size: 24px;
  margin: 0 0 16px;
}
p {
  margin: 8px 0 16px;
}
.columns {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: 48px;
}
form {
  max-width: 620px;
}
.field {
  margin: 24px 0;
}
aside {
  padding: 24px;
  background: #f1f8fe;
  border-left: 4px solid #1a5a96;
  align-self: start;
}
.status-mark {
  background: #1a5a96;
  color: white;
  border-radius: 50%;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  font-weight: bold;
  margin-bottom: 16px;
}
ol {
  padding-left: 24px;
}
li {
  padding-left: 4px;
  margin: 12px 0;
}
.error {
  color: #ce3e39;
  font-size: 14px;
}
.result {
  margin-top: 24px;
  color: #24613b;
}
summary {
  cursor: pointer;
  text-decoration: underline;
  color: #1a5a96;
}
details {
  margin-top: 24px;
}
details p {
  font-size: 14px;
}
@media (max-width: 767px) {
  main {
    padding: 16px;
  }
  .columns {
    grid-template-columns: 1fr;
    gap: 24px;
  }
  h1 {
    font-size: 28px;
  }
  aside {
    padding: 16px;
  }
}
```

代码参考

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

## B.C. 设计系统 Agent 设置

### 实现起点

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

```
npm create vite@latest bc-reminder -- --template react
cd bc-reminder
npm install
npm install @bcgov/design-system-react-components@0.8.1 @bcgov/design-tokens@5.0.0 @bcgov/bc-sans@2.1.2
# Replace src/main.jsx and add src/bc.css using the complete examples.
npm run dev
```

### 适用于你项目的提示词

```
使用 B.C. React components 0.8.1 和 BC Sans 2.1.2 构建提醒表单。保留条件验证、已保留的输入内容、可见错误以及过期结果清除功能。使用当前设计令牌名称和正确的带空格字体系列。尚无正式通知约定前，请将示例保留在本地。
```

## B.C. 设计系统 来源与范围

基于当前公开的 B.C. React 库进行的 OpenDesign 应用研究。这是原创的本地表单，并非官方政府服务；请使用虚构的联系信息。

- 当前组件源代码 React Aria 库与软件包边界。

- 设计令牌 语义设计令牌模型及当前值。

- 排版 BC Sans 基础与用法。

- BC Sans 源文件 公开字体系列及正确的 CSS 入口点。

## B.C. 设计系统 相关资源

### Canada.ca 设计系统

通过一份完整的可检索准备指南，探索 GC 设计系统的字体排印、语义化令牌与原生 Web 组件，并涵盖展开收起与空状态行为。

### CFPB 设计系统

实现 CFPB 的编辑网格、现行排版规范和原生 Expandables，并提供可用的阅读工作表、经过验证的软件包 API，以及明确的品牌标识使用边界。

### CMS 设计系统

使用带版本的主题变量、真实的 Review 组件 API，以及可正常运行的编辑与审核流程，实现 CMS Core，并保留用户答案和焦点。


- [实现参考](https://github.com/bcgov/design-system/)
- [来源](https://www2.gov.bc.ca/gov/content/digital/design-system/foundations/design-tokens)
- [代码参考](https://github.com/bcgov/design-system/)
- [当前组件源代码 React Aria 库与软件包边界。](https://github.com/bcgov/design-system/)
- [设计令牌 语义设计令牌模型及当前值。](https://www2.gov.bc.ca/gov/content/digital/design-system/foundations/design-tokens)
- [排版 BC Sans 基础与用法。](https://www2.gov.bc.ca/gov/content/digital/design-system/foundations/typography)
- [BC Sans 源文件 公开字体系列及正确的 CSS 入口点。](https://github.com/bcgov/bc-sans)
