01Workday Canvas 设计系统界面预览
筛选课程标题、添加课程(每门仅限一次)、移除已选课程,并查看实时总时长。
这是对 Canvas 交互模式和克制的企业级层级结构所做的原创离线 HTML 解读。配色和布局是应用自身的选择,并非 Sana 租户的渲染效果。·
实现参考 02Workday Canvas 设计系统设计参考
当前 Canvas Kit 源码和 Canvas 设计令牌 sys.json。下方的原始 JSON 名称是源码路径,并非臆造的生成式 CSS 变量名称。与基础设计令牌别名对应的值会标注为别名;带日期的迁移参考明确标注为历史内容。
| 名称 / 规则 | 值 / 配置 | 用途 |
|---|
sys.padding.xs | {base.size.100} | 输入框的水平内边距角色。 |
sys.padding.md | {base.size.200} | 组件默认内边距。 |
sys.padding.xl | {base.size.300} | 卡片内边距。 |
sys.gap.sm | {base.size.100} | 紧凑布局中的相邻元素间距。 |
sys.gap.md | {base.size.200} | 默认行内间距。 |
sys.gap.xl | {base.size.400} | 卡片内侧留白。 |
sys.gap.xxl | {base.size.800} | 区块间距。 |
| 名称 / 规则 | 值 / 配置 | 用途 |
|---|
sys.type.body.sm | font-size body.sm + line-height body.sm + normal weight | 复合正文文本角色。 |
sys.font-size.body.sm | {base.size.200} | 正文基础字号别名。 |
sys.line-height.body.sm | {base.size.300} | 正文行高别名。 |
sys.font-weight.normal | {base.font-weight.400} | 当前名称为 normal,而非 regular。 |
sys.shape.sm | {base.size.50} | 小圆角。 |
sys.shape.md | {base.size.100} | 输入框圆角。 |
sys.shape.full | {base.size.75} × 100 | 全圆角。 |
| 名称 / 规则 | 值 / 配置 | 用途 |
|---|
FormField.Label | Connected label element | 与输入框的生成式关联。 |
FormField.Input as={TextInput} | Polymorphic input slot | 这不是旧版的 label 属性 API。 |
FormField.Hint | Linked helper content | 说明输入框的预期内容。 |
orientation | vertical / horizontalStart / horizontalEnd | 使用当前支持的对齐方式。 |
FormField.Field | Input and hint alignment wrapper | 适用于水平布局。 |
| 名称 / 规则 | 值 / 配置 | 用途 |
|---|
grow | 100% width when true | 输入框的响应式扩展。 |
width | number or string | 可选的覆盖值。 |
minimum width fallback | px2rem(280) | 检查狭窄父容器中的布局。 |
error | error / caution styling | 由应用提供验证逻辑。 |
transition | 0.2s box-shadow, 0.2s border-color | 源码中的实际转换。 |
font size role | system.legacy.fontSize.subtext.lg | 不要用无关的标题类型替换组件内部实现。 |
| 名称 / 规则 | 值 / 配置 | 用途 |
|---|
Background | brand.action.base, with legacy accent fallback | 租户品牌样式。 |
Hover | brand.action.dark or colorSpace.hover | 状态颜色根据操作颜色派生。 |
Pressed | brand.action.darkest or colorSpace.pressed | 明显的按压态样式。 |
variant="inverse" | Inverse surface and foreground roles | 适用于对比度合适的背景。 |
Border radius | system.legacy.shape.full | 当前源码仍保留旧版兼容性。 |
| 名称 / 规则 | 值 / 配置 | 用途 |
|---|
Former space.s → system.space.x4 | 1rem in 2023 migration table | 当前源码将内边距与间距区分开来。 |
Former space.m → system.space.x6 | 1.5rem in 2023 migration table | 不要假定旧名称仍是首选。 |
Former borderRadius.m → system.shape.x1 | 0.25rem in 2023 migration table | 当前源码使用语义化的形状尺寸。 |
03Workday Canvas 设计系统完整指南
实用指南,介绍如何为真实界面选择、应用和评审此系统。
DESIGN-workday-canvas.md完整指导、实现起点和源代码链接。
下载指南Workday Canvas 设计系统:复制迁移示例前,先阅读当前源码
间距现在按用途区分
Canvas 的间距体系从 canvas-kit-react/tokens 中的 T 恤尺码间距常量,演进到带编号的 system.space 设计令牌,再发展为 padding 和 gap 等源码分类。当前 sys.json 通过基础设计令牌引用来表达这些分类对应的底层值。本参考保留了实际路径,而不是通过拼接单词臆造 CSS 变量。
在转换旧版应用时,迁移表仍然很有参考价值:它解释了为什么过去的 space.s 对应 1rem。不过,这只是历史依据,并不意味着所有旧版导出项都是当前首选接口。预览中使用的是应用自行选定的 16 和 24px 间隔;设计令牌名称应以源码表为准。
阅读配套规范
Workday Canvas 设计系统:颜色由租户和组件状态决定
主按钮不只是一个固定的蓝色色块
PrimaryButton 会先读取 brand.action.base;如果没有该值,则回退到旧版主强调色。悬停态和按压态样式也会先查询 brand 操作颜色角色,再通过 colorSpace 辅助函数派生状态颜色。因此,仅更改一个硬编码的背景色无法复现组件的主题约定。
原始学习预览采用克制的蓝色应用配色,并明确标注为一种 HTML 解读。它并非某个特定客户租户或新版 Sana Canvas 主题的渲染效果。完整的原生示例会使用已安装的设计令牌层来提供组件颜色。
阅读配套规范
Workday Canvas 设计系统:复合角色保留行高和字重
字号别名并不等于完整的文本样式
当前的 body.sm 文本对象组合了字体系列、normal 字重、正文字号和正文行高。标题角色使用粗体字重。若只从旧截图中挑选一个像素字号,这些关联就会丢失。源码使用 normal,而不是旧版迁移说明中出现的 regular 键。
Canvas Kit 组件包不包含字体。字体加载应作为独立的集成决策;离线示例使用系统备用字体,不会声称内置了 Roboto 或 Sana 字体。
阅读配套规范
Workday Canvas 设计系统的 FormField 是一个复合组件
让字段组合标签、输入框和提示
源代码提供 FormField.Label、FormField.Input 和 FormField.Hint。将 TextInput 作为 prop 传给 Input,可让模型生成无障碍关联。这与旧示例不同:旧示例会在外层字段上设置 label prop,并直接放入未封装的输入框。
对于水平表单,当前的方向值为 horizontalStart 和 horizontalEnd。FormField.Field 会在该对齐方式下将输入框与提示组合在一起。我们的筛选器仍采用垂直布局,因为说明文字应紧跟在标签和控件下方。
- 在 FormField.Label 中放置清晰可见的标签。
- 通过复合组件的 Input 插槽传入 TextInput。
- 使用 Hint 说明筛选范围。
- 添加约束时,将验证逻辑保留在应用中。
阅读配套规范
Workday Canvas 设计系统中,选择状态独立于可见结果而保留
使用稳定的课程 ID 并推导总时长
每门原始课程都有 ID、标题和时长。选中列表保存 ID,显示的总时长则根据课程记录推导得出。筛选目录不会移除已选项目。移除项目后,其“添加”操作会立即恢复,且总时长会相应减少。
这是一个原创的本地规划场景。它不会调用 Workday API、为员工办理注册,也不会修改学习记录。明确这一界限,可避免原型被误认为会执行真实的业务交易。
- 仅筛选目录视图。
- 每个课程 ID 仅添加一次。
- 根据这些 ID 推导已选课程和总分钟数。
- 移除已选项目,但不重置搜索。
阅读配套规范
Workday Canvas 设计系统中,使用主题组件前应先加载令牌
Provider 与 CSS 层各司其职
当前的入门示例会在应用入口处导入基础、品牌、组件和系统变量样式,然后使用 CanvasProvider 包裹界面。仅使用 provider 不能替代这些 CSS 定义。此处的代码遵循这一组合方式,并提供完整的 React 根节点。
Sana Canvas 为可选项:其变量会在系统变量之后加载,文档中所述的 html data-theme 属性用于选择该主题。无法控制文档根节点的嵌入式应用,可通过专用的 provider 主题选项将主题传递到弹出窗口。升级软件包时,不要意外将该主题添加到现有租户。
阅读配套规范
Workday Canvas 设计系统组件示例
完整的独立交互示例
将完整文档保存为 index.html,然后在浏览器中打开。这是对 Canvas 交互模式和克制的企业级层级结构的原创离线 HTML 解读。配色和布局由应用自行决定,并非 Sana 租户的实际渲染效果。
html
<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Canvas learning plan interpretation</title><style>*{box-sizing:border-box}body{margin:0;background:#f3f5f7;color:#20272f;font:16px/1.5 system-ui,sans-serif}button,input{font:inherit}header{background:white;border-bottom:1px solid #d5dbe0;padding:16px 24px;display:flex;gap:12px;align-items:center}.mark{width:32px;height:32px;border:4px solid #ed9b28;border-bottom:0;border-radius:50% 50% 0 0;height:16px;margin-right:8px}header span{font-size:14px;color:#59616b;margin-left:auto}main{max-width:1100px;margin:32px auto;padding:0 24px}h1{font-size:28px;line-height:36px;margin:0 0 8px}p{margin:0 0 24px}label{display:block;font-size:14px;font-weight:600;margin-bottom:8px}#search{width:100%;max-width:580px;height:40px;border:1px solid #89949f;border-radius:8px;padding:8px 12px;background:white}.hint{font-size:12px;color:#59616b;margin:8px 0 24px}.layout{display:grid;grid-template-columns:1fr 290px;gap:24px;align-items:start}.course{display:grid;grid-template-columns:96px 1fr;gap:24px;background:white;padding:24px;border-radius:8px;border:1px solid #d5dbe0;margin-bottom:16px;min-width:0}.art{height:96px;display:grid;place-items:center;border-radius:8px;font-size:40px;color:#005cb9;background:#e7f1fd}.art.orange{background:#fff0d6;color:#805200}.art.green{background:#e6f4ed;color:#236443}.category{font-size:12px;color:#59616b;text-transform:uppercase;letter-spacing:1px}h2{font-size:20px;line-height:28px;margin:4px 0 8px}.course p{font-size:14px;margin:0 0 16px;color:#59616b}.row{display:flex;justify-content:space-between;align-items:center;gap:16px}.row span{font-size:14px;color:#59616b}button{border:0;border-radius:999px;background:#005cb9;color:white;font-size:14px;font-weight:600;padding:8px 16px;cursor:pointer}button:disabled{opacity:.5;cursor:default}button:focus-visible,input:focus-visible{outline:3px solid #005cb9;outline-offset:3px}.plan{padding:24px;background:white;border:1px solid #d5dbe0;border-radius:8px}.plan h2{margin:0 0 16px}.number{font-size:36px;font-weight:700;line-height:1.2}.plan-total{padding-bottom:20px;border-bottom:1px solid #d5dbe0;margin-bottom:20px}.plan-total span{font-size:14px}.plan ul{padding:0;list-style:none;margin:0}.plan li{border-bottom:1px solid #e6e9ed;padding:0 0 16px;margin:0 0 16px;font-size:14px}.remove{background:transparent;color:#005cb9;padding:4px 0;display:block;border-radius:3px;text-decoration:underline;font-weight:400}.empty{font-size:14px;color:#59616b}.boundary{font-size:12px;color:#59616b;margin-top:24px}.no-results{background:white;border:1px solid #d5dbe0;padding:24px;border-radius:8px}@media(max-width:720px){main{padding:0 16px;margin:24px auto}.layout{grid-template-columns:1fr}.course{padding:16px;grid-template-columns:60px 1fr;gap:16px}.art{height:60px;font-size:28px}header span{display:none}.row{align-items:flex-start;flex-direction:column;gap:8px}.plan{padding:20px}}
</style><header><div class="mark" aria-hidden="true"></div><strong>Learning workspace</strong><span>Personal development · Local plan</span></header><main><h1>Make room to grow</h1><p>Choose a few focused lessons for your learning plan.</p><label for="search">Find a course</label><input id="search" type="search" placeholder="Search course titles" aria-describedby="hint"><p id="hint" class="hint">Search filters the catalog; your selected courses stay in the plan.</p><div class="layout"><section id="catalog" aria-label="Course catalog"></section><aside class="plan"><h2>Your local plan</h2><div class="plan-total" role="status"><div class="number" id="minutes">0</div><span>minutes · <span id="count">0</span> <span id="course-word">courses</span></span></div><ul id="selected"></ul><p class="empty" id="empty">Add a course to start your plan.</p><small>No enrollment is submitted.</small></aside></div><p class="boundary">Independent HTML interpretation of Canvas composition patterns. Original palette, course illustrations and content; no Workday account or enrollment service is connected.</p></main><script>const courses=[{id:'feedback',name:'Giving useful feedback',minutes:25,category:'Communication',description:'Practice clear observations and constructive next steps.',art:'◎',color:''},{id:'planning',name:'Planning a focused week',minutes:20,category:'Personal effectiveness',description:'Protect attention and organize a realistic working week.',art:'▤',color:'orange'},{id:'meetings',name:'Running inclusive meetings',minutes:30,category:'Teamwork',description:'Make space for different voices and useful decisions.',art:'◌',color:'green'}];let chosen=new Set;const catalog=document.querySelector('#catalog');function render(){let matches=courses.filter(c=>c.name.toLowerCase().includes(document.querySelector('#search').value.toLowerCase()));catalog.innerHTML=matches.length?matches.map(c=>`<article class="course"><div class="art ${c.color}" aria-hidden="true">${c.art}</div><div><div class="category">${c.category}</div><h2>${c.name}</h2><p>${c.description}</p><div class="row"><span>${c.minutes} minutes</span><button data-add="${c.id}" ${chosen.has(c.id)?'disabled':''}>${chosen.has(c.id)?'Added to plan':'Add to plan'}</button></div></div></article>`).join(''):'<div class="no-results">No matching courses. Try “feedback” or clear your search.</div>';let selected=courses.filter(c=>chosen.has(c.id));document.querySelector('#minutes').textContent=selected.reduce((sum,c)=>sum+c.minutes,0);document.querySelector('#count').textContent=selected.length;document.querySelector('#course-word').textContent=selected.length===1?'course':'courses';document.querySelector('#empty').hidden=selected.length>0;document.querySelector('#selected').innerHTML=selected.map(c=>`<li>${c.name}<button class="remove" data-remove="${c.id}" aria-label="Remove ${c.name}">Remove</button></li>`).join('');document.querySelectorAll('[data-add]').forEach(b=>b.onclick=()=>{let id=b.dataset.add;chosen.add(id);render();document.querySelector('[data-remove="'+id+'"]').focus()});document.querySelectorAll('[data-remove]').forEach(b=>b.onclick=()=>{let id=b.dataset.remove;chosen.delete(id);render();let add=document.querySelector('[data-add="'+id+'"]');(add||document.querySelector('#search')).focus()})}document.querySelector('#search').oninput=render;render();</script></html>
代码参考
- 添加课程会更改示例学习计划。原生示例还会单独展示 CanvasProvider 和复合 FormField API;不会提交注册操作。
复合字段与可选学习项目
FormField 会生成输入框关联关系,TextInput 会筛选原创课程数据,每个 PrimaryButton 只会添加一次对应项目。移除项目后,所选课程总时长会重新计算。
jsx
// In a React 18+ application with a bundler:
// npm install @workday/canvas-kit-react @workday/canvas-tokens-web @workday/canvas-kit-styling @emotion/react @emotion/styled
import React,{useState} from 'react';
import {createRoot} from 'react-dom/client';
import {CanvasProvider} from '@workday/canvas-kit-react/common';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {PrimaryButton} from '@workday/canvas-kit-react/button';
import {system} from '@workday/canvas-tokens-web';
import {cssVar} from '@workday/canvas-kit-styling';
import '@workday/canvas-tokens-web/css/base/_variables.css';
import '@workday/canvas-tokens-web/css/brand/_variables.css';
import '@workday/canvas-tokens-web/css/component/_variables.css';
import '@workday/canvas-tokens-web/css/system/_variables.css';
// Default theme example; do not silently add a Sana theme to an existing tenant.
const courses=[{id:'feedback',name:'Giving useful feedback',minutes:25},{id:'planning',name:'Planning a focused week',minutes:20},{id:'meetings',name:'Running inclusive meetings',minutes:30}];
function LearningPlan(){
const [query,setQuery]=useState(''),[chosen,setChosen]=useState([]);
const visible=courses.filter(c=>c.name.toLowerCase().includes(query.toLowerCase()));
const selected=courses.filter(c=>chosen.includes(c.id));
return <main style={{fontFamily:cssVar(system.fontFamily.default),maxWidth:900,margin:'auto',padding:24}}>
<h1>Build a learning plan</h1>
<FormField><FormField.Label>Find a course</FormField.Label>
<FormField.Input as={TextInput} grow value={query} onChange={e=>setQuery(e.target.value)}/>
<FormField.Hint>Filter the three example course titles.</FormField.Hint>
</FormField>
<ul>{visible.map(c=><li key={c.id} style={{margin:'24px 0'}}><h2>{c.name}</h2><p>{c.minutes} minutes</p>
<PrimaryButton type="button" disabled={chosen.includes(c.id)} onClick={()=>setChosen(old=>[...old,c.id])}>
{chosen.includes(c.id)?'Added to plan':'Add to plan'}
</PrimaryButton></li>)}</ul>
{!visible.length&&<p>No matching courses.</p>}
<section aria-label="Learning plan"><h2>Your local plan</h2><p role="status">{selected.length} courses · {selected.reduce((sum,c)=>sum+c.minutes,0)} minutes</p>
<ul>{selected.map(c=><li key={c.id}>{c.name} <button type="button" onClick={()=>setChosen(old=>old.filter(id=>id!==c.id))}>Remove {c.name}</button></li>)}</ul>
<p>No enrollment is submitted.</p></section></main>;
}
createRoot(document.getElementById('root')).render(<CanvasProvider><LearningPlan/></CanvasProvider>);代码参考
- 每个独立课程项目都配有 PrimaryButton;整个计划不会提供容易造成误导的注册操作。
- 移除操作使用明确由应用负责的原生按钮。
- 禁用的“已添加”状态可防止重复添加,动态计数则会确认更改。
04Workday Canvas 设计系统 AI Agent 配置
实现起点
适用于打包应用的完整 React 组合。当前的 CanvasProvider 和令牌 CSS 配置;原创课程数据与原生移除控件。不执行远程注册。已检查源代码,但未在此工作区中编译软件包。
jsx
// In a React 18+ application with a bundler:
// npm install @workday/canvas-kit-react @workday/canvas-tokens-web @workday/canvas-kit-styling @emotion/react @emotion/styled
import React,{useState} from 'react';
import {createRoot} from 'react-dom/client';
import {CanvasProvider} from '@workday/canvas-kit-react/common';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {PrimaryButton} from '@workday/canvas-kit-react/button';
import {system} from '@workday/canvas-tokens-web';
import {cssVar} from '@workday/canvas-kit-styling';
import '@workday/canvas-tokens-web/css/base/_variables.css';
import '@workday/canvas-tokens-web/css/brand/_variables.css';
import '@workday/canvas-tokens-web/css/component/_variables.css';
import '@workday/canvas-tokens-web/css/system/_variables.css';
// Default theme example; do not silently add a Sana theme to an existing tenant.
const courses=[{id:'feedback',name:'Giving useful feedback',minutes:25},{id:'planning',name:'Planning a focused week',minutes:20},{id:'meetings',name:'Running inclusive meetings',minutes:30}];
function LearningPlan(){
const [query,setQuery]=useState(''),[chosen,setChosen]=useState([]);
const visible=courses.filter(c=>c.name.toLowerCase().includes(query.toLowerCase()));
const selected=courses.filter(c=>chosen.includes(c.id));
return <main style={{fontFamily:cssVar(system.fontFamily.default),maxWidth:900,margin:'auto',padding:24}}>
<h1>Build a learning plan</h1>
<FormField><FormField.Label>Find a course</FormField.Label>
<FormField.Input as={TextInput} grow value={query} onChange={e=>setQuery(e.target.value)}/>
<FormField.Hint>Filter the three example course titles.</FormField.Hint>
</FormField>
<ul>{visible.map(c=><li key={c.id} style={{margin:'24px 0'}}><h2>{c.name}</h2><p>{c.minutes} minutes</p>
<PrimaryButton type="button" disabled={chosen.includes(c.id)} onClick={()=>setChosen(old=>[...old,c.id])}>
{chosen.includes(c.id)?'Added to plan':'Add to plan'}
</PrimaryButton></li>)}</ul>
{!visible.length&&<p>No matching courses.</p>}
<section aria-label="Learning plan"><h2>Your local plan</h2><p role="status">{selected.length} courses · {selected.reduce((sum,c)=>sum+c.minutes,0)} minutes</p>
<ul>{selected.map(c=><li key={c.id}>{c.name} <button type="button" onClick={()=>setChosen(old=>old.filter(id=>id!==c.id))}>Remove {c.name}</button></li>)}</ul>
<p>No enrollment is submitted.</p></section></main>;
}
createRoot(document.getElementById('root')).render(<CanvasProvider><LearningPlan/></CanvasProvider>);适用于你项目的提示词
Agent 提示词
使用当前的 Canvas FormField 组合方式和令牌 CSS 层级。课程筛选与选择状态应彼此独立,避免重复添加,并根据课程计算总分钟数。通过语义角色保留租户主题;不要将历史编号的间距名称误认为当前推荐的来源类别。
05Workday Canvas 设计系统来源与范围
独立的 OpenDesign 参考。此处不涉及员工报名或 Workday API。明确区分当前来源名称与较早的迁移名称。