Crea un plan de aprendizaje local con el FormField compuesto, el TextInput controlado y el PrimaryButton de Canvas Kit, y distingue las fuentes actuales de tokens semánticos de los ejemplos de migración antiguos de space.x4.
Interpretación original y sin conexión HTML de los patrones de interacción de Canvas y de una jerarquía empresarial sobria. La paleta y la maquetación son decisiones de la aplicación, no una representación de un tenant de Sana. · Referencia de implementación
02
Referencia de diseño del sistema de diseño de Workday Canvas
Código fuente actual de Canvas Kit y sys.json de tokens de Canvas. Los nombres JSON sin procesar que aparecen a continuación son rutas del código fuente, no nombres de variables CSS generados e inventados. Los valores que son alias de tokens base se muestran como alias; la referencia de migración fechada se identifica explícitamente como histórica.
Sistema de diseño de Workday Canvas: consulta el código fuente actual antes de copiar un ejemplo de migración
El espaciado ahora distingue entre funciones
Canvas ha evolucionado desde las constantes de espacio de tipo camiseta de canvas-kit-react/tokens, pasando por los tokens system.space numerados, hasta categorías del código fuente como padding y gap. El sys.json actual expresa sus valores subyacentes mediante referencias a tokens base. Esta referencia conserva las rutas reales en lugar de inventar una variable CSS concatenando palabras.
La tabla de migración sigue siendo útil al convertir una aplicación antigua: explica por qué el valor space.s anterior equivalía a un rem. Es una referencia histórica, no una garantía de que cada exportación antigua sea la interfaz actual recomendada. La vista previa utiliza intervalos de aplicación elegidos expresamente de 16 y 24px; la tabla del código fuente sigue siendo la autoridad para los nombres de tokens de la biblioteca.
Sistema de diseño de Workday Canvas: el color depende del tenant y del estado del componente
Un botón principal es más que una muestra azul fija
PrimaryButton primero consulta brand.action.base y, si no está disponible, recurre a su color de énfasis principal heredado. Los estilos de desplazamiento y pulsación también consultan las funciones de acción de brand antes de derivar los estados mediante los helpers de color colorSpace. Por eso no basta con cambiar un único fondo codificado para reproducir el contrato de tema del componente.
La vista previa de aprendizaje original utiliza una paleta de aplicación azul y sobria, y se identifica como una interpretación HTML. No representa el tenant de un cliente concreto ni el tema más reciente de Sana Canvas. El ejemplo nativo completo permite que las capas de tokens instaladas proporcionen los colores de los componentes.
Sistema de diseño de Workday Canvas: las funciones tipográficas compuestas conservan el interlineado y el grosor
Un alias de tamaño no define todo el estilo
El objeto tipográfico actual body.sm combina la familia tipográfica, el grosor normal, el tamaño del texto del cuerpo y su interlineado. Las funciones tipográficas de encabezado utilizan el grosor negrita. Elegir únicamente un tamaño en píxeles a partir de una captura antigua haría perder esas relaciones. En el código fuente, el nombre es normal, no la clave regular que aparece en la documentación de migración antigua.
El paquete de componentes de Canvas Kit no incluye las fuentes. Mantén la carga de fuentes como una decisión de integración independiente; el ejemplo sin conexión utiliza una fuente alternativa del sistema y no afirma incluir Roboto ni una fuente de Sana.
Sistema de diseño de Workday Canvas: FormField es un componente compuesto
Deja que el campo componga su etiqueta, su campo de entrada y su indicación
El código fuente expone FormField.Label, FormField.Input y FormField.Hint. Pasar TextInput como prop al slot Input permite que el modelo genere las asociaciones de accesibilidad. Esto difiere de los ejemplos antiguos, que asignaban una prop label al campo externo y colocaban dentro un campo de entrada sin envolver.
En los formularios horizontales, horizontalStart y horizontalEnd son los valores de orientación actuales. FormField.Field agrupa el campo de entrada y la indicación dentro de esa alineación. Nuestro filtro permanece en vertical porque su explicación debe ir justo debajo de la etiqueta y del control.
Coloca una etiqueta visible real en FormField.Label.
Pasa TextInput por el slot compuesto Input.
Usa Hint para indicar el alcance del filtro.
Mantén la lógica de validación en la aplicación al introducir restricciones.
Sistema de diseño de Workday Canvas: un campo de entrada expansible sigue necesitando un contenedor flexible
Ten en cuenta el ancho mínimo
TextInput tiene un ancho mínimo de reserva de 280px, expresado mediante px2rem en su código fuente. grow solicita el ancho completo, pero no hace que un contenedor rígido de dos columnas quepa mágicamente en una ventana estrecha. El catálogo sin conexión apila en móviles la barra lateral del plan debajo de las tarjetas de cursos y permite que cada columna del diseño se reduzca.
El valor de búsqueda es un estado React ordinario. La aplicación busca coincidencias parciales sin distinguir mayúsculas y minúsculas en los títulos de sus tres cursos y muestra explícitamente cuando no hay resultados. El filtro no elimina los cursos seleccionados, porque la visibilidad y la pertenencia al plan son estados distintos.
La selección del sistema de diseño Workday Canvas persiste independientemente de los resultados visibles
Usa IDs de curso estables y calcula los totales
Cada curso original tiene un ID, un título y una duración. La lista de selección almacena IDs, mientras que el total mostrado se calcula a partir de los registros de los cursos. Filtrar el catálogo no elimina los elementos seleccionados. Al quitar un elemento, su acción Añadir vuelve a estar disponible de inmediato y el total disminuye.
Este es un escenario original de planificación local. No llama a las API de Workday, no matricula a ningún empleado ni modifica ningún registro de aprendizaje. Hacer visible este límite evita que un prototipo dé a entender que realiza una transacción comercial real.
Filtra solo la vista del catálogo.
Añade cada ID de curso una sola vez.
Calcula los cursos seleccionados y el total de minutos a partir de esos IDs.
Sistema de diseño Workday Canvas: carga los tokens antes de esperar que los componentes adopten el tema
El proveedor y las capas CSS tienen responsabilidades distintas
El ejemplo actual de inicio importa los estilos de variables base, de marca, de componentes y del sistema en el punto de entrada de la aplicación y, después, envuelve la interfaz con CanvasProvider. Un proveedor no sustituye esas definiciones CSS. El código sigue esta composición y proporciona una raíz React completa.
Sana Canvas es opcional: sus variables se cargan después de las variables del sistema y el atributo documentado html data-theme lo selecciona. Las aplicaciones integradas que no pueden controlar la raíz del documento tienen una opción de tema específica en el proveedor para propagarlo a las ventanas emergentes. Al actualizar los paquetes, no añadas ese tema por accidente a un tenant existente.
Ejemplos de componentes del sistema de diseño Workday Canvas
Muestra interactiva independiente completa
Guarda el documento completo como index.html y ábrelo en un navegador. Interpretación original sin conexión HTML de los patrones de interacción de Canvas y de una jerarquía empresarial sobria. La paleta y el diseño son decisiones de la aplicación, no una representación de un tenant de 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>
Añadir un curso modifica el plan de aprendizaje de ejemplo. La implementación nativa muestra por separado CanvasProvider y el API compuesto FormField; no se envía ninguna solicitud de inscripción.
Campo compuesto con elementos de aprendizaje seleccionables
FormField establece la relación con el campo de entrada, TextInput filtra los datos de los cursos originales y cada PrimaryButton añade un elemento una sola vez. Al quitar un elemento, se vuelve a calcular el total seleccionado.
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 corresponde a cada curso independiente; el plan completo no tiene una acción de inscripción que pueda inducir a error.
Quitar usa un botón nativo controlado explícitamente por la aplicación.
El estado Añadido deshabilitado evita duplicados, mientras un contador dinámico confirma el cambio.
04
Configuración del agente del sistema de diseño Workday Canvas
Punto de partida para la implementación
Composición React completa para una aplicación empaquetada. Configuración actual de CanvasProvider y de tokens CSS; datos originales de cursos y controles nativos para quitarlos. Sin inscripciones remotas. Código fuente revisado, pero no compilado con paquetes en este espacio de trabajo.
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>);
Una instrucción para tu proyecto
Instrucción para el agente
Usa la composición actual de Canvas FormField y las capas de tokens CSS. Mantén el filtrado de cursos separado del estado de selección, evita duplicados y calcula el total de minutos. Conserva la tematización del tenant mediante roles semánticos; no confundas los nombres históricos de espacios numerados con las categorías de origen preferidas actualmente.
05
Fuentes y alcance del sistema de diseño Workday Canvas
Referencia independiente de OpenDesign. No hay inscripciones de empleados ni se utilizan elementos de Workday API. Se distinguen explícitamente los nombres actuales de las fuentes de los nombres anteriores usados durante la migración.