canon-ds 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +153 -0
- package/THIRD_PARTY_NOTICES.md +55 -0
- package/bin/canon.js +6 -0
- package/lib/build-manifest.mjs +54 -0
- package/lib/build.mjs +69 -0
- package/lib/cli.mjs +273 -0
- package/lib/color.js +160 -0
- package/lib/components/_shared.mjs +93 -0
- package/lib/components/accordion.mjs +136 -0
- package/lib/components/account-card.mjs +166 -0
- package/lib/components/activity-feed.mjs +176 -0
- package/lib/components/activity-gauge.mjs +98 -0
- package/lib/components/agent-presence.mjs +156 -0
- package/lib/components/alert.mjs +154 -0
- package/lib/components/app-store-button.mjs +108 -0
- package/lib/components/avatar-group.mjs +128 -0
- package/lib/components/avatar.mjs +225 -0
- package/lib/components/badge-group.mjs +55 -0
- package/lib/components/badge.mjs +174 -0
- package/lib/components/banner.mjs +116 -0
- package/lib/components/breadcrumb.mjs +170 -0
- package/lib/components/button-group.mjs +114 -0
- package/lib/components/button.mjs +197 -0
- package/lib/components/card-header.mjs +115 -0
- package/lib/components/card.mjs +106 -0
- package/lib/components/carousel.mjs +182 -0
- package/lib/components/chart-frame.mjs +101 -0
- package/lib/components/checkbox.mjs +163 -0
- package/lib/components/close-button.mjs +49 -0
- package/lib/components/code.mjs +123 -0
- package/lib/components/color-picker.mjs +122 -0
- package/lib/components/combobox.mjs +210 -0
- package/lib/components/command-palette.mjs +226 -0
- package/lib/components/content-divider.mjs +104 -0
- package/lib/components/counter.mjs +86 -0
- package/lib/components/credit-card.mjs +126 -0
- package/lib/components/date-picker.mjs +202 -0
- package/lib/components/description-list.mjs +88 -0
- package/lib/components/dialog.mjs +140 -0
- package/lib/components/divider.mjs +115 -0
- package/lib/components/drawer.mjs +150 -0
- package/lib/components/empty-state.mjs +112 -0
- package/lib/components/featured-card.mjs +124 -0
- package/lib/components/featured-icon.mjs +67 -0
- package/lib/components/field.mjs +122 -0
- package/lib/components/file-dropzone.mjs +166 -0
- package/lib/components/filter-bar.mjs +179 -0
- package/lib/components/header-navigation.mjs +94 -0
- package/lib/components/icon-button.mjs +179 -0
- package/lib/components/index.mjs +52 -0
- package/lib/components/inline-cta.mjs +139 -0
- package/lib/components/input-group.mjs +164 -0
- package/lib/components/input.mjs +197 -0
- package/lib/components/kanban.mjs +179 -0
- package/lib/components/kbd.mjs +70 -0
- package/lib/components/kicker.mjs +79 -0
- package/lib/components/link.mjs +108 -0
- package/lib/components/list.mjs +117 -0
- package/lib/components/media-frame.mjs +119 -0
- package/lib/components/menu.mjs +190 -0
- package/lib/components/message.mjs +249 -0
- package/lib/components/metric-group.mjs +210 -0
- package/lib/components/mobile-header.mjs +173 -0
- package/lib/components/multi-select.mjs +218 -0
- package/lib/components/notification.mjs +175 -0
- package/lib/components/number-input.mjs +149 -0
- package/lib/components/page-header.mjs +68 -0
- package/lib/components/pagination.mjs +236 -0
- package/lib/components/pin-input.mjs +120 -0
- package/lib/components/popover.mjs +126 -0
- package/lib/components/progress-circle.mjs +115 -0
- package/lib/components/progress.mjs +93 -0
- package/lib/components/prose.mjs +96 -0
- package/lib/components/radio.mjs +160 -0
- package/lib/components/rating.mjs +93 -0
- package/lib/components/rich-text-editor.mjs +178 -0
- package/lib/components/section-header.mjs +62 -0
- package/lib/components/segmented-control.mjs +121 -0
- package/lib/components/select.mjs +163 -0
- package/lib/components/sidebar-nav.mjs +172 -0
- package/lib/components/sidebar.mjs +124 -0
- package/lib/components/skeleton.mjs +66 -0
- package/lib/components/slider.mjs +122 -0
- package/lib/components/social-button.mjs +119 -0
- package/lib/components/spinner.mjs +69 -0
- package/lib/components/stat.mjs +120 -0
- package/lib/components/stepper.mjs +228 -0
- package/lib/components/switch.mjs +159 -0
- package/lib/components/table.mjs +212 -0
- package/lib/components/tabs.mjs +207 -0
- package/lib/components/tag.mjs +196 -0
- package/lib/components/tags-input.mjs +146 -0
- package/lib/components/textarea.mjs +117 -0
- package/lib/components/timeline.mjs +135 -0
- package/lib/components/toast.mjs +115 -0
- package/lib/components/tooltip.mjs +86 -0
- package/lib/components/topbar.mjs +207 -0
- package/lib/components/tree-view.mjs +156 -0
- package/lib/components/video-player.mjs +189 -0
- package/lib/connect.mjs +110 -0
- package/lib/design-files.mjs +175 -0
- package/lib/distribution.mjs +109 -0
- package/lib/editor.js +940 -0
- package/lib/engine.js +339 -0
- package/lib/generators/agents.mjs +68 -0
- package/lib/generators/connection.md +58 -0
- package/lib/generators/css.mjs +2 -0
- package/lib/generators/designmd.mjs +324 -0
- package/lib/generators/documentation.css +82 -0
- package/lib/generators/documentation.html +81 -0
- package/lib/generators/documentation.js +96 -0
- package/lib/generators/documentation.mjs +35 -0
- package/lib/generators/dtcg.mjs +60 -0
- package/lib/generators/preview.css +279 -0
- package/lib/generators/preview.mjs +225 -0
- package/lib/generators/react.mjs +162 -0
- package/lib/generators/tailwind.mjs +114 -0
- package/lib/install.mjs +108 -0
- package/lib/lint.mjs +538 -0
- package/lib/mcp.mjs +268 -0
- package/lib/open.mjs +7 -0
- package/lib/patterns/_app.mjs +56 -0
- package/lib/patterns/app-shell.mjs +25 -0
- package/lib/patterns/audit-log.mjs +37 -0
- package/lib/patterns/auth-pages.mjs +44 -0
- package/lib/patterns/billing-page.mjs +39 -0
- package/lib/patterns/calendar-page.mjs +55 -0
- package/lib/patterns/dashboard-page.mjs +28 -0
- package/lib/patterns/files-page.mjs +30 -0
- package/lib/patterns/first-run.mjs +31 -0
- package/lib/patterns/form-layout.mjs +24 -0
- package/lib/patterns/inbox-page.mjs +58 -0
- package/lib/patterns/index.mjs +29 -0
- package/lib/patterns/list-detail-page.mjs +24 -0
- package/lib/patterns/mobile-app.mjs +46 -0
- package/lib/patterns/modal-flows.mjs +32 -0
- package/lib/patterns/onboarding-page.mjs +42 -0
- package/lib/patterns/permissions-page.mjs +38 -0
- package/lib/patterns/profile-page.mjs +32 -0
- package/lib/patterns/settings-page.mjs +24 -0
- package/lib/project.mjs +28 -0
- package/lib/serve.mjs +279 -0
- package/lib/system.mjs +210 -0
- package/lib/tokens/base.js +251 -0
- package/lib/tokens/canon-preset.mjs +67 -0
- package/lib/tokens/index.mjs +1 -0
- package/lib/tokens/legacy-canon.json +559 -0
- package/lib/tokens/presets.mjs +161 -0
- package/lib/tokens/resolve.mjs +1 -0
- package/lib/types.mjs +4 -0
- package/lib/version.mjs +2 -0
- package/package.json +54 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { componentRules, renderRules, rePrefix } from "./css.mjs";
|
|
2
|
+
import { toCssVars, toLiteral } from "../tokens/resolve.mjs";
|
|
3
|
+
import { CATEGORY_ORDER, CATEGORY_LABEL } from "../components/index.mjs";
|
|
4
|
+
import { VERSION } from "../version.mjs";
|
|
5
|
+
const pascal = (s) => s.replace(/(^|[-_])(\w)/g, (_m, _p, c) => c.toUpperCase());
|
|
6
|
+
const code = (s) => `\`${s}\``;
|
|
7
|
+
const fence = (lang, s) => `\`\`\`${lang}\n${s.trim()}\n\`\`\``;
|
|
8
|
+
const row = (cells) => `| ${cells.map((c) => c.replace(/\|/g, '\\|').replace(/\n/g, ' ')).join(' | ')} |`;
|
|
9
|
+
const table = (head, rows) => [row(head), `|${head.map(() => '---').join('|')}|`, ...rows.map(row)].join('\n');
|
|
10
|
+
/** "var(--p-space-3) → 12px" style annotation for a declaration value. */
|
|
11
|
+
function annotate(value, idx) {
|
|
12
|
+
const css = toCssVars(value, idx);
|
|
13
|
+
if (css === value)
|
|
14
|
+
return code(value);
|
|
15
|
+
const l = toLiteral(value, idx, 'light'), d = toLiteral(value, idx, 'dark');
|
|
16
|
+
return `${code(css)} → ${code(l)}${d !== l ? ` (dark ${code(d)})` : ''}`;
|
|
17
|
+
}
|
|
18
|
+
function declTable(decls, idx) {
|
|
19
|
+
const rows = Object.entries(decls).map(([k, v]) => [code(k), annotate(v, idx)]);
|
|
20
|
+
return rows.length ? table(['property', 'value → resolved'], rows) : '_no declarations_';
|
|
21
|
+
}
|
|
22
|
+
function blockSection(title, block, spec, idx, depth) {
|
|
23
|
+
if (!block)
|
|
24
|
+
return '';
|
|
25
|
+
const out = [];
|
|
26
|
+
const parts = Object.entries(block).filter(([k, v]) => k !== '@states' && v && Object.keys(v).length);
|
|
27
|
+
const states = Object.entries(block['@states'] ?? {});
|
|
28
|
+
if (!parts.length && !states.length)
|
|
29
|
+
return '';
|
|
30
|
+
out.push(`${depth} ${title}`);
|
|
31
|
+
for (const [part, decls] of parts) {
|
|
32
|
+
out.push(`**${part}** (${code(partSelector(spec, part))})`);
|
|
33
|
+
out.push(declTable(decls, idx));
|
|
34
|
+
}
|
|
35
|
+
for (const [state, sparts] of states) {
|
|
36
|
+
for (const [part, decls] of Object.entries(sparts)) {
|
|
37
|
+
if (!Object.keys(decls).length)
|
|
38
|
+
continue;
|
|
39
|
+
out.push(`**${part}** on **${state}** (${code(spec.states[state]?.selector ?? state)})`);
|
|
40
|
+
out.push(declTable(decls, idx));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return out.join('\n\n');
|
|
44
|
+
}
|
|
45
|
+
let PREFIX = 'cn';
|
|
46
|
+
const partSelector = (spec, part) => (part === 'root' ? `.${PREFIX}-${spec.slug}` : `.${PREFIX}-${spec.slug}__${part}`);
|
|
47
|
+
function componentDoc(spec, idx, n) {
|
|
48
|
+
const p = PREFIX;
|
|
49
|
+
const Name = pascal(spec.slug);
|
|
50
|
+
const out = [];
|
|
51
|
+
out.push(`### ${n} ${spec.name} — ${code(`.${p}-${spec.slug}`)}`);
|
|
52
|
+
out.push(`${spec.description}`);
|
|
53
|
+
out.push(`**When to use:** ${spec.usage}`);
|
|
54
|
+
out.push(`**Category:** ${CATEGORY_LABEL[spec.category]} · **Related:** ${spec.related.map((r) => `[${pascal(r)}](#${r})`).join(', ') || '—'}`);
|
|
55
|
+
// anatomy
|
|
56
|
+
out.push(`#### Anatomy`);
|
|
57
|
+
out.push(table(['part', 'selector', 'element', 'role'], spec.anatomy.map((a) => [a.part + (a.optional ? ' _(optional)_' : ''), code(partSelector(spec, a.part)), code(`<${a.element}>`), a.description])));
|
|
58
|
+
// props
|
|
59
|
+
const props = Object.entries(spec.props);
|
|
60
|
+
if (props.length) {
|
|
61
|
+
out.push(`#### Props (data attributes)`);
|
|
62
|
+
out.push(table(['prop', 'attribute', 'values', 'default', 'meaning'], props.map(([k, v]) => [code(k), code(`data-${k}`), v.values.map(code).join(' '), code(v.default), v.description])));
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
out.push(`#### Props\nNone. One shape only.`);
|
|
66
|
+
// states
|
|
67
|
+
const states = Object.entries(spec.states);
|
|
68
|
+
if (states.length) {
|
|
69
|
+
out.push(`#### States`);
|
|
70
|
+
out.push(table(['state', 'how to trigger', 'selector', 'behavior'], states.map(([k, v]) => [code(k), v.markup ?? '—', code(v.selector), v.description])));
|
|
71
|
+
}
|
|
72
|
+
// markup
|
|
73
|
+
out.push(`#### Markup examples`);
|
|
74
|
+
for (const ex of spec.examples) {
|
|
75
|
+
out.push(`**${ex.title}**${ex.description ? ` — ${ex.description}` : ''}`);
|
|
76
|
+
out.push(fence('html', rePrefix(ex.html, p)));
|
|
77
|
+
}
|
|
78
|
+
if (spec.recipes?.length) {
|
|
79
|
+
out.push(`#### Recipes`);
|
|
80
|
+
for (const ex of spec.recipes) {
|
|
81
|
+
out.push(`**${ex.title}**${ex.description ? ` — ${ex.description}` : ''}`);
|
|
82
|
+
out.push(fence('html', rePrefix(ex.html, p)));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// react
|
|
86
|
+
const propsUsage = props.map(([k, v]) => `${k}="${v.default}"`).join(' ');
|
|
87
|
+
out.push(`#### React`);
|
|
88
|
+
out.push(fence('tsx', `import { ${Name}${spec.anatomy.filter((a) => a.part !== 'root').slice(0, 3).map((a) => `, ${Name}${pascal(a.part)}`).join('')} } from '@/ui';\n\n<${Name} ${propsUsage}>…</${Name}>`));
|
|
89
|
+
// specs
|
|
90
|
+
out.push(`#### Specification (every value)`);
|
|
91
|
+
out.push(blockSection('Base', spec.base, spec, idx, '#####'));
|
|
92
|
+
for (const [axis, values] of Object.entries(spec.variants ?? {})) {
|
|
93
|
+
for (const [value, block] of Object.entries(values)) {
|
|
94
|
+
const s = blockSection(`${axis} = ${value}`, block, spec, idx, '#####');
|
|
95
|
+
if (s)
|
|
96
|
+
out.push(s);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const c of spec.compound ?? []) {
|
|
100
|
+
const s = blockSection(`when ${Object.entries(c.when).map(([a, v]) => `${a}=${v}`).join(' + ')}`, c.block, spec, idx, '#####');
|
|
101
|
+
if (s)
|
|
102
|
+
out.push(s);
|
|
103
|
+
}
|
|
104
|
+
// css
|
|
105
|
+
out.push(`#### Generated CSS (verbatim, annotated)`);
|
|
106
|
+
const css = renderRules(componentRules(spec, p), idx, true, p) + (spec.extraCss ? '\n' + toCssVars(rePrefix(spec.extraCss.trim(), p), idx) : '');
|
|
107
|
+
out.push(fence('css', css));
|
|
108
|
+
// rules
|
|
109
|
+
out.push(`#### Rules`);
|
|
110
|
+
out.push(spec.rules.map((r) => `- ${r}`).join('\n'));
|
|
111
|
+
out.push(`#### Accessibility`);
|
|
112
|
+
out.push(spec.a11y.map((r) => `- ${r}`).join('\n'));
|
|
113
|
+
return out.filter(Boolean).join('\n\n');
|
|
114
|
+
}
|
|
115
|
+
function foundations(system, idx) {
|
|
116
|
+
const p = PREFIX;
|
|
117
|
+
const t = system.tokens;
|
|
118
|
+
const out = [];
|
|
119
|
+
out.push(`## 2. Foundations (tokens)`);
|
|
120
|
+
out.push(`Every value below exists as a CSS custom property (${code(`--${p}-…`)}) and as a Tailwind utility (via ${code('tailwind.theme.css')}). Use the **semantic** tokens in UI code. Primitives are for defining semantics, charts and illustrations only.`);
|
|
121
|
+
// colors
|
|
122
|
+
out.push(`### 2.1 Color`);
|
|
123
|
+
out.push(`#### Primitive scales`);
|
|
124
|
+
for (const [name, val] of Object.entries(t.color.primitive)) {
|
|
125
|
+
if (typeof val === 'string') {
|
|
126
|
+
out.push(`- ${code(name)} ${code(`--${p}-${name}`)} = ${code(val)}`);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
out.push(`**${name}** — ${code(`--${p}-${name}-{step}`)}`);
|
|
130
|
+
out.push(table(['step', ...Object.keys(val)], [['hex', ...Object.values(val).map(code)]]));
|
|
131
|
+
}
|
|
132
|
+
out.push(`#### Semantic colors (use these)`);
|
|
133
|
+
out.push(table(['token', 'CSS variable', 'light', 'dark', 'Tailwind', 'use for'], Object.entries(t.color.semantic).map(([k, v]) => {
|
|
134
|
+
const r = idx.get(`color.${k}`);
|
|
135
|
+
const tw = k.startsWith('bg-') ? `bg-${k}` : k.startsWith('fg-') ? `text-${k}` : k.startsWith('border-') ? `border-${k}` : `ring-${k}`;
|
|
136
|
+
return [code(k), code(r.cssVar), code(r.light), code(r.dark), code(tw), v.description ?? ''];
|
|
137
|
+
})));
|
|
138
|
+
out.push(`Rules: text uses ${code('fg-*')}, fills use ${code('bg-*')}, lines use ${code('border-*')}. Never pick a primitive for UI (${code('neutral-500')}) when a semantic token exists (${code('fg-subtle')}). Never write a hex.`);
|
|
139
|
+
// typography
|
|
140
|
+
out.push(`### 2.2 Typography`);
|
|
141
|
+
out.push(table(['family', 'CSS variable', 'stack'], Object.entries(t.font.family).map(([k, v]) => [code(k), code(`--${p}-font-family-${k}`), code(v)])));
|
|
142
|
+
out.push(`**Font sizes** ${code(`--${p}-font-size-{name}`)}: ` + Object.entries(t.font.size).map(([k, v]) => `${code(k)} ${v}`).join(' · '));
|
|
143
|
+
out.push(`**Weights**: ` + Object.entries(t.font.weight).map(([k, v]) => `${code(k)} ${v}`).join(' · ') + ` \n**Line heights**: ` + Object.entries(t.font.lineHeight).map(([k, v]) => `${code(k)} ${v}`).join(' · ') + ` \n**Letter spacing**: ` + Object.entries(t.font.letterSpacing).map(([k, v]) => `${code(k)} ${v}`).join(' · ') + ` \n**Control text** (buttons/inputs by size): ` + Object.entries(t.font.control).map(([k, v]) => `${code(k)} ${v}`).join(' · '));
|
|
144
|
+
out.push(`#### Composite text styles — use the class or the five properties together, never a size alone`);
|
|
145
|
+
out.push(table(['style', 'class', 'size / line-height', 'weight', 'tracking', 'family', 'transform', 'use for'], Object.entries(t.type).map(([k, ts]) => [code(k), code(`.${p}-text-${k}`), `${idx.get(`type.${k}.size`).light} / ${idx.get(`type.${k}.lineHeight`).light}`, idx.get(`type.${k}.weight`).light, idx.get(`type.${k}.letterSpacing`).light, idx.get(`type.${k}.family`).light.split(',')[0].replace(/"/g, ''), ts.transform ?? '—', ts.description ?? ''])));
|
|
146
|
+
out.push(`Heading levels map: h1 = heading-xl (page title), h2 = heading-lg, h3 = heading-md (card/dialog titles), h4 = heading-sm, h5 = heading-xs. Marketing heroes use display-*. Body copy is body-md in the product and body-lg on marketing pages. Helper/meta text is body-sm in fg-muted.`);
|
|
147
|
+
// spacing
|
|
148
|
+
out.push(`### 2.3 Spacing`);
|
|
149
|
+
out.push(`4px grid. ${code(`--${p}-space-{n}`)}: ` + Object.entries(t.space).map(([k, v]) => `${code(k)}=${v}`).join(' · '));
|
|
150
|
+
out.push(`Rhythm: inside a control 8–12px; between related items 8px (space.2); between fields 16px (space.4); between groups 24px (space.6); between sections 32px (space.8); page padding 24px (space.6) desktop / 16px (space.4) mobile; between page header and body 32px; hero sections 64–96px vertical (space.16–24).`);
|
|
151
|
+
// radius
|
|
152
|
+
out.push(`### 2.4 Radius`);
|
|
153
|
+
out.push(table(['token', 'value', 'use'], Object.entries(t.radius).map(([k, v]) => [code(`radius.${k}`), v, { control: 'buttons, inputs, selects, menu items', card: 'cards, table wrappers, list wrappers', panel: 'menus, popovers, tooltips', overlay: 'dialogs, drawers, command palette', full: 'pills, avatars, dots, switches', none: 'flush edges (banner, topbar)' }[k] ?? '—'])));
|
|
154
|
+
// shadows
|
|
155
|
+
out.push(`### 2.5 Shadows`);
|
|
156
|
+
out.push(table(['token', 'light', 'dark', 'use'], Object.entries(t.shadow).map(([k, v]) => { const r = idx.get(`shadow.${k}`); return [code(`shadow.${k}`), code(r.light), code(r.dark), v.description ?? '']; })));
|
|
157
|
+
out.push(`### 2.6 Borders`);
|
|
158
|
+
out.push(Object.entries(t.border.width).map(([k, v]) => `${code(`border.width.${k}`)} ${v}`).join(' · ') + `. Default border is ${code('1px solid {color.border-default}')}; controls use ${code('border-control')}; focused controls use ${code('border-action')}; invalid use ${code('border-danger')}.`);
|
|
159
|
+
out.push(`### 2.7 Sizes`);
|
|
160
|
+
out.push(`**Control heights** ` + Object.entries(t.size.control).map(([k, v]) => `${code(k)} ${v}`).join(' · ') + ` \n**Control horizontal padding** ` + Object.entries(t.size.controlPadding).map(([k, v]) => `${code(k)} ${v}`).join(' · ') + ` \n**Control icon size** ` + Object.entries(t.size.controlIcon).map(([k, v]) => `${code(k)} ${v}`).join(' · ') + ` \n**Icon sizes** ` + Object.entries(t.size.icon).map(([k, v]) => `${code(k)} ${v}`).join(' · ') + ` \n**Containers** ` + Object.entries(t.size.container).map(([k, v]) => `${code(k)} ${v}`).join(' · '));
|
|
161
|
+
out.push(`### 2.8 Z-index`);
|
|
162
|
+
out.push(Object.entries(t.z).map(([k, v]) => `${code(`z.${k}`)} ${v}`).join(' · '));
|
|
163
|
+
out.push(`### 2.9 Motion`);
|
|
164
|
+
out.push(`Durations: ` + Object.entries(t.motion.duration).map(([k, v]) => `${code(k)} ${v}`).join(' · ') + ` \nEasings: ` + Object.entries(t.motion.easing).map(([k, v]) => `${code(k)} ${v}`).join(' · ') + ` \nOnly color/opacity/transform/box-shadow transition. Enter animations ≤ 200ms, exit ≤ 150ms. Always honored: ${code('prefers-reduced-motion')} (base.css neutralizes animations).`);
|
|
165
|
+
out.push(`### 2.10 Breakpoints`);
|
|
166
|
+
out.push(Object.entries(t.breakpoint).map(([k, v]) => `${code(k)} ${v}`).join(' · ') + `. Mobile first. Layout collapses: multi-column → single column below ${code('md')}; page padding shrinks to 16px; topbar tabs become a menu; drawers become bottom sheets.`);
|
|
167
|
+
out.push(`### 2.11 Opacity`);
|
|
168
|
+
out.push(Object.entries(t.opacity).map(([k, v]) => `${code(`opacity.${k}`)} ${v}`).join(' · '));
|
|
169
|
+
out.push(`### 2.12 Layout primitives (in ${code(`${p}.css`)})`);
|
|
170
|
+
out.push(table(['class', 'attributes', 'what it does'], [
|
|
171
|
+
[code(`.${p}-section`), code('data-padding="none|sm|md"') + ' ' + code('data-bg="subtle|surface|inverse|brand|brand-subtle"'), 'Full-width band. Default vertical padding 96px (64 on mobile).'],
|
|
172
|
+
[code(`.${p}-container`), code('data-size="sm|md|lg|2xl"'), 'Centered max-width column (default 1280px) with 32px side padding (16 on mobile).'],
|
|
173
|
+
[code(`.${p}-stack`), code('data-gap="0…16"') + ' ' + code('data-align'), 'Vertical flex column. Default gap 16px.'],
|
|
174
|
+
[code(`.${p}-row`), code('data-gap data-align data-justify="between|center|end" data-wrap data-stack'), 'Horizontal flex row. data-stack collapses to a column on mobile.'],
|
|
175
|
+
[code(`.${p}-grid`), code('data-cols="1…6" data-gap data-keep'), 'Equal-column grid. Collapses to 2 cols below lg (for 4–6) and 1 col below md; data-keep keeps 2.'],
|
|
176
|
+
[code(`.${p}-split`), code('data-ratio="1:2|2:1|1:3" data-align="start"'), 'Two-column media/text layout; stacks on mobile.'],
|
|
177
|
+
[code(`.${p}-center`) + ' / ' + code(`.${p}-measure`) + ' / ' + code(`.${p}-mx-auto`) + ' / ' + code(`.${p}-flex-1`), code('data-size="sm|md|lg"') + ' on measure', 'Text centering, reading width (65ch / 480 / 640 / 768), centering, flex fill.'],
|
|
178
|
+
[code(`.${p}-hide-mobile`) + ' / ' + code(`.${p}-show-mobile`), '', 'Responsive visibility below md.'],
|
|
179
|
+
]));
|
|
180
|
+
out.push(`Use these for page and section structure. Any other layout CSS must use tokens.`);
|
|
181
|
+
out.push(`### 2.13 Icons`);
|
|
182
|
+
out.push(`${system.meta.icons.set}, stroke ${system.meta.icons.strokeWidth}. ${system.meta.icons.note} Inline SVG with ${code('aria-hidden="true"')} when decorative; sized with the ${code('size.icon.*')} / ${code('size.controlIcon.*')} tokens; color ${code('currentColor')}.`);
|
|
183
|
+
return out.join('\n\n');
|
|
184
|
+
}
|
|
185
|
+
export function designMd(system, idx) {
|
|
186
|
+
PREFIX = system.meta.prefix;
|
|
187
|
+
const p = PREFIX;
|
|
188
|
+
const dist = `design/${system.meta.out || 'dist'}`;
|
|
189
|
+
const out = [];
|
|
190
|
+
const byCat = CATEGORY_ORDER.map((cat) => ({ cat, items: system.components.filter((c) => c.category === cat) })).filter((g) => g.items.length);
|
|
191
|
+
out.push(`# ${system.meta.name} — Design System Specification for AI agents`);
|
|
192
|
+
out.push(`> Generated by canon ${VERSION} on ${new Date().toISOString().slice(0, 10)} from ${code('design/')} · ${system.components.length} components · ${system.patterns.length} patterns · ${idx.size} tokens · class prefix ${code(`${p}-`)}. \n> This is the visual contract and reference catalog for UI work. Start with ${code('DESIGN.compact.md')} and read only relevant sections here. Edit design source to change the system; ${code('canon sync')} rebuilds outputs and refreshes installed documents.`);
|
|
193
|
+
out.push(`## 0. How to use this document`);
|
|
194
|
+
out.push(`### Connecting to an existing project\nPreserve the existing stack, routes, behavior, data and design definitions. Reuse an existing Canon setup and install its references before UI work. Adopt Canon incrementally within the requested scope. A connection-only request ends with verified integration and readiness; do not recreate the app, migrate all screens or invent a product task. Continue an active task using these instructions without requiring the user to repeat them.`);
|
|
195
|
+
out.push(`### Implementation rules
|
|
196
|
+
1. **Use semantic tokens in UI.** Colors, type, spacing, radii and shadows use ${code(`var(--${p}-…)`)} or generated Tailwind utilities. In design specs use ${code('{token}')} references; define new values in ${code('design/tokens.json')} when the system needs them.
|
|
197
|
+
2. **Reuse stable components.** Start with shared application UI and this catalog. Preserve semantic elements, required anatomy, root/part classes (${code(`.${p}-<component>`)}, ${code(`.${p}-<component>__<part>`)}) and allowed ${code('data-*')} values. Adapt examples to content, framework bindings, events and routing; optional parts remain optional.
|
|
198
|
+
3. **Extend through source.** Compose screens in application files. If a reusable visual component or variant is missing, extend a spec or add ${code('design/components/<slug>.json')} with anatomy, props, states, token styles, examples and accessibility rules. Add reusable page patterns in ${code('design/patterns/')}. Regenerate instead of scattering overrides or patching generated files.
|
|
199
|
+
4. **Wire real behavior.** States use the documented native or data attributes. React wrappers map props to styled markup; the application implements interactions, keyboard behavior and focus management. Cover relevant loading, error, empty, success and disabled states.
|
|
200
|
+
5. **Use complete typography and spacing.** Use composite text styles (${code(`.${p}-text-body-md`)} etc.) and the spacing scale (§2.3). Group related content and keep at most one primary action per region.
|
|
201
|
+
6. **Respect themes and access.** Semantic tokens switch via ${code('data-theme')} or the OS. Verify supported themes, readable contrast, semantic headings, labels, accessible names, visible focus, keyboard operation and ${code('prefers-reduced-motion')}.
|
|
202
|
+
7. **Design for the task.** Choose hierarchy, density and composition from the user's goal, content and visual references. Use supplied facts and clearly identified fixtures; never present invented testimonials or metrics as evidence.`);
|
|
203
|
+
out.push(`### Workflow for every UI task
|
|
204
|
+
1. Inspect repository instructions, framework, routes, shared UI, data flow and checks. The user sets product intent; own routine technical decisions, dependency installation with the project's package manager, configuration and CSS/wrapper integration. Follow the user's scope: a prototype can use a small working flow and separate fixtures; production features need useful domain/data/presentation boundaries and requested persistence, validation and failure handling. Add complexity only when needed.
|
|
205
|
+
2. Re-read the current ${code('DESIGN.compact.md')} at the beginning of UI work. With Canon MCP, call ${code('design_rules')} once and retrieve only relevant ${code('get_component')}, ${code('get_pattern')} and token groups. Without MCP, read matching source JSON or sections here. MCP supplies context and lint; execute setup, commands and preview processes through terminal tools. Choose variants by their stated meaning.
|
|
206
|
+
3. Compose a working flow using the documented structure and token-based layout CSS. Keep product logic in application files and reusable design changes in ${code('design/')}. Save in the project's bound Studio updates the design, generated styles and installed references together. For source edits outside Studio, run ${code('canon sync')}. MCP refreshes automatically on its next data request after a build. ${code('canon build')} only regenerates outputs.
|
|
207
|
+
4. Preserve the framework's rendering model. Keep client code local to interactions; avoid gratuitous dependencies, all-icons imports, repeated CSS imports and oversized unoptimized media. Measure significant pages with bundle, load and interaction tooling before claiming performance gains.
|
|
208
|
+
5. Run ${code('canon lint <changed files>')}, ${code('canon check')} and applicable repository type checks/tests. Inspect changed screens in a browser at narrow and wide widths, exercising keyboard and relevant states. For runnable UI work, start or reuse the application's dev server, verify the affected route and leave the preview running. Use the project's dev command; ${code('canon serve')} is the catalog gallery. Return the preview URL, checks run and remaining limitations.`);
|
|
209
|
+
out.push(`### Self-check before finishing
|
|
210
|
+
- [ ] The requested flow works; fixtures and production data are distinguishable.
|
|
211
|
+
- [ ] Shared components preserve their contract; any new reusable visual behavior is specified in design source.
|
|
212
|
+
- [ ] Hierarchy, copy, density and actions fit the task at supported viewport sizes and themes.
|
|
213
|
+
- [ ] Relevant states, labels, keyboard interaction and focus behavior have been exercised.
|
|
214
|
+
- [ ] Token/class/prop lint and generated freshness checks passed, or existing failures are reported accurately.
|
|
215
|
+
- [ ] Browser review, accessibility and performance conclusions have their own evidence; Canon lint does not measure them.`);
|
|
216
|
+
out.push(`### Where things live
|
|
217
|
+
- Source of truth: ${code('design/system.json')}, ${code('design/tokens.json')}, ${code('design/components/*.json')}, ${code('design/patterns/*.json')}.
|
|
218
|
+
- Compiled CSS: import ${code(`${dist}/${p}.css`)} once for a quick prototype. For a smaller product bundle, explicitly select ${code(`${dist}/tokens.css`)} + ${code(`${dist}/base.css`)} + used ${code(`${dist}/css/components/<slug>.css`)} and ${code(`${dist}/css/patterns/<slug>.css`)}. Include referenced component/pattern dependencies. Use one strategy to avoid duplicate CSS; selection is explicit.
|
|
219
|
+
- Tailwind: ${code(`${dist}/tailwind.theme.css`)} (v4) or ${code(`${dist}/tailwind.preset.cjs`)} (v3).
|
|
220
|
+
- React: ${code(`${dist}/react/`)} — thin typed wrappers; props → data attributes. Use the repository's import path; ${code('@/ui')} in examples is illustrative.
|
|
221
|
+
- Gallery (see everything rendered): ${code(`${dist}/preview.html`)} (or ${code('canon serve')}).
|
|
222
|
+
- Machine access: ${code('canon mcp')} exposes rules, individual specs, tokens and snippet linting. ${code('canon add <slug>')} restores an existing catalog component; it does not author a new spec.
|
|
223
|
+
- Paths assume ${code('design/')}; use the project's configured ${code('--design')} and installation ${code('--root')} when different.`);
|
|
224
|
+
// 1 direction
|
|
225
|
+
out.push(`## 1. Art direction`);
|
|
226
|
+
out.push(system.meta.direction.summary);
|
|
227
|
+
out.push(`### Principles\n${system.meta.direction.principles.map((x) => `- ${x}`).join('\n')}`);
|
|
228
|
+
out.push(`### Never (the slop list)\n${system.meta.direction.never.map((x) => `- ${x}`).join('\n')}`);
|
|
229
|
+
// 2 foundations
|
|
230
|
+
out.push(foundations(system, idx));
|
|
231
|
+
// 3 components
|
|
232
|
+
out.push(`## 3. Components`);
|
|
233
|
+
out.push(`### Index`);
|
|
234
|
+
out.push(table(['component', 'class', 'category', 'props', 'use for'], system.components.map((c) => [`[${c.name}](#${c.slug})`, code(`.${p}-${c.slug}`), CATEGORY_LABEL[c.category], Object.entries(c.props).map(([k, v]) => `${k}: ${v.values.join('/')}`).join('; ') || '—', c.usage.split(/(?<=\.)\s/)[0]])));
|
|
235
|
+
let i = 0;
|
|
236
|
+
for (const g of byCat) {
|
|
237
|
+
out.push(`## 3.${CATEGORY_ORDER.indexOf(g.cat) + 1} ${CATEGORY_LABEL[g.cat]}`);
|
|
238
|
+
for (const c of g.items) {
|
|
239
|
+
i++;
|
|
240
|
+
out.push(`<a id="${c.slug}"></a>`);
|
|
241
|
+
out.push(componentDoc(c, idx, `3.${CATEGORY_ORDER.indexOf(g.cat) + 1}.${i}`));
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// 4 patterns
|
|
245
|
+
out.push(`## 4. Patterns — application layouts and pages`);
|
|
246
|
+
out.push(`Patterns provide starting structures composed from the components above, with a primary layout and, where listed, alternatives. Choose and adapt them to the task, content and navigation. Wire application behavior and verify the composed screen at its supported viewport sizes.`);
|
|
247
|
+
const pcats = { 'app-layout': 'Application layouts', 'app-section': 'Application sections', 'app-page': 'Application pages', 'marketing-section': 'Marketing sections', 'marketing-page': 'Marketing pages', 'shared-page': 'Shared pages (auth, errors)', email: 'Email' };
|
|
248
|
+
for (const [cat, label] of Object.entries(pcats)) {
|
|
249
|
+
const items = system.patterns.filter((pt) => pt.category === cat);
|
|
250
|
+
if (!items.length)
|
|
251
|
+
continue;
|
|
252
|
+
out.push(`### ${label}`);
|
|
253
|
+
for (const pt of items) {
|
|
254
|
+
out.push(`<a id="pattern-${pt.slug}"></a>\n#### ${pt.name}`);
|
|
255
|
+
out.push(pt.description);
|
|
256
|
+
out.push(pt.rules.map((r) => `- ${r}`).join('\n'));
|
|
257
|
+
out.push(`**Primary layout**\n\n${fence('html', rePrefix(pt.html, p))}`);
|
|
258
|
+
for (const v of pt.variants ?? [])
|
|
259
|
+
out.push(`**${v.title}**${v.description ? ` — ${v.description}` : ''}\n\n${fence('html', rePrefix(v.html, p))}`);
|
|
260
|
+
if (pt.css)
|
|
261
|
+
out.push(`**Pattern CSS** (already in ${code(`${p}.css`)})\n\n${fence('css', toCssVars(rePrefix(pt.css.trim(), p), idx))}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// 5 anti patterns
|
|
265
|
+
out.push(`## 5. Anti-patterns — what "AI slop" looks like and what to do instead`);
|
|
266
|
+
out.push(table(['never', 'instead'], [
|
|
267
|
+
['A grid of identical cards with an icon in a colored circle and a title', 'A list or table for collections; a card only for one self-contained thing; icons plain, 20px, fg-muted'],
|
|
268
|
+
['Decorative gradients, glass blur or glow unrelated to the content', 'Semantic surfaces with clear hierarchy and purposeful emphasis'],
|
|
269
|
+
['The same centered headline and three feature cards for every product', 'Choose composition from the task, supplied content and visual references'],
|
|
270
|
+
['Buttons with different radii or heights next to each other', 'Same size and radius.control for every control in a row'],
|
|
271
|
+
['Shadow on every card', 'shadow only on floating layers; resting cards use border-default'],
|
|
272
|
+
['Colored text for emphasis, colored headings', 'Weight and size; color only for status/action'],
|
|
273
|
+
['Emoji icons, mixed icon sets', 'One icon set, one stroke width'],
|
|
274
|
+
['Invented metrics, testimonials or sample records presented as fact', 'Supplied product facts or clearly identified, realistic fixtures'],
|
|
275
|
+
['Navigation that simply mirrors database tables', 'Navigation organized around user tasks with deliberate grouping'],
|
|
276
|
+
['Automatically choosing a detail page or drawer for every entity', 'Choose from task complexity, navigation context and deep-link needs'],
|
|
277
|
+
['`outline: none` without a replacement', 'The focus ring token on :focus-visible'],
|
|
278
|
+
['Ad-hoc `style=""` with px values', 'Layout utilities or pattern CSS with tokens'],
|
|
279
|
+
]));
|
|
280
|
+
// 6 token index
|
|
281
|
+
out.push(`## 6. Token index (every CSS variable)`);
|
|
282
|
+
out.push(table(['variable', 'light', 'dark'], [...idx.values()].map((t) => [code(t.cssVar), code(t.light), t.themed ? code(t.dark) : '='])));
|
|
283
|
+
// 7 lint
|
|
284
|
+
out.push(`## 7. Linting`);
|
|
285
|
+
out.push(`${code('canon lint [paths]')} scans supported source files for raw design values, Tailwind palette/arbitrary values, unknown ${code(`${p}-`)} classes and unsupported component ${code('data-*')} props. Where possible, violations suggest tokens. ${code('canon check')} runs configured lint and verifies generated file hashes, source freshness and complete build coverage. These checks do not prove visual quality, accessibility, application correctness or runtime performance; use relevant repository tests, browser review and measurements for those.`);
|
|
286
|
+
return out.join('\n\n') + '\n';
|
|
287
|
+
}
|
|
288
|
+
export function compactMd(system, idx) {
|
|
289
|
+
PREFIX = system.meta.prefix;
|
|
290
|
+
const p = PREFIX;
|
|
291
|
+
const dist = `design/${system.meta.out || 'dist'}`;
|
|
292
|
+
const out = [];
|
|
293
|
+
out.push(`# ${system.meta.name} — design rules and index`);
|
|
294
|
+
out.push(`Re-read this current file at the beginning of UI work. Use semantic tokens for colors, type, spacing, radius and shadows, and documented classes and ${code('data-*')} props. Reuse stable components and compose the screen for the user's task.`);
|
|
295
|
+
out.push(`## Connect within the existing project\nPreserve its stack, routes, behavior, data and design definitions. Reuse the existing Canon setup and install its references. Adopt Canon incrementally in the areas requested. For a connection-only request, verify integration and report readiness; do not recreate the app, migrate all screens or invent a product task. Continue an active task using these instructions without asking the user to repeat them.`);
|
|
296
|
+
out.push(`## Own setup; match the scope\nInspect repository instructions, framework, routes, shared UI, data flow and checks. The user sets product intent; own routine technical decisions, install needed dependencies with the project's package manager, configure integration and wire CSS/wrappers into the app. A quick prototype needs a small working flow and separate, clearly identified fixtures; production features need useful domain/data/presentation boundaries and requested persistence, validation and failure handling. Follow existing conventions and add complexity only when needed.`);
|
|
297
|
+
out.push(`## Retrieve only what you need\nWith Canon MCP, call ${code('design_rules')} once, then ${code('get_component')} or ${code('get_pattern')} with a ${code('slug')} from the indexes below. ${code('list_tokens({"group":"space"})')} and ${code('get_token({"name":"color.bg-action"})')} provide exact values. Without MCP, read ${code('design/components/<slug>.json')}, ${code('design/patterns/<slug>.json')} or the linked section of DESIGN.md. Retrieve a working set of specs; the full catalog is a reference. MCP supplies context and lint; execute setup, commands and preview processes through terminal tools.`);
|
|
298
|
+
out.push(`## Build the product flow
|
|
299
|
+
- Preserve semantic elements, required anatomy and allowed props while adapting content, framework bindings, routes and events. React wrappers provide styled markup and typed attributes; application code supplies behavior.
|
|
300
|
+
- Choose hierarchy, density and grouping from the task, supplied content and visual references. Keep at most one primary action per region. Clearly identify fixtures; never invent testimonials or metrics as evidence.
|
|
301
|
+
- Implement relevant loading, error, empty, success and disabled states. Provide labels, accessible names, visible focus, keyboard operation and focus management; respect reduced motion and supported themes.
|
|
302
|
+
- Preserve the framework's rendering model and keep client code local to interactions. Avoid gratuitous dependencies, all-icons imports and oversized unoptimized media. Measure significant pages with bundle, load and interaction tooling before claiming performance gains.`);
|
|
303
|
+
out.push(`## CSS integration\nA prototype can import ${code(`${dist}/${p}.css`)} once. For a smaller product bundle, explicitly select ${code(`${dist}/tokens.css`)} + ${code(`${dist}/base.css`)} + used ${code(`${dist}/css/components/<slug>.css`)} and ${code(`${dist}/css/patterns/<slug>.css`)}. Include every component/pattern dependency referenced by that UI. Use one strategy to avoid duplicate CSS; selection is explicit.`);
|
|
304
|
+
out.push(`## Direction\n${system.meta.direction.summary}\n${system.meta.direction.principles.map((x) => `- ${x}`).join('\n')}\n\nNever: ${system.meta.direction.never.join(' · ')}`);
|
|
305
|
+
out.push(`## Common semantic colors (variable → light / dark)`);
|
|
306
|
+
out.push(['bg-canvas', 'bg-surface', 'bg-subtle', 'bg-action', 'bg-action-hover', 'fg-default', 'fg-muted', 'fg-on-action', 'fg-danger', 'fg-success', 'border-default', 'ring-focus'].flatMap((k) => { const r = idx.get(`color.${k}`); return r ? [`- ${code(r.cssVar)} ${r.light} / ${r.dark}`] : []; }).join('\n'));
|
|
307
|
+
out.push(`## Text styles`);
|
|
308
|
+
out.push(`Use ${code(`.${p}-text-<name>`)} to apply a complete style. Available: ${Object.keys(system.tokens.type).map(code).join(', ')}. Retrieve the type token group for sizes, weights and leading.`);
|
|
309
|
+
out.push(`## Scales\nspace ${Object.entries(system.tokens.space).map(([k, v]) => `${k}=${v}`).join(' ')}\nradius ${Object.entries(system.tokens.radius).map(([k, v]) => `${k}=${v}`).join(' ')}\nshadow ${Object.keys(system.tokens.shadow).join(' ')}\ncontrol heights ${Object.entries(system.tokens.size.control).map(([k, v]) => `${k}=${v}`).join(' ')}`);
|
|
310
|
+
out.push(`## Component index\nRoot class: ${code(`.${p}-<slug>`)}. Part class: ${code(`.${p}-<slug>__<part>`)}. Retrieve the spec before choosing parts, variants or states.`);
|
|
311
|
+
for (const category of CATEGORY_ORDER) {
|
|
312
|
+
const items = system.components.filter((c) => c.category === category);
|
|
313
|
+
if (items.length)
|
|
314
|
+
out.push(`- **${CATEGORY_LABEL[category]}:** ${items.map((c) => `[${c.slug}](DESIGN.md#${c.slug})`).join(', ')}`);
|
|
315
|
+
}
|
|
316
|
+
out.push(`## Pattern index\n${system.patterns.map((pt) => `- [${pt.slug}](DESIGN.md#pattern-${pt.slug}) — ${pt.name} (${pt.category})`).join('\n')}`);
|
|
317
|
+
out.push(`## Change the right source\nProduct routes, domain logic, data access and screen composition belong in application files. Shared tokens and reusable visual behavior belong in ${code('design/')}: extend a spec or add ${code('design/components/<slug>.json')} (and a pattern when useful), then regenerate instead of scattering overrides. ${code('canon add <slug>')} restores an existing catalog component. Save in the project's bound Studio updates the design, generated styles and installed references together. For source edits outside Studio, run ${code('canon sync')}. MCP refreshes automatically on its next data request after a build. ${code('canon build')} only regenerates outputs. Never patch generated files. Paths assume ${code('design/')}; use configured ${code('--design')} and installation ${code('--root')} when different.`);
|
|
318
|
+
out.push(`## Verify and deliver\nRun ${code('canon lint <changed files>')}, ${code('canon check')} and applicable repository type checks/tests; fix violations introduced by the change. Inspect changed screens in a browser at narrow and wide widths, exercising keyboard and relevant states. Canon lint checks token/class/prop usage; check also verifies generated output freshness. Neither measures visual quality, accessibility or performance. For runnable UI work, start or reuse the application's dev server, verify the affected route and leave the preview running. Use the project's dev command; ${code('canon serve')} is the catalog gallery. Return the preview URL, checks actually run and remaining limitations.`);
|
|
319
|
+
return out.join('\n\n') + '\n';
|
|
320
|
+
}
|
|
321
|
+
export function generate(system, idx, write) {
|
|
322
|
+
write('DESIGN.md', designMd(system, idx));
|
|
323
|
+
write('DESIGN.compact.md', compactMd(system, idx));
|
|
324
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
:root { --doc-header-height: 72px; }
|
|
2
|
+
[hidden] { display: none !important; }
|
|
3
|
+
html { scroll-padding-top: calc(var(--doc-header-height) + 24px); }
|
|
4
|
+
body { line-height: 1.65; }
|
|
5
|
+
button { font: inherit; }
|
|
6
|
+
a, button, summary { -webkit-tap-highlight-color: transparent; }
|
|
7
|
+
a:focus-visible, button:focus-visible, summary:focus-visible, [tabindex]:focus-visible { outline: 2px solid var(--P-color-border-action); outline-offset: 4px; }
|
|
8
|
+
.doc-skip { position: fixed; top: 12px; left: 12px; z-index: 100; transform: translateY(-180%); padding: 8px 16px; color: var(--P-color-fg-on-action); background: var(--P-color-bg-action); border-radius: var(--P-radius-control); }
|
|
9
|
+
.doc-skip:focus { transform: none; }
|
|
10
|
+
.doc-header { position: sticky; top: 0; z-index: 30; height: var(--doc-header-height); padding-inline: 32px; border-bottom: 1px solid var(--P-color-border-default); background: var(--P-color-bg-canvas); }
|
|
11
|
+
.doc-header-inner { display: flex; align-items: center; justify-content: space-between; gap: 20px; height: 100%; max-width: 1120px; margin-inline: auto; }
|
|
12
|
+
.doc-brand { display: inline-flex; align-items: center; gap: 10px; white-space: nowrap; text-decoration: none; }
|
|
13
|
+
.doc-brand strong { font-size: 23px; font-weight: 650; letter-spacing: -.06em; line-height: 1; }
|
|
14
|
+
.doc-mark { width: 28px; height: 28px; background: var(--P-color-bg-action); clip-path: polygon(0 0, 100% 0, 100% 22%, 27% 22%, 27% 78%, 100% 78%, 100% 100%, 0 100%); }
|
|
15
|
+
.doc-brand-label { font-size: 13px; color: var(--P-color-fg-muted); border-left: 1px solid var(--P-color-border-default); padding-left: 12px; margin-left: 6px; }
|
|
16
|
+
.doc-header-actions { display: flex; align-items: center; gap: 16px; }
|
|
17
|
+
.doc-icon-button { display: inline-grid; place-items: center; width: 36px; height: 36px; padding: 0; border: 0; border-radius: var(--P-radius-control); color: var(--P-color-fg-muted); background: transparent; cursor: pointer; }
|
|
18
|
+
.doc-icon-button:hover { background: var(--P-color-bg-subtle); }
|
|
19
|
+
.doc-icon-button svg { width: 18px; height: 18px; }
|
|
20
|
+
.doc-theme-sun { display: none; }
|
|
21
|
+
:root[data-theme="dark"] .doc-theme-sun { display: inline; }
|
|
22
|
+
:root[data-theme="dark"] .doc-theme-moon { display: none; }
|
|
23
|
+
.doc-studio-link { display: inline-flex; align-items: center; justify-content: center; gap: 16px; min-height: 38px; padding: 8px 12px; border: 1px solid var(--P-color-border-default); border-radius: var(--P-radius-control); font-size: 13px; font-weight: 500; white-space: nowrap; }
|
|
24
|
+
.doc-studio-link:hover { border-color: var(--P-color-border-action); color: var(--P-color-fg-action); }
|
|
25
|
+
.doc-shell { max-width: 880px; padding-inline: 32px; margin-inline: auto; }
|
|
26
|
+
.doc-nav { display: flex; align-items: center; gap: 32px; border-bottom: 1px solid var(--P-color-border-default); padding-top: 20px; }
|
|
27
|
+
.doc-nav a { display: inline-flex; align-items: center; min-height: 48px; padding: 12px 0; margin-bottom: -1px; border-bottom: 2px solid transparent; color: var(--P-color-fg-muted); font-size: 14px; }
|
|
28
|
+
.doc-nav a:hover { color: var(--P-color-fg-default); }
|
|
29
|
+
.doc-nav a[aria-current="page"] { border-bottom-color: var(--P-color-bg-action); color: var(--P-color-fg-action); font-weight: 600; }
|
|
30
|
+
.doc-main { min-width: 0; padding-top: 44px; }
|
|
31
|
+
.doc-eyebrow { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; font-family: var(--P-font-family-mono); text-transform: uppercase; letter-spacing: .08em; color: var(--P-color-fg-muted); font-size: 10px; }
|
|
32
|
+
.doc-eyebrow::before { content: ""; width: 22px; height: 2px; background: var(--P-color-bg-action); }
|
|
33
|
+
.doc-article h1 { max-width: 22ch; font-size: clamp(29px, 3.5vw, 42px); font-weight: 500; line-height: 1.2; letter-spacing: -.035em; overflow-wrap: anywhere; }
|
|
34
|
+
.doc-article h1[tabindex="-1"]:focus { outline: none; }
|
|
35
|
+
.doc-article .doc-lead { max-width: 65ch; margin: 18px 0 28px; font-size: 16px; color: var(--P-color-fg-muted); line-height: 1.75; }
|
|
36
|
+
.doc-article h2 { margin: 32px 0 12px; font-size: 20px; font-weight: 600; line-height: 1.4; letter-spacing: -.02em; }
|
|
37
|
+
.doc-article p { margin: 12px 0; color: var(--P-color-fg-muted); font-size: 14px; line-height: 1.8; }
|
|
38
|
+
.doc-article p a { color: var(--P-color-fg-action); text-decoration: underline; text-underline-offset: 3px; }
|
|
39
|
+
.doc-start { display: flex; flex-direction: column; align-items: flex-start; gap: 16px; margin: 28px 0 36px; padding: 24px; border: 1px solid var(--P-color-border-default); border-radius: var(--P-radius-card); background: var(--P-color-bg-surface); }
|
|
40
|
+
.doc-start-intent { width: 100%; margin: 0 0 4px; padding: 0; border: 0; color: var(--P-color-fg-default); font-size: 16px; line-height: 1.8; white-space: pre-line; overflow-wrap: anywhere; }
|
|
41
|
+
.doc-start-copy { display: inline-flex; align-items: center; justify-content: space-between; gap: 24px; min-height: 42px; padding: 10px 16px; border: 0; border-radius: var(--P-radius-control); background: var(--P-color-bg-action); color: var(--P-color-fg-on-action); font-size: 13px; font-weight: 500; cursor: pointer; }
|
|
42
|
+
.doc-start-copy:hover { background: var(--P-color-bg-action-hover); }
|
|
43
|
+
.doc-article .doc-start-status { margin: 0; font-size: 12px; line-height: 1.5; }
|
|
44
|
+
.doc-details { margin-top: 26px; padding-block: 16px; border-block: 1px solid var(--P-color-border-default); font-size: 13px; }
|
|
45
|
+
.doc-details summary { color: var(--P-color-fg-muted); cursor: pointer; list-style: revert; }
|
|
46
|
+
.doc-details p { margin-bottom: 0; }
|
|
47
|
+
.doc-studio-cta { margin-bottom: 8px; }
|
|
48
|
+
.doc-steps { counter-reset: step; list-style: none; padding: 0; margin: 24px 0; }
|
|
49
|
+
.doc-steps li { counter-increment: step; position: relative; padding: 24px 0 24px 44px; border-bottom: 1px solid var(--P-color-border-default); }
|
|
50
|
+
.doc-steps li:first-child { padding-top: 12px; }
|
|
51
|
+
.doc-steps li::before { content: "0" counter(step); position: absolute; top: 28px; left: 0; font-family: var(--P-font-family-mono); font-size: 11px; color: var(--P-color-fg-action); }
|
|
52
|
+
.doc-steps li:first-child::before { top: 16px; }
|
|
53
|
+
.doc-steps h2 { margin: 0 0 8px; font-size: 18px; }
|
|
54
|
+
.doc-steps p { margin: 0; }
|
|
55
|
+
.doc-article .doc-hint { margin-top: 26px; font-size: 12px; }
|
|
56
|
+
.doc-footer { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 12px; margin-top: 48px; padding: 20px 0 28px; border-top: 1px solid var(--P-color-border-subtle); font-size: 11px; color: var(--P-color-fg-subtle); }
|
|
57
|
+
.doc-announcer { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
58
|
+
@media (max-width: 600px) {
|
|
59
|
+
:root { --doc-header-height: 64px; }
|
|
60
|
+
.doc-header { padding-inline: 20px; }
|
|
61
|
+
.doc-header-inner { gap: 12px; }
|
|
62
|
+
.doc-brand { gap: 7px; }
|
|
63
|
+
.doc-brand strong { font-size: 21px; }
|
|
64
|
+
.doc-mark { width: 24px; height: 24px; }
|
|
65
|
+
.doc-brand-label { display: none; }
|
|
66
|
+
.doc-header-actions { gap: 6px; }
|
|
67
|
+
.doc-studio-link { gap: 10px; padding-inline: 10px; font-size: 12px; }
|
|
68
|
+
.doc-shell { padding-inline: 20px; }
|
|
69
|
+
.doc-nav { padding-top: 12px; }
|
|
70
|
+
.doc-main { padding-top: 30px; }
|
|
71
|
+
.doc-article .doc-lead { font-size: 15px; }
|
|
72
|
+
.doc-start { padding: 20px; margin-block: 24px 28px; }
|
|
73
|
+
.doc-start-intent { font-size: 14px; }
|
|
74
|
+
.doc-start-copy { width: 100%; gap: 12px; }
|
|
75
|
+
.doc-steps li { padding-left: 36px; }
|
|
76
|
+
.doc-footer { margin-top: 36px; }
|
|
77
|
+
}
|
|
78
|
+
@media print {
|
|
79
|
+
.doc-header, .doc-nav, .doc-start-copy, .doc-start-status, .doc-skip { display: none !important; }
|
|
80
|
+
.doc-shell { max-width: none; padding: 0; }
|
|
81
|
+
.doc-main { padding-top: 0; }
|
|
82
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-BR" data-theme="{{theme}}">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<meta name="description" content="Conecte o Canon ao seu agente com um prompt. Use o Studio para explorar componentes e ajustar o design do seu projeto.">
|
|
7
|
+
<title>Canon · Documentação</title>
|
|
8
|
+
{{font-link}}
|
|
9
|
+
<style>{{styles}}</style>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<a class="doc-skip" href="#doc-main">Pular para o conteúdo</a>
|
|
13
|
+
<header class="doc-header">
|
|
14
|
+
<div class="doc-header-inner">
|
|
15
|
+
<a class="doc-brand" href="#primeiro-projeto" aria-label="Canon — início da documentação"><span class="doc-mark" aria-hidden="true"></span><strong>canon</strong><span class="doc-brand-label">docs</span></a>
|
|
16
|
+
<div class="doc-header-actions">
|
|
17
|
+
<button class="doc-icon-button" id="doc-theme" type="button" aria-label="Alternar tema claro e escuro" title="Alternar tema">
|
|
18
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path class="doc-theme-moon" d="M20.9 13A9 9 0 0 1 11 3.1 9 9 0 1 0 20.9 13Z"/><g class="doc-theme-sun"><circle cx="12" cy="12" r="4"/><path d="M12 2v2m0 16v2M2 12h2m16 0h2M4.9 4.9l1.4 1.4m11.4 11.4 1.4 1.4m-14.2 0 1.4-1.4M17.7 6.3l1.4-1.4"/></g></svg>
|
|
19
|
+
</button>
|
|
20
|
+
<a class="doc-studio-link" href="./preview.html">Abrir Studio <span aria-hidden="true">↗</span></a>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</header>
|
|
24
|
+
<div class="doc-shell">
|
|
25
|
+
<nav class="doc-nav" id="doc-nav" aria-label="Documentação">{{navigation}}</nav>
|
|
26
|
+
<main class="doc-main" id="doc-main" tabindex="-1">
|
|
27
|
+
<article class="doc-article" id="primeiro-projeto" data-label="Conectar">
|
|
28
|
+
<div class="doc-eyebrow">Comece aqui</div>
|
|
29
|
+
<h1>Conecte o Canon ao seu projeto</h1>
|
|
30
|
+
<p class="doc-lead">Copie o prompt e cole no Claude ou no Codex que você já está usando, na conversa do seu projeto. O agente conecta o design e abre o Studio para você.</p>
|
|
31
|
+
<div class="doc-start">
|
|
32
|
+
<blockquote class="doc-start-intent" id="doc-connect-prompt">Use o Canon como design system deste projeto.
|
|
33
|
+
Leia e siga: __CANON_CONNECTION_URL__</blockquote>
|
|
34
|
+
<button class="doc-start-copy" id="doc-start-copy" type="button"><span>Copiar prompt de conexão</span><span aria-hidden="true">↗</span></button>
|
|
35
|
+
<p class="doc-start-status" id="doc-start-status" role="status">Funciona em projetos novos ou em andamento e preserva um Canon já conectado.</p>
|
|
36
|
+
</div>
|
|
37
|
+
<h2 id="primeiro-projeto-aplicar">Continue de onde parou</h2>
|
|
38
|
+
<p>O agente importa o design salvo neste Studio, integra os estilos no app existente e abre um Studio próprio do projeto. Se o projeto já usa Canon, ele mantém a fonte e as personalizações atuais.</p>
|
|
39
|
+
<p>{{connection-customization}}</p>
|
|
40
|
+
<details class="doc-details">
|
|
41
|
+
<summary>Já tenho o MCP conectado</summary>
|
|
42
|
+
<p>Use o mesmo prompt de conexão. O agente preserva a configuração existente, atualiza as referências e abre o Studio vinculado ao projeto.</p>
|
|
43
|
+
</details>
|
|
44
|
+
</article>
|
|
45
|
+
|
|
46
|
+
<article class="doc-article" id="studio" data-label="Studio" hidden>
|
|
47
|
+
<div class="doc-eyebrow">Seu design system, à vista</div>
|
|
48
|
+
<h1>Explore e ajuste no Studio</h1>
|
|
49
|
+
<p class="doc-lead">{{studio-intro}}</p>
|
|
50
|
+
<a class="doc-studio-link doc-studio-cta" href="./preview.html">Abrir Studio <span aria-hidden="true">↗</span></a>
|
|
51
|
+
<ol class="doc-steps">
|
|
52
|
+
<li>
|
|
53
|
+
<h2 id="studio-explorar">Explore o catálogo</h2>
|
|
54
|
+
<p>Use a busca ou as categorias do catálogo. Em <strong>Example</strong>, escolha a variante ou etapa que quer ver. Alterne entre <strong>Desktop</strong> e <strong>Mobile</strong>; use <strong>100%</strong> para conferir os detalhes em tamanho real e <strong>Fit</strong> para ver o conjunto. Links e formulários dos exemplos mantêm você no Studio.</p>
|
|
55
|
+
</li>
|
|
56
|
+
<li>
|
|
57
|
+
<h2 id="studio-personalizar">Personalize o visual</h2>
|
|
58
|
+
<p>No Studio do projeto, abra <strong>Customize → Style</strong> para ajustar cores, fontes, formas e espaçamento. Os componentes acompanham suas escolhas. Confira também o tema claro e escuro.</p>
|
|
59
|
+
</li>
|
|
60
|
+
<li>
|
|
61
|
+
<h2 id="studio-inspecionar">Ajuste uma peça</h2>
|
|
62
|
+
<p>Com o editor aberto, clique em um componente para abrir <strong>Inspect</strong>. Escolha a parte e confira <strong>Apply changes to</strong>: você pode editar o estilo compartilhado ou uma variante específica. Os ajustes ficam agrupados em layout, tipografia, cores e efeitos. <strong>Reset</strong> remove apenas a alteração da variante escolhida. <strong>Direction</strong> guarda a orientação visual que o agente recebe.</p>
|
|
63
|
+
</li>
|
|
64
|
+
<li>
|
|
65
|
+
<h2 id="studio-salvar">Salve e continue com o agente</h2>
|
|
66
|
+
<p>{{studio-save}}</p>
|
|
67
|
+
</li>
|
|
68
|
+
</ol>
|
|
69
|
+
<details class="doc-details">
|
|
70
|
+
<summary id="studio-desfazer">Quero desfazer uma alteração</summary>
|
|
71
|
+
<p>Use <strong>Undo</strong> no editor. Se a mudança já tiver sido salva, clique em <strong>Save</strong> novamente para gravar a reversão.</p>
|
|
72
|
+
</details>
|
|
73
|
+
<p class="doc-hint">O visual aparece no app na atualização normal da prévia. Para mudar a estrutura de uma tela ou seu comportamento, continue a conversa com o agente.</p>
|
|
74
|
+
</article>
|
|
75
|
+
</main>
|
|
76
|
+
<footer class="doc-footer"><span>Canon · {{name}}</span>{{public-links}}<span>v{{version}}</span></footer>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="doc-announcer" id="doc-announcer" role="status" aria-live="polite"></div>
|
|
79
|
+
<script>{{script}}</script>
|
|
80
|
+
</body>
|
|
81
|
+
</html>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
'use strict';
|
|
3
|
+
const articles = [...document.querySelectorAll('.doc-article')];
|
|
4
|
+
const navigation = document.querySelector('#doc-nav');
|
|
5
|
+
const announcer = document.querySelector('#doc-announcer');
|
|
6
|
+
let current = null;
|
|
7
|
+
|
|
8
|
+
async function copyText(value, button) {
|
|
9
|
+
try {
|
|
10
|
+
if (!navigator.clipboard?.writeText) throw new Error('Clipboard API unavailable');
|
|
11
|
+
await navigator.clipboard.writeText(value);
|
|
12
|
+
return true;
|
|
13
|
+
} catch {
|
|
14
|
+
const field = document.createElement('textarea');
|
|
15
|
+
field.value = value;
|
|
16
|
+
field.setAttribute('readonly', '');
|
|
17
|
+
field.style.cssText = 'position:fixed;left:-9999px;top:0';
|
|
18
|
+
document.body.append(field);
|
|
19
|
+
field.select();
|
|
20
|
+
let copied = false;
|
|
21
|
+
try { copied = document.execCommand('copy'); } catch { /* Offer manual selection below. */ }
|
|
22
|
+
field.remove();
|
|
23
|
+
button.focus({ preventScroll: true });
|
|
24
|
+
return copied;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const starterButton = document.querySelector('#doc-start-copy');
|
|
29
|
+
const starterPrompt = document.querySelector('#doc-connect-prompt');
|
|
30
|
+
const starterStatus = document.querySelector('#doc-start-status');
|
|
31
|
+
const connectionUrl = new URL('./CONNECT.md', location.href).href;
|
|
32
|
+
starterPrompt.textContent = starterPrompt.textContent.replace('__CANON_CONNECTION_URL__', connectionUrl);
|
|
33
|
+
starterButton.addEventListener('click', async () => {
|
|
34
|
+
if (await copyText(starterPrompt.textContent, starterButton)) {
|
|
35
|
+
starterStatus.textContent = 'Prompt copiado. Cole na conversa do seu projeto no Claude ou no Codex.';
|
|
36
|
+
} else {
|
|
37
|
+
const range = document.createRange();
|
|
38
|
+
range.selectNodeContents(starterPrompt);
|
|
39
|
+
const selection = window.getSelection();
|
|
40
|
+
selection.removeAllRanges();
|
|
41
|
+
selection.addRange(range);
|
|
42
|
+
starterStatus.textContent = 'Selecionei o prompt acima. Use o atalho de copiar e cole no seu agente.';
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
function navigate() {
|
|
47
|
+
let id = '';
|
|
48
|
+
try { id = decodeURIComponent(location.hash.slice(1)); } catch { /* Unknown fragments open the connection page. */ }
|
|
49
|
+
const target = document.getElementById(id);
|
|
50
|
+
const next = target?.closest('.doc-article') || articles[0];
|
|
51
|
+
const previous = current;
|
|
52
|
+
current = next;
|
|
53
|
+
for (const article of articles) article.hidden = article !== next;
|
|
54
|
+
for (const link of navigation.querySelectorAll('a')) {
|
|
55
|
+
if (link.hash === '#' + next.id) link.setAttribute('aria-current', 'page');
|
|
56
|
+
else link.removeAttribute('aria-current');
|
|
57
|
+
}
|
|
58
|
+
document.title = next.querySelector('h1').textContent + ' · Canon Docs';
|
|
59
|
+
requestAnimationFrame(() => {
|
|
60
|
+
if (current !== next) return;
|
|
61
|
+
if (target && next.contains(target) && target !== next) {
|
|
62
|
+
const details = target.closest('details');
|
|
63
|
+
if (details) details.open = true;
|
|
64
|
+
target.scrollIntoView({ block: 'start' });
|
|
65
|
+
} else {
|
|
66
|
+
window.scrollTo({ top: 0, behavior: 'instant' });
|
|
67
|
+
}
|
|
68
|
+
if (previous && previous !== next) {
|
|
69
|
+
const heading = next.querySelector('h1');
|
|
70
|
+
heading.tabIndex = -1;
|
|
71
|
+
heading.focus({ preventScroll: true });
|
|
72
|
+
announcer.textContent = next.dataset.label;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
navigation.addEventListener('click', event => {
|
|
78
|
+
const link = event.target.closest('a');
|
|
79
|
+
if (link && location.hash === link.hash) {
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
navigate();
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
const themeButton = document.querySelector('#doc-theme');
|
|
85
|
+
function themeAction() {
|
|
86
|
+
const action = document.documentElement.dataset.theme === 'dark' ? 'Ativar tema claro' : 'Ativar tema escuro';
|
|
87
|
+
themeButton.setAttribute('aria-label', action); themeButton.title = action;
|
|
88
|
+
}
|
|
89
|
+
themeAction();
|
|
90
|
+
themeButton.addEventListener('click', () => {
|
|
91
|
+
document.documentElement.dataset.theme = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';
|
|
92
|
+
themeAction();
|
|
93
|
+
});
|
|
94
|
+
window.addEventListener('hashchange', navigate);
|
|
95
|
+
navigate();
|
|
96
|
+
})();
|