기업용 소프트웨어 · 새 리소스

Workday Canvas 디자인 시스템.

Canvas Kit의 복합 FormField, 제어형 TextInput, PrimaryButton을 사용해 로컬 학습 계획을 구성하고, 현재 사용하는 시맨틱 토큰의 출처와 이전 space.x4 마이그레이션 예시를 구분하세요.

OpenDesign 가이드7개 챕터참조 항목 33개독자적 적용 가이드
01

Workday Canvas 디자인 시스템 인터페이스 미리보기

강의 제목을 필터링하고, 강의를 한 번만 추가하고, 선택 항목을 삭제하고, 총 소요 시간을 실시간으로 비교하세요.

학습 계획 작성 도구전체 예시 열기
Canvas 상호작용 패턴과 절제된 엔터프라이즈 계층 구조를 HTML 방식으로 해석한 오프라인 원본입니다. 팔레트와 레이아웃은 애플리케이션의 선택 사항이며, Sana 테넌트의 렌더링 결과가 아닙니다. · 구현 레퍼런스
02

Workday 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.smfont-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 구성

출처
이름 / 규칙값 / 구성용도
FormField.LabelConnected label element입력 요소와의 생성된 연결 관계입니다.
FormField.Input as={TextInput}Polymorphic input slot이전의 label prop API가 아닙니다.
FormField.HintLinked helper content예상되는 입력을 설명합니다.
orientationvertical / horizontalStart / horizontalEnd현재 정렬 값을 사용하세요.
FormField.FieldInput and hint alignment wrapper가로 레이아웃에서 유용합니다.

TextInput 구현

출처
이름 / 규칙값 / 구성용도
grow100% width when true입력 요소를 반응형으로 확장합니다.
widthnumber or string선택적 재정의입니다.
minimum width fallbackpx2rem(280)좁은 부모 레이아웃에서 확인하세요.
errorerror / caution styling유효성 검사는 애플리케이션에서 처리합니다.
transition0.2s box-shadow, 0.2s border-color실제 소스에서의 전환입니다.
font size rolesystem.legacy.fontSize.subtext.lg컴포넌트 내부 구현을 관련 없는 제목 유형으로 대체하지 마세요.

PrimaryButton 구현

출처
이름 / 규칙값 / 구성용도
Backgroundbrand.action.base, with legacy accent fallback테넌트 브랜딩입니다.
Hoverbrand.action.dark or colorSpace.hover상태는 동작 색상에서 파생됩니다.
Pressedbrand.action.darkest or colorSpace.pressed눌렀을 때 뚜렷하게 달라지는 스타일입니다.
variant="inverse"Inverse surface and foreground roles대비가 적절한 배경에서 사용하세요.
Border radiussystem.legacy.shape.full현재 소스는 이전 버전과의 호환성을 유지합니다.

마이그레이션 참고 자료이며, 현재 토큰 이름이 아닙니다

출처
이름 / 규칙값 / 구성용도
Former space.s → system.space.x41rem in 2023 migration table현재 소스에서는 안쪽 여백과 간격을 구분합니다.
Former space.m → system.space.x61.5rem in 2023 migration table이전 이름이 계속 권장된다고 가정하지 마세요.
Former borderRadius.m → system.shape.x10.25rem in 2023 migration table현재 소스에서는 의미에 따라 모양 크기를 구분합니다.
03

Workday Canvas 디자인 시스템 종합 가이드

실제 인터페이스를 위해 이 시스템을 선택하고 적용하고 검토하는 실용 가이드.

DESIGN-workday-canvas.md

완전한 안내와 구현 시작점, 소스 링크.

가이드 다운로드

Workday Canvas 디자인 시스템 마이그레이션 예시를 복사하기 전에 최신 소스를 확인하세요

간격을 용도에 따라 구분합니다

Canvas는 canvas-kit-react/tokens의 티셔츠 크기 간격 상수에서 번호가 붙은 system.space 토큰을 거쳐, padding과 gap 같은 소스 범주로 발전해 왔습니다. 현재 sys.json에서는 이러한 값의 기반을 기본 토큰 참조로 표현합니다. 이 참고 자료는 여러 단어를 이어 붙여 CSS 변수를 만들어 내는 대신 실제 경로를 그대로 보여 줍니다.

이 OpenDesign 가이드에서

이전 애플리케이션을 변환할 때는 마이그레이션 표가 여전히 유용합니다. 기존 space.s가 1rem에 해당했던 이유를 설명하기 때문입니다. 하지만 이는 과거의 근거일 뿐, 예전 내보내기 이름이 모두 현재 권장 인터페이스라는 뜻은 아닙니다. 미리보기에서는 애플리케이션에서 선택한 16px 및 24px 간격을 사용하며, 라이브러리 토큰 이름의 기준은 소스 표입니다.

지원 사양 읽기

Workday Canvas 디자인 시스템 색상은 테넌트와 컴포넌트 상태에 따라 달라집니다

기본 버튼은 고정된 파란색 견본 그 이상입니다

PrimaryButton은 먼저 brand.action.base를 참조하고, 없으면 이전 primary 강조 색상으로 대체합니다. 호버 및 눌림 스타일도 상태를 파생할 때 brand 액션 역할을 먼저 참조한 다음 colorSpace 헬퍼를 사용합니다. 따라서 배경색 하나를 하드코딩해 바꿔도 컴포넌트의 테마 규칙을 재현할 수 없습니다.

