Canvas 상호작용 패턴과 절제된 엔터프라이즈 계층 구조를 HTML 방식으로 해석한 오프라인 원본입니다. 팔레트와 레이아웃은 애플리케이션의 선택 사항이며, Sana 테넌트의 렌더링 결과가 아닙니다. · 구현 레퍼런스
02
Workday Canvas 디자인 시스템 디자인 참고 자료
최신 Canvas Kit 소스와 Canvas 토큰 sys.json을 사용합니다. 아래의 원시 JSON 이름은 소스 경로이며, 새로 생성한 CSS 변수명이 아닙니다. 기본 토큰을 참조하는 값은 별칭으로 표시했으며, 날짜가 명시된 마이그레이션 참고 자료는 과거 기준임을 분명히 밝혔습니다.
Workday Canvas 디자인 시스템 마이그레이션 예시를 복사하기 전에 최신 소스를 확인하세요
간격을 용도에 따라 구분합니다
Canvas는 canvas-kit-react/tokens의 티셔츠 크기 간격 상수에서 번호가 붙은 system.space 토큰을 거쳐, padding과 gap 같은 소스 범주로 발전해 왔습니다. 현재 sys.json에서는 이러한 값의 기반을 기본 토큰 참조로 표현합니다. 이 참고 자료는 여러 단어를 이어 붙여 CSS 변수를 만들어 내는 대신 실제 경로를 그대로 보여 줍니다.
이전 애플리케이션을 변환할 때는 마이그레이션 표가 여전히 유용합니다. 기존 space.s가 1rem에 해당했던 이유를 설명하기 때문입니다. 하지만 이는 과거의 근거일 뿐, 예전 내보내기 이름이 모두 현재 권장 인터페이스라는 뜻은 아닙니다. 미리보기에서는 애플리케이션에서 선택한 16px 및 24px 간격을 사용하며, 라이브러리 토큰 이름의 기준은 소스 표입니다.
PrimaryButton은 먼저 brand.action.base를 참조하고, 없으면 이전 primary 강조 색상으로 대체합니다. 호버 및 눌림 스타일도 상태를 파생할 때 brand 액션 역할을 먼저 참조한 다음 colorSpace 헬퍼를 사용합니다. 따라서 배경색 하나를 하드코딩해 바꿔도 컴포넌트의 테마 규칙을 재현할 수 없습니다.
원본 학습 미리보기는 절제된 파란색 애플리케이션 팔레트를 사용하며, HTML 방식의 해석임을 표시합니다. 특정 고객 테넌트나 최신 Sana Canvas 테마의 렌더링 결과가 아닙니다. 완전한 네이티브 예시에서는 설치된 토큰 계층이 컴포넌트 색상을 제공합니다.
Workday Canvas 디자인 시스템 복합 스타일 역할은 행간과 글꼴 두께를 유지합니다
크기 별칭만으로 스타일 전체를 나타낼 수는 없습니다
현재 body.sm 타이포그래피 객체에는 글꼴 패밀리, normal 두께, 본문 크기, 본문 행간이 함께 정의되어 있습니다. 제목 역할에는 bold 두께가 사용됩니다. 이전 스크린샷에서 픽셀 크기만 선택하면 이러한 관계가 사라집니다. 소스에서는 이전 마이그레이션 설명에 등장하는 regular 키가 아니라 normal이라는 이름을 사용합니다.
Canvas Kit 컴포넌트 패키지에는 글꼴이 포함되지 않습니다. 글꼴 로드는 별도의 통합 결정으로 처리하세요. 오프라인 예시에서는 시스템 대체 글꼴을 사용하며 Roboto나 Sana 글꼴이 번들에 포함되어 있다고 주장하지 않습니다.
소스에서는 FormField.Label, FormField.Input, FormField.Hint를 제공합니다. TextInput을 Input의 prop으로 전달하면 모델이 접근성 연결을 생성할 수 있습니다. 이는 바깥 필드에 label prop을 설정하고 래핑하지 않은 입력 요소를 안에 넣던 이전 예시와 다릅니다.
가로형 양식에는 현재 방향 값인 horizontalStart와 horizontalEnd를 사용합니다. FormField.Field는 해당 정렬 안에서 입력 요소와 도움말을 함께 묶습니다. 필터 설명을 레이블과 컨트롤 바로 아래에 표시해야 하므로 여기서는 세로형을 사용합니다.
Workday Canvas 디자인 시스템 입력 요소가 늘어나도 부모 요소는 유연해야 합니다
최소 너비를 고려하세요
TextInput의 기본 최소 너비는 280px이며, 소스에서는 px2rem으로 표현됩니다. grow는 전체 너비를 요청하는 속성이지, 고정된 2열 부모가 좁은 뷰포트에 맞도록 자동으로 조정해 주는 기능은 아닙니다. 오프라인 카탈로그는 모바일에서 계획 사이드바를 강의 카드 아래에 배치하고, 각 레이아웃 열이 줄어들 수 있도록 합니다.
검색어는 일반적인 React 상태입니다. 앱은 세 강의 제목을 대상으로 대소문자를 구분하지 않는 부분 문자열 검색을 수행하고, 결과가 없으면 이를 명시적으로 표시합니다. 표시 여부와 계획에 포함되는 여부는 별개의 상태이므로, 필터를 적용해도 선택한 강의는 해제되지 않습니다.
Workday Canvas 디자인 시스템 컴포넌트에 테마를 적용하기 전에 토큰을 로드하세요
Provider와 CSS 레이어는 서로 다른 역할을 합니다
현재 시작하기 예제는 애플리케이션 진입점에서 base, brand, component, system 변수 스타일을 가져온 다음 CanvasProvider로 인터페이스를 감쌉니다. Provider만으로는 이러한 CSS 정의를 대체할 수 없습니다. 여기에 있는 코드는 이 구성을 따르며 완전한 React 루트를 제공합니다.
Sana Canvas는 선택적으로 사용할 수 있습니다. 해당 변수는 시스템 변수 다음에 로드되며, 문서화된 html data-theme 속성으로 테마를 선택합니다. 문서 루트를 제어할 수 없는 임베디드 애플리케이션에는 팝업에 테마를 전파하는 전용 provider 테마 옵션이 있습니다. 패키지를 업그레이드할 때 기존 테넌트에 실수로 해당 테마를 추가하지 마세요.
전체 문서를 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>
PrimaryButton은 각각의 독립된 강의 항목에 속합니다. 전체 계획에는 오해를 부르는 등록 버튼이 없습니다.
제거 작업에는 애플리케이션에서 명시적으로 정의한 네이티브 버튼을 사용합니다.
추가 완료 상태에서는 중복 추가를 막도록 버튼을 비활성화하고, 실시간 개수 표시로 변경 사항을 알립니다.
04
Workday Canvas 디자인 시스템 에이전트 설정
구현 시작점
번들로 제공되는 애플리케이션의 전체 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>);
프로젝트를 위한 프롬프트
에이전트 프롬프트
현재 Canvas FormField 구성과 토큰 CSS 레이어를 사용하세요. 강의 필터링과 선택 상태를 분리하고, 중복을 방지하며, 총 소요 시간을 계산하세요. 의미 기반 역할을 통해 테넌트 테마를 유지하세요. 과거의 숫자형 간격 이름을 현재 권장되는 소스 카테고리로 오인하지 마세요.
05
Workday Canvas 디자인 시스템 소스와 범위
독립적인 OpenDesign 참고 자료입니다. 직원 등록이나 Workday API는 관련되지 않습니다. 현재 소스 이름과 이전 마이그레이션 이름을 명확히 구분합니다.