원본 학습 미리보기는 절제된 파란색 애플리케이션 팔레트를 사용하며, HTML 방식의 해석임을 표시합니다. 특정 고객 테넌트나 최신 Sana Canvas 테마의 렌더링 결과가 아닙니다. 완전한 네이티브 예시에서는 설치된 토큰 계층이 컴포넌트 색상을 제공합니다.

지원 사양 읽기

Workday Canvas 디자인 시스템 복합 스타일 역할은 행간과 글꼴 두께를 유지합니다

크기 별칭만으로 스타일 전체를 나타낼 수는 없습니다

현재 body.sm 타이포그래피 객체에는 글꼴 패밀리, normal 두께, 본문 크기, 본문 행간이 함께 정의되어 있습니다. 제목 역할에는 bold 두께가 사용됩니다. 이전 스크린샷에서 픽셀 크기만 선택하면 이러한 관계가 사라집니다. 소스에서는 이전 마이그레이션 설명에 등장하는 regular 키가 아니라 normal이라는 이름을 사용합니다.

Canvas Kit 컴포넌트 패키지에는 글꼴이 포함되지 않습니다. 글꼴 로드는 별도의 통합 결정으로 처리하세요. 오프라인 예시에서는 시스템 대체 글꼴을 사용하며 Roboto나 Sana 글꼴이 번들에 포함되어 있다고 주장하지 않습니다.

지원 사양 읽기

Workday Canvas 디자인 시스템 FormField는 복합 컴포넌트입니다

필드에서 레이블, 입력 요소, 도움말을 구성하세요

소스에서는 FormField.Label, FormField.Input, FormField.Hint를 제공합니다. TextInput을 Input의 prop으로 전달하면 모델이 접근성 연결을 생성할 수 있습니다. 이는 바깥 필드에 label prop을 설정하고 래핑하지 않은 입력 요소를 안에 넣던 이전 예시와 다릅니다.

가로형 양식에는 현재 방향 값인 horizontalStart와 horizontalEnd를 사용합니다. FormField.Field는 해당 정렬 안에서 입력 요소와 도움말을 함께 묶습니다. 필터 설명을 레이블과 컨트롤 바로 아래에 표시해야 하므로 여기서는 세로형을 사용합니다.

  • FormField.Label 안에 실제로 표시되는 레이블을 배치하세요.
  • 복합 Input 슬롯에 TextInput을 전달하세요.
  • 필터 범위 설명에는 Hint를 사용하세요.
  • 제약 조건을 추가할 때 유효성 검사 로직은 애플리케이션에 유지하세요.

지원 사양 읽기

Workday Canvas 디자인 시스템 입력 요소가 늘어나도 부모 요소는 유연해야 합니다

최소 너비를 고려하세요

TextInput의 기본 최소 너비는 280px이며, 소스에서는 px2rem으로 표현됩니다. grow는 전체 너비를 요청하는 속성이지, 고정된 2열 부모가 좁은 뷰포트에 맞도록 자동으로 조정해 주는 기능은 아닙니다. 오프라인 카탈로그는 모바일에서 계획 사이드바를 강의 카드 아래에 배치하고, 각 레이아웃 열이 줄어들 수 있도록 합니다.

검색어는 일반적인 React 상태입니다. 앱은 세 강의 제목을 대상으로 대소문자를 구분하지 않는 부분 문자열 검색을 수행하고, 결과가 없으면 이를 명시적으로 표시합니다. 표시 여부와 계획에 포함되는 여부는 별개의 상태이므로, 필터를 적용해도 선택한 강의는 해제되지 않습니다.

지원 사양 읽기

Workday Canvas 디자인 시스템 선택은 표시된 결과와 별도로 유지됩니다

안정적인 강의 ID를 사용하고 합계를 계산하세요

각 강의에는 ID, 제목, 소요 시간이 있습니다. 선택 목록에는 ID를 저장하고, 표시할 합계는 강의 데이터에서 계산합니다. 카탈로그를 필터링해도 선택한 항목은 제거되지 않습니다. 항목을 제거하면 추가 작업이 즉시 다시 활성화되고 합계도 줄어듭니다.

이것은 독립적으로 구성한 로컬 계획 시나리오입니다. Workday API를 호출하거나, 직원을 등록하거나, 학습 기록을 수정하지 않습니다. 이러한 경계를 명확히 드러내면 프로토타입이 실제 업무 트랜잭션인 것처럼 오해되는 일을 막을 수 있습니다.

  1. 카탈로그 보기만 필터링합니다.
  2. 강의 ID는 한 번만 추가합니다.
  3. 강의 ID를 바탕으로 선택한 강의와 총 소요 시간을 계산합니다.
  4. 검색어를 초기화하지 않고 선택 항목을 제거합니다.

지원 사양 읽기

Workday Canvas 디자인 시스템 컴포넌트에 테마를 적용하기 전에 토큰을 로드하세요

Provider와 CSS 레이어는 서로 다른 역할을 합니다

현재 시작하기 예제는 애플리케이션 진입점에서 base, brand, component, system 변수 스타일을 가져온 다음 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은 각각의 독립된 강의 항목에 속합니다. 전체 계획에는 오해를 부르는 등록 버튼이 없습니다.
  • 제거 작업에는 애플리케이션에서 명시적으로 정의한 네이티브 버튼을 사용합니다.
  • 추가 완료 상태에서는 중복 추가를 막도록 버튼을 비활성화하고, 실시간 개수 표시로 변경 사항을 알립니다.
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는 관련되지 않습니다. 현재 소스 이름과 이전 마이그레이션 이름을 명확히 구분합니다.