@c2n/skill 0.0.7
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/.claude-plugin/plugin.json +12 -0
- package/.mcp.json +9 -0
- package/LICENSE +21 -0
- package/README.md +70 -0
- package/bin/install.js +127 -0
- package/mcp.json +10 -0
- package/package.json +72 -0
- package/plugin.json +27 -0
- package/skills/c2n-components/SKILL.md +92 -0
- package/skills/c2n-components/agents/openai.yaml +7 -0
- package/skills/c2n-components/references/component-catalog.md +90 -0
- package/skills/c2n-components/references/frameworks.md +84 -0
- package/skills/c2n-components/references/theming.md +64 -0
- package/skills/c2n-components/references/variant-components.md +101 -0
- package/skills/c2n-components/references/workflow.md +42 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "c2n",
|
|
3
|
+
"description": "Build UI with the c2n web components (c2-* Lit elements from @c2n/*): theme once with @c2n/theme, use the tags directly, turn repeated looks into variant components. Bundles the @c2n/mcp server.",
|
|
4
|
+
"version": "0.0.7",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "code2nguyen"
|
|
7
|
+
},
|
|
8
|
+
"homepage": "https://code2nguyen.github.io/web-components/",
|
|
9
|
+
"repository": "https://github.com/code2nguyen/web-components",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": ["web components", "lit", "design system", "theme", "c2n"]
|
|
12
|
+
}
|
package/.mcp.json
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Nguyen Thai Vinh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# @c2n/skill
|
|
2
|
+
|
|
3
|
+
Portable Agent Skill and MCP configuration for building applications with the `c2-*` elements from `@c2n/*`. The same package supports Claude Code, Codex and Google Antigravity.
|
|
4
|
+
|
|
5
|
+
## Install in a project
|
|
6
|
+
|
|
7
|
+
Install the package, then let its project-scoped installer copy the skill and merge the MCP server without replacing unrelated configuration:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i -D @c2n/skill
|
|
11
|
+
npx c2n-skill install
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The default installs all supported agents. Select one or disable MCP configuration when needed:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx c2n-skill install --agent claude
|
|
18
|
+
npx c2n-skill install --agent codex
|
|
19
|
+
npx c2n-skill install --agent antigravity
|
|
20
|
+
npx c2n-skill install --agent codex,antigravity --no-mcp
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Generated project files:
|
|
24
|
+
|
|
25
|
+
| Agent | Skill | MCP configuration |
|
|
26
|
+
| ----------- | -------------------------------- | ------------------------- |
|
|
27
|
+
| Claude | `.claude/skills/c2n-components/` | `.mcp.json` |
|
|
28
|
+
| Codex | `.agents/skills/c2n-components/` | `.codex/config.toml` |
|
|
29
|
+
| Antigravity | `.agents/skills/c2n-components/` | `.agents/mcp_config.json` |
|
|
30
|
+
|
|
31
|
+
Codex and Antigravity intentionally share the portable `.agents/skills/` copy. Re-running the installer updates c2n-owned files and preserves other MCP server entries.
|
|
32
|
+
|
|
33
|
+
Restart the selected agent after installation so it discovers both additions. The skill gives the agent c2n-specific workflow and theming guidance; the MCP server supplies the current component APIs, examples, presets and generated code. They work independently, but installing both gives the best result.
|
|
34
|
+
|
|
35
|
+
## Use with an agent
|
|
36
|
+
|
|
37
|
+
Ask the agent to use c2n explicitly when starting an application task, for example:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
Use the c2n skill and MCP server to build this settings form. Check each component API before writing markup, load @c2n/theme once, and create application variants for repeated styles.
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
You can also invoke the installed `c2n-components` skill by name in clients that support explicit skill invocation. The agent should consult MCP first and inspect the installed package manifest when MCP is unavailable.
|
|
44
|
+
|
|
45
|
+
## Plugin installation
|
|
46
|
+
|
|
47
|
+
The package root contains the portable Agent Plugins `plugin.json`, `mcp.json` and `skills/` layout used by Codex-compatible plugin hosts. Claude compatibility remains under `.claude-plugin/plugin.json` and `.mcp.json`.
|
|
48
|
+
|
|
49
|
+
From this repository, Claude Code can install the marketplace plugin:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
/plugin marketplace add code2nguyen/web-components
|
|
53
|
+
/plugin install c2n@c2n
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The MCP server starts through the exact matching `@c2n/mcp` release. For an MCP-only setup, run `npx -y @c2n/mcp` from any stdio MCP client.
|
|
57
|
+
|
|
58
|
+
See the [AI tools guide](https://code2nguyen.github.io/web-components/guides/ai-tools) for installation, verification, manual configuration and troubleshooting in each supported agent.
|
|
59
|
+
|
|
60
|
+
## What the skill does
|
|
61
|
+
|
|
62
|
+
1. Detects the project (framework, installed `@c2n/*` packages, existing theme bridge and variants).
|
|
63
|
+
2. Gets facts from the MCP tools (`get_component`, `get_examples`, `get_theme`), falling back to a compact component catalog for discovery and the installed `custom-elements.json` files for exact APIs.
|
|
64
|
+
3. Themes once with `@c2n/theme`, uses the tags directly, and turns every repeated pattern into a CSS-class variant, a wrapper component, a Lit subclass or a composed component.
|
|
65
|
+
|
|
66
|
+
`references/` holds the workflow, theming, variant and framework guides. `component-catalog.md` is a generated discovery index, not an API reference; installed component manifests provide version-correct API facts when MCP is unavailable.
|
|
67
|
+
|
|
68
|
+
## Development
|
|
69
|
+
|
|
70
|
+
`npm run build:tools` at the repository root regenerates the compact catalog and synchronizes every plugin manifest and `@c2n/mcp` pin from `package.json`. `npm test -w packages/tools/skill` verifies idempotent installation in temporary projects.
|
package/bin/install.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { dirname, join, resolve } from 'node:path'
|
|
4
|
+
import process from 'node:process'
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
6
|
+
|
|
7
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
8
|
+
const supportedAgents = new Set(['claude', 'codex', 'antigravity'])
|
|
9
|
+
|
|
10
|
+
function copyDirectory(source, destination) {
|
|
11
|
+
mkdirSync(destination, { recursive: true })
|
|
12
|
+
for (const name of readdirSync(source)) {
|
|
13
|
+
const from = join(source, name)
|
|
14
|
+
const to = join(destination, name)
|
|
15
|
+
if (statSync(from).isDirectory()) copyDirectory(from, to)
|
|
16
|
+
else copyFileSync(from, to)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function installSkill(projectRoot, relativeDirectory) {
|
|
21
|
+
const destination = join(projectRoot, relativeDirectory, 'c2n-components')
|
|
22
|
+
const existingSkill = join(destination, 'SKILL.md')
|
|
23
|
+
if (existsSync(existingSkill) && !/^name:\s*c2n-components\s*$/m.test(readFileSync(existingSkill, 'utf8'))) {
|
|
24
|
+
throw new Error(`Refusing to replace a different skill at ${destination}`)
|
|
25
|
+
}
|
|
26
|
+
copyDirectory(join(packageRoot, 'skills/c2n-components'), destination)
|
|
27
|
+
return destination
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function mergeJsonServer(file, server) {
|
|
31
|
+
mkdirSync(dirname(file), { recursive: true })
|
|
32
|
+
const value = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : {}
|
|
33
|
+
value.mcpServers = { ...(value.mcpServers ?? {}), c2n: server }
|
|
34
|
+
writeFileSync(file, JSON.stringify(value, null, 2) + '\n')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function mergeCodexServer(file, version) {
|
|
38
|
+
mkdirSync(dirname(file), { recursive: true })
|
|
39
|
+
const lines = existsSync(file) ? readFileSync(file, 'utf8').split(/\r?\n/) : []
|
|
40
|
+
const start = lines.findIndex((line) => line.trim() === '[mcp_servers.c2n]')
|
|
41
|
+
if (start >= 0) {
|
|
42
|
+
let end = start + 1
|
|
43
|
+
while (end < lines.length && !/^\s*\[\[?[^\]]+\]\]?\s*$/.test(lines[end])) end += 1
|
|
44
|
+
lines.splice(start, end - start)
|
|
45
|
+
}
|
|
46
|
+
const current = lines.join('\n').trimEnd()
|
|
47
|
+
const section = `[mcp_servers.c2n]\ncommand = "npx"\nargs = ["-y", "@c2n/mcp@${version}"]`
|
|
48
|
+
writeFileSync(file, `${current}${current ? '\n\n' : ''}${section}\n`)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function installProject({ projectRoot = process.cwd(), agents = ['claude', 'codex', 'antigravity'], includeMcp = true } = {}) {
|
|
52
|
+
const selected = [...new Set(agents)]
|
|
53
|
+
for (const agent of selected) {
|
|
54
|
+
if (!supportedAgents.has(agent)) throw new Error(`Unknown agent "${agent}". Use claude, codex, antigravity, or all.`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const root = resolve(projectRoot)
|
|
58
|
+
const packageJson = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'))
|
|
59
|
+
const command = { command: 'npx', args: ['-y', `@c2n/mcp@${packageJson.version}`] }
|
|
60
|
+
const server = { type: 'stdio', ...command }
|
|
61
|
+
const installedSkills = new Set()
|
|
62
|
+
|
|
63
|
+
if (selected.includes('claude')) installedSkills.add(installSkill(root, '.claude/skills'))
|
|
64
|
+
if (selected.includes('codex') || selected.includes('antigravity')) installedSkills.add(installSkill(root, '.agents/skills'))
|
|
65
|
+
|
|
66
|
+
const configs = []
|
|
67
|
+
if (includeMcp && selected.includes('claude')) {
|
|
68
|
+
const file = join(root, '.mcp.json')
|
|
69
|
+
mergeJsonServer(file, server)
|
|
70
|
+
configs.push(file)
|
|
71
|
+
}
|
|
72
|
+
if (includeMcp && selected.includes('codex')) {
|
|
73
|
+
const file = join(root, '.codex/config.toml')
|
|
74
|
+
mergeCodexServer(file, packageJson.version)
|
|
75
|
+
configs.push(file)
|
|
76
|
+
}
|
|
77
|
+
if (includeMcp && selected.includes('antigravity')) {
|
|
78
|
+
const file = join(root, '.agents/mcp_config.json')
|
|
79
|
+
mergeJsonServer(file, command)
|
|
80
|
+
configs.push(file)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { agents: selected, skills: [...installedSkills], configs }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function usage() {
|
|
87
|
+
return `Install the c2n skill and MCP server into a project.\n\nUsage:\n c2n-skill install [--agent all|claude|codex|antigravity] [--project <path>] [--no-mcp]\n\nThe default is --agent all --project .`
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function run(argv = process.argv.slice(2)) {
|
|
91
|
+
const args = [...argv]
|
|
92
|
+
if (args[0] === 'install') args.shift()
|
|
93
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
94
|
+
console.log(usage())
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let projectRoot = process.cwd()
|
|
99
|
+
let agents = ['claude', 'codex', 'antigravity']
|
|
100
|
+
let includeMcp = true
|
|
101
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
102
|
+
const argument = args[index]
|
|
103
|
+
if (argument === '--no-mcp') includeMcp = false
|
|
104
|
+
else if (argument === '--project') projectRoot = args[++index]
|
|
105
|
+
else if (argument.startsWith('--project=')) projectRoot = argument.slice('--project='.length)
|
|
106
|
+
else if (argument === '--agent') agents = (args[++index] ?? '').split(',')
|
|
107
|
+
else if (argument.startsWith('--agent=')) agents = argument.slice('--agent='.length).split(',')
|
|
108
|
+
else throw new Error(`Unknown argument "${argument}".\n\n${usage()}`)
|
|
109
|
+
}
|
|
110
|
+
if (!projectRoot) throw new Error('--project requires a path')
|
|
111
|
+
if (agents.includes('all')) agents = ['claude', 'codex', 'antigravity']
|
|
112
|
+
|
|
113
|
+
const result = installProject({ projectRoot, agents, includeMcp })
|
|
114
|
+
console.log(`Installed c2n for ${result.agents.join(', ')} in ${resolve(projectRoot)}`)
|
|
115
|
+
for (const skill of result.skills) console.log(` skill: ${skill}`)
|
|
116
|
+
for (const config of result.configs) console.log(` MCP: ${config}`)
|
|
117
|
+
console.log('Restart the selected agent so it discovers the skill and MCP server.')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
121
|
+
try {
|
|
122
|
+
run()
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error(error instanceof Error ? error.message : error)
|
|
125
|
+
process.exitCode = 1
|
|
126
|
+
}
|
|
127
|
+
}
|
package/mcp.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@c2n/skill",
|
|
3
|
+
"version": "0.0.7",
|
|
4
|
+
"description": "Portable agent skill and MCP tooling for building applications with c2n web components",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"c2n-skill": "bin/install.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
".claude-plugin",
|
|
11
|
+
".mcp.json",
|
|
12
|
+
"bin",
|
|
13
|
+
"mcp.json",
|
|
14
|
+
"plugin.json",
|
|
15
|
+
"skills",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"agent-skills",
|
|
20
|
+
"antigravity",
|
|
21
|
+
"claude-code",
|
|
22
|
+
"codex",
|
|
23
|
+
"plugin",
|
|
24
|
+
"skill",
|
|
25
|
+
"mcp",
|
|
26
|
+
"web components",
|
|
27
|
+
"c2n"
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"author": "code2nguyen@gmail.com",
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"registry": "https://registry.npmjs.org",
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/code2nguyen/web-components.git"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "wireit",
|
|
41
|
+
"prepack": "npm run build",
|
|
42
|
+
"test": "node --test scripts/install.test.ts",
|
|
43
|
+
"version": "node scripts/build-skill.ts && git add .claude-plugin/plugin.json .mcp.json mcp.json plugin.json skills"
|
|
44
|
+
},
|
|
45
|
+
"wireit": {
|
|
46
|
+
"build": {
|
|
47
|
+
"dependencies": [
|
|
48
|
+
"../mcp:build"
|
|
49
|
+
],
|
|
50
|
+
"command": "node scripts/build-skill.ts",
|
|
51
|
+
"files": [
|
|
52
|
+
"scripts/build-skill.ts",
|
|
53
|
+
"../mcp/data/registry.json",
|
|
54
|
+
"package.json"
|
|
55
|
+
],
|
|
56
|
+
"output": [
|
|
57
|
+
"skills/c2n-components/references/component-catalog.md",
|
|
58
|
+
".claude-plugin/plugin.json",
|
|
59
|
+
".mcp.json",
|
|
60
|
+
"mcp.json",
|
|
61
|
+
"plugin.json"
|
|
62
|
+
],
|
|
63
|
+
"clean": false
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@c2n/config": "*",
|
|
68
|
+
"@c2n/mcp": "0.0.7",
|
|
69
|
+
"prettier": "3.9.6"
|
|
70
|
+
},
|
|
71
|
+
"gitHead": "c8db398a27f7cd417d665fdf5da455fee2d4e910"
|
|
72
|
+
}
|
package/plugin.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
|
+
"name": "c2n",
|
|
4
|
+
"description": "Build UI with the c2n web components (c2-* Lit elements from @c2n/*): theme once with @c2n/theme, use the tags directly, turn repeated looks into variant components. Bundles the @c2n/mcp server.",
|
|
5
|
+
"version": "0.0.7",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "code2nguyen"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://code2nguyen.github.io/web-components/",
|
|
10
|
+
"repository": "https://github.com/code2nguyen/web-components",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": ["web components", "lit", "design system", "theme", "c2n"],
|
|
13
|
+
"extensions": {
|
|
14
|
+
"com.openai": {
|
|
15
|
+
"interface": {
|
|
16
|
+
"displayName": "c2n Web Components",
|
|
17
|
+
"shortDescription": "Build and theme applications with c2n components",
|
|
18
|
+
"longDescription": "Component APIs, examples, theme guidance and reusable workflows for applications built with @c2n web components.",
|
|
19
|
+
"developerName": "code2nguyen",
|
|
20
|
+
"category": "Developer Tools",
|
|
21
|
+
"capabilities": ["Read"],
|
|
22
|
+
"websiteURL": "https://code2nguyen.github.io/web-components/",
|
|
23
|
+
"defaultPrompt": ["Use c2n Web Components to build this interface with the appropriate c2-* elements and theme tokens."]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: c2n-components
|
|
3
|
+
description: Build and theme applications with the `c2-*` web components from `@c2n/*`. Use when adopting c2n, implementing UI with c2n components, mapping `@c2n/theme` tokens, or creating application-owned variants and compositions.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# c2n components
|
|
8
|
+
|
|
9
|
+
Build screens from `@c2n/*` web components with the least code: **theme once, use the tags directly, name what repeats.** Facts about components come from the c2n MCP tools or the component manifests, never from memory. The references in `references/` hold the details; read only the one the current step points to.
|
|
10
|
+
|
|
11
|
+
This workflow expects a JavaScript project and Node.js 20 or newer. The c2n MCP server is recommended; the skill retains a compact discovery index and can inspect installed package manifests when MCP is unavailable.
|
|
12
|
+
|
|
13
|
+
## 1. Detect the project
|
|
14
|
+
|
|
15
|
+
Inspect the project's `package.json` with the available filesystem or shell tools. Identify installed `@c2n/*` packages and the framework before changing dependencies or source files.
|
|
16
|
+
|
|
17
|
+
Then look for what already exists before adding anything:
|
|
18
|
+
|
|
19
|
+
- a theme: `--c2-theme--` in the CSS, or an import of `@c2n/theme`;
|
|
20
|
+
- a variants directory: `src/components/ui/`, `src/ui/`, files that `extends … from '@c2n/`, or classes setting `--c2-…` variables;
|
|
21
|
+
- the CSS entry point and the dark-mode switch (`data-theme`, a class, `prefers-color-scheme`).
|
|
22
|
+
|
|
23
|
+
Reuse an existing variant or token bridge over creating a new one.
|
|
24
|
+
|
|
25
|
+
## 2. Get the facts
|
|
26
|
+
|
|
27
|
+
When c2n MCP tools are available:
|
|
28
|
+
|
|
29
|
+
1. `list_components` or `search_components` to pick the component for the need.
|
|
30
|
+
2. `get_component` for attributes, slots, events, documented CSS parts and CSS variables (grouped by semantic target and state, with the theme token each follows).
|
|
31
|
+
3. `get_examples` for real markup. Start with the default example, then retrieve gallery examples matching the requested state, layout or use case. Preserve any accessibility note returned with an example.
|
|
32
|
+
4. `get_presets` when a curated visual treatment is useful. A preset is structured CSS-variable and attribute data suitable for generation; a gallery example is broader usage and composition context.
|
|
33
|
+
5. `get_theme` **before writing any CSS**, so overrides go on tokens when a token exists.
|
|
34
|
+
6. `generate_variant` when a selected preset or gallery look repeats: it validates names and emits the class / HTML / Lit code.
|
|
35
|
+
7. `get_workflow_guide` for the workflow, theming, variant or framework guide text.
|
|
36
|
+
|
|
37
|
+
Without the server, use `references/component-catalog.md` only to identify a likely package. Then read `node_modules/@c2n/<name>/custom-elements.json` for the installed version's attributes, slots, events, CSS parts and CSS properties. If the package is not installed, use https://code2nguyen.github.io/web-components/. Never infer an API from the catalog or invent a variable, attribute, slot or event name.
|
|
38
|
+
|
|
39
|
+
## 3. Theme once
|
|
40
|
+
|
|
41
|
+
Read `references/theming.md`.
|
|
42
|
+
|
|
43
|
+
- Install the theme with the components: `npm install @c2n/theme @c2n/<component>…`.
|
|
44
|
+
- Import `@c2n/theme/theme.css` once at the application root (`main.ts`, root layout, global stylesheet). If the app already has design tokens, import `@c2n/theme/base.css` alone and bridge the app's tokens onto the `--c2-theme--*` names on `:root`.
|
|
45
|
+
- Override tokens on `:root` (light) and under the app's dark selector. Component variables are never set globally when a token covers the job.
|
|
46
|
+
- Register elements with side-effect imports (`import '@c2n/button'`) at the entry or in the module that renders them; icons individually (`import '@c2n/feather-icons/icons/search.js'`).
|
|
47
|
+
|
|
48
|
+
## 4. Decide: tag, variant, or composed component
|
|
49
|
+
|
|
50
|
+
Read `references/variant-components.md` when creating one.
|
|
51
|
+
|
|
52
|
+
| Situation | Do |
|
|
53
|
+
| ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
54
|
+
| Appears once, themed default is right | plain `<c2-…>` tag |
|
|
55
|
+
| Same look repeats, markup otherwise plain | **CSS class** setting `--c2-<component>__…` variables (in the app's variants stylesheet) |
|
|
56
|
+
| Same attributes / slots / accessible name repeat too | **wrapper component** in the app's framework rendering the c2 element |
|
|
57
|
+
| Must be its own tag (strings, other Lit templates, shipping) | **Lit subclass**: `class X extends Button { static override styles = [Button.styles, css\`:host{…}\`] }`, defined as `app-*` |
|
|
58
|
+
| Several c2 components + logic repeat | **composed component**: children's variables on `:host`/a class, attributes forwarded, events re-emitted |
|
|
59
|
+
| No c2 component fits (kbd hint, count pill, …) | app component styled with `--c2-theme--*` tokens; avoid patching installed `@c2n/*` packages for app-local behavior |
|
|
60
|
+
|
|
61
|
+
Naming: the app's prefix (`app-*`, `site-*`, `my-*`), kebab-case with a hyphen, never `c2-*`. One file per variant in one directory, exported from a barrel, with a short header comment (what it wraps, which variables it fixes, where it is used).
|
|
62
|
+
|
|
63
|
+
## 5. Conventions
|
|
64
|
+
|
|
65
|
+
- Prefer variables set on the host element, a class or an ancestor. Use `::part()` only when `get_component` documents that native CSS part and variables cannot express the change. Do not repeat inline `style="--c2-…"`.
|
|
66
|
+
- Grammar `--c2-<component>__<part>[__<state>]--<property>`; states `hover | active | focus | selected | disabled | open | error | read-only`.
|
|
67
|
+
- Boolean attributes are present or absent (`disabled`, `running`, `selected`); slots by name (`prefix-icon`, `suffix-icon`, `header`, `footer`, `description`…).
|
|
68
|
+
- Composed components set child variables from the parent (`--c2-text-field--border-top: none` on the wrapper class) and re-emit events (`redispatchEvent` from `@c2n/core/dom-helper.js` in Lit).
|
|
69
|
+
- Icons: use the registered Feather or Phosphor icon tag and its published module; prefer individual icon imports and theme through the icon package's shared variables.
|
|
70
|
+
- Dark mode only through tokens; SSR or static HTML guards unregistered tags with `c2-x:not(:defined) { visibility: hidden }`.
|
|
71
|
+
|
|
72
|
+
## 6. Framework notes
|
|
73
|
+
|
|
74
|
+
Read `references/frameworks.md` for details.
|
|
75
|
+
|
|
76
|
+
- Plain HTML / Vite: side-effect imports in a `<script type="module">` or the entry module.
|
|
77
|
+
- Lit: import what you render; subclass for tag variants; `redispatchEvent` for child events.
|
|
78
|
+
- Astro: `@astrojs/lit` islands (`<Button client:load>`, kebab-case attributes only; a `client:only` island must not contain islands) or plain tags plus a client `<script>` for repeated markup; variant styles need `is:global`.
|
|
79
|
+
- React 19: props become attributes, custom events via `ref.addEventListener`; older React needs string attributes and refs.
|
|
80
|
+
- Vue: `compilerOptions.isCustomElement = (tag) => tag.startsWith('c2-')`; `.prop` for arrays/objects.
|
|
81
|
+
|
|
82
|
+
## 7. Verify
|
|
83
|
+
|
|
84
|
+
- Every element used is registered (no empty tags, no `HTMLUnknownElement`); `@c2n/theme` imported exactly once.
|
|
85
|
+
- Every variable, attribute, slot and event name exists in `get_component` / the manifest.
|
|
86
|
+
- Every `::part()` name exists in `get_component`; no repeated inline variable styles; repeated looks became variants.
|
|
87
|
+
- Variant tags contain a hyphen and do not start with `c2-`.
|
|
88
|
+
- Light and dark both checked; the project's build, lint and type-check pass.
|
|
89
|
+
|
|
90
|
+
## 8. Report
|
|
91
|
+
|
|
92
|
+
List the packages added, where the theme is imported and which tokens are set, each variant created (name → base tag, file), composed components, and the gaps left as plain app components.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
interface:
|
|
2
|
+
display_name: 'c2n Components'
|
|
3
|
+
short_description: 'Build applications with c2n components'
|
|
4
|
+
default_prompt: 'Use $c2n-components to build this interface with the appropriate c2-* elements and theme tokens.'
|
|
5
|
+
|
|
6
|
+
policy:
|
|
7
|
+
allow_implicit_invocation: true
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# c2n component catalog
|
|
2
|
+
|
|
3
|
+
GENERATED by `packages/tools/skill/scripts/build-skill.ts` from the @c2n/mcp registry (c2n 0.0.7). Do not edit by hand.
|
|
4
|
+
Use this compact index only to discover a likely component when the c2n MCP server is unavailable. It intentionally omits component APIs and examples. Read the selected installed package's `custom-elements.json` for exact attributes, slots, events, CSS parts and CSS properties.
|
|
5
|
+
|
|
6
|
+
## Buttons
|
|
7
|
+
|
|
8
|
+
- **Button** — `c2-button` · `@c2n/button` — Themeable button with slots for text, prefix, suffix and running icons.
|
|
9
|
+
- **Button Group** — `c2-button-group` · `@c2n/button-group` — Attached buttons with shared borders, optionally a segmented control with single or multiple selection.
|
|
10
|
+
- **Copy Button** — `c2-copy-button` · `@c2n/copy-button` — Button that copies text to the clipboard — the element it sits in, another element by id, or a literal string.
|
|
11
|
+
- **Icon Button** — `c2-icon-button` · `@c2n/icon-button` — Round, hoverable button wrapping a slotted SVG icon.
|
|
12
|
+
|
|
13
|
+
## Chart
|
|
14
|
+
|
|
15
|
+
- **Area chart** — `c2-area-chart, c2-chart-series` · `@c2n/chart` — A line chart with the region under each line filled — every line-chart attribute, plus a fill opacity.
|
|
16
|
+
- **Bar chart** — `c2-bar-chart, c2-chart-series` · `@c2n/chart` — Bars over categories or time buckets, with several series drawn side by side within each band.
|
|
17
|
+
- **Line chart** — `c2-line-chart, c2-chart-series` · `@c2n/chart` — A line chart over a time or numeric x axis, drawn on canvas by uPlot, with series declared as children.
|
|
18
|
+
- **Pie chart** — `c2-pie-chart, c2-chart-series` · `@c2n/chart` — A pie or donut chart drawn by ECharts, where one row is one slice and label-field names it.
|
|
19
|
+
- **Sparkline** — `c2-sparkline, c2-chart-series` · `@c2n/chart` — A chromeless trend line sized for a table cell, a list row or the trend slot of a c2-stat.
|
|
20
|
+
|
|
21
|
+
## Chat
|
|
22
|
+
|
|
23
|
+
- **Chat Input** — `c2-chat-input` · `@c2n/chat-input` — Auto-growing message composer with keyboard submission, toolbar actions and native form support.
|
|
24
|
+
- **Chat Message** — `c2-chat-message` · `@c2n/chat-message` — Flexible message row for conversations, assistant answers and activity updates.
|
|
25
|
+
|
|
26
|
+
## Data display
|
|
27
|
+
|
|
28
|
+
- **Attachment** — `c2-attachment, c2-attachment-group` · `@c2n/attachment` — File and image attachments with metadata, upload progress, failure states, and actions.
|
|
29
|
+
- **Avatar** — `c2-avatar` · `@c2n/avatar` — Image, initials or icon for a person, with status dot and badge.
|
|
30
|
+
- **Badge** — `c2-badge` · `@c2n/badge` — Tinted pill for status text, counts and dots, optionally pinned to a corner of another element.
|
|
31
|
+
- **Code Viewer** — `c2-code-viewer` · `@c2n/code-viewer` — Syntax-highlighted code with line numbers, copy button and dark mode, powered by shiki.
|
|
32
|
+
- **Kbd** — `c2-kbd` · `@c2n/kbd` — Keyboard key label for shortcuts and command hints, with the semantics of the native kbd element.
|
|
33
|
+
- **List** — `c2-list` · `@c2n/list` — Vertical list container with single or multiple selection.
|
|
34
|
+
- **List Item** — `c2-list-item` · `@c2n/list-item` — Selectable row with icon slots, used on its own or as the option of list and select.
|
|
35
|
+
- **Stat** — `c2-stat` · `@c2n/stat` — Displays a KPI with an optional icon, trend and supporting description.
|
|
36
|
+
- **Table** — `c2-table, c2-table-column` · `@c2n/table` — Virtualized data grid with declarative columns, sorting, selection, pinning and resizing.
|
|
37
|
+
- **Virtual List** — `c2-virtual-list` · `@c2n/virtual-list` — Windowed list with built-in search, sorting, selection and an async data source.
|
|
38
|
+
|
|
39
|
+
## Feedback
|
|
40
|
+
|
|
41
|
+
- **Modal** — `c2-modal` · `@c2n/modal` — Dialog built on the native dialog element: focus trap, backdrop, Escape, title, body and footer.
|
|
42
|
+
- **Overlay** — `c2-overlay` · `@c2n/overlay` — Anchored popup built on the browser Popover API, positioned with floating-ui.
|
|
43
|
+
- **Progress** — `c2-progress` · `@c2n/progress` — Linear progress bar, indeterminate or filling to a value, with an optional label and count.
|
|
44
|
+
- **Sheet** — `c2-sheet` · `@c2n/sheet` — Dialog pinned to an edge of the screen, for content that complements the page rather than interrupting it.
|
|
45
|
+
- **Skeleton** — `c2-skeleton` · `@c2n/skeleton` — Placeholder block standing in for content that has not arrived, in three shapes and three animations.
|
|
46
|
+
- **Spinner** — `c2-spinner` · `@c2n/spinner` — Circular progress indicator, indeterminate or showing a value, with optional text.
|
|
47
|
+
- **Toast** — `c2-toast, c2-toast-region` · `@c2n/toast` — Notification cards and a manager for stacked, queued notifications with independent lifetimes.
|
|
48
|
+
- **Tooltip** — `c2-tooltip` · `@c2n/tooltip` — Contextual hint shown when its target is hovered or focused, rendered in the top layer.
|
|
49
|
+
|
|
50
|
+
## Icons
|
|
51
|
+
|
|
52
|
+
- **Feather Icons** — `c2-feather-{name}` · `@c2n/feather-icons` — 287 open-source Feather icons, one web component each.
|
|
53
|
+
- **Mat Icon** — `c2-mat-icon` · `@c2n/mat-icon` — 2,234 Material Icons ligatures rendered through a single element.
|
|
54
|
+
- **Phosphor Icons** — `c2-phosphor-{name}` · `@c2n/phosphor-icons` — 1,512 flexible icons in six weights, one web component each.
|
|
55
|
+
|
|
56
|
+
## Inputs
|
|
57
|
+
|
|
58
|
+
- **Autocomplete** — `c2-autocomplete` · `@c2n/autocomplete` — Searchable combobox for local or remote items with customizable list rows.
|
|
59
|
+
- **Checkbox** — `c2-checkbox` · `@c2n/checkbox` — Native checkbox behaviour in a quiet, themeable box with an opt-in hover layer.
|
|
60
|
+
- **Color Area** — `c2-color-area` · `@c2n/color-area` — Two-dimensional area for picking saturation and value of a colour.
|
|
61
|
+
- **Color Select** — `c2-color-select` · `@c2n/color-select` — Colour swatch that opens a full picker built from area and slider.
|
|
62
|
+
- **Color Slider** — `c2-color-slider` · `@c2n/color-slider` — Horizontal slider for choosing a hue from 0 to 360.
|
|
63
|
+
- **Label** — `c2-label` · `@c2n/label` — Caption that names and activates the control referenced by its for attribute, with a required marker.
|
|
64
|
+
- **Radio** — `c2-radio, c2-radio-group` · `@c2n/radio` — Radio options built on native inputs, grouped into one value with keyboard navigation.
|
|
65
|
+
- **Select** — `c2-select` · `@c2n/select` — Dropdown that pairs a themeable trigger with an anchored list of c2-list-item options.
|
|
66
|
+
- **Slider** — `c2-slider` · `@c2n/slider` — Range input with a themeable track, thumb, step ticks and value bubble.
|
|
67
|
+
- **Switch** — `c2-switch` · `@c2n/switch` — On/off toggle on a native switch input, with label, description and thumb icons.
|
|
68
|
+
- **Text Field** — `c2-text-field, c2-text-field-clear` · `@c2n/text-field` — Single-line input with icon slots, clear button, helper and error text, and a character counter.
|
|
69
|
+
- **Textarea** — `c2-textarea` · `@c2n/textarea` — Multiline text input with resizing, helper and error text, and a character counter.
|
|
70
|
+
|
|
71
|
+
## Layout
|
|
72
|
+
|
|
73
|
+
- **Accordion** — `c2-accordion` · `@c2n/accordion` — Connected, animated panels with shared borders and single or multiple expansion.
|
|
74
|
+
- **Card** — `c2-card` · `@c2n/card` — Groups related content and actions on a single bordered surface.
|
|
75
|
+
- **Chatbot** — `c2-chatbot` · `@c2n/chatbot` — Chatbot component for c2n applications.
|
|
76
|
+
- **Details** — `c2-details` · `@c2n/details` — Collapsible disclosure built on native details and summary.
|
|
77
|
+
- **dropdown-list** — `c2-dropdown-list` · `@c2n/dropdown-list` — dropdown-list component for c2n applications.
|
|
78
|
+
- **Header** — `c2-header` · `@c2n/header` — Arranges brand, navigation, actions and a mobile trigger in a reusable site shell.
|
|
79
|
+
- **reorder-list** — `c2-reorder-list` · `@c2n/reorder-list` — reorder-list component for c2n applications.
|
|
80
|
+
- **Seperator** — `c2-seperator` · `@c2n/seperator` — Horizontal or vertical rule with an optional label, for dividing content and toolbars.
|
|
81
|
+
|
|
82
|
+
## Navigation
|
|
83
|
+
|
|
84
|
+
- **Breadcrumb** — `c2-breadcrumb` · `@c2n/breadcrumb` — Navigation trail of link buttons with separators, a current page and optional collapsing.
|
|
85
|
+
- **Link Button** — `c2-link-button` · `@c2n/link-button` — Text-styled control for link and navigation actions, rendered as a real anchor or a button.
|
|
86
|
+
- **Menu** — `c2-menu, c2-menu-item` · `@c2n/menu` — Commands, links, checkboxes and submenus in a popover anchored to a trigger.
|
|
87
|
+
- **Navigation Menu** — `c2-navigation-menu, c2-navigation-menu-item, c2-navigation-menu-link` · `@c2n/navigation-menu` — Site navigation bar whose triggers open panels of links below the header.
|
|
88
|
+
- **Pagination** — `c2-pagination` · `@c2n/pagination` — Page navigation in three layouts: numbered pages, a simple page status, or a table-footer row with rows-per-page.
|
|
89
|
+
- **Side Nav** — `c2-side-nav` · `@c2n/side-nav` — Responsive navigation drawer beside the page: pushes the content on large screens, slides over it with a backdrop on small ones.
|
|
90
|
+
- **Tabs** — `c2-tabs, c2-tab` · `@c2n/tabs` — Tab strip that shows one content panel at a time.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Using c2n components per framework
|
|
2
|
+
|
|
3
|
+
All components are standard custom elements (Lit 3). Register with a side-effect import; then they are plain HTML.
|
|
4
|
+
|
|
5
|
+
## Events (every framework)
|
|
6
|
+
|
|
7
|
+
Each component that fires events exports an event map (`TableEventMap`, `SelectEventMap`, …) from the same module as its class, and declares typed `addEventListener` overloads, so the detail narrows with no cast:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
table.addEventListener('selection-change', (event) => event.detail.rows) // TableRow[]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`EventMapOf<T>` from `@c2n/core/event-helper.js` recovers a component's map for code that is generic over elements. Use the map type instead of hand-writing `CustomEvent<{ value: string[] }>`.
|
|
14
|
+
|
|
15
|
+
`selection-change` **does not bubble** — `c2-list`, `c2-select`, `c2-table`, `c2-tabs` and `c2-virtual-list` all fire it, so a listener goes on the element itself, never on an ancestor. `c2-tabs` fires `selection-change`, not `change`.
|
|
16
|
+
|
|
17
|
+
Every form-associated component fires plain `input` and `change` alongside its semantic event, which is what generic two-way bindings listen for.
|
|
18
|
+
|
|
19
|
+
A duplicate `customElements.define` warns and keeps the first definition instead of throwing.
|
|
20
|
+
|
|
21
|
+
## Plain HTML / Vite / any bundler
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<script type="module">
|
|
25
|
+
import '@c2n/theme/theme.css'
|
|
26
|
+
import '@c2n/button'
|
|
27
|
+
</script>
|
|
28
|
+
<c2-button>Save</c2-button>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
With a bundler, put the imports in the entry module (`main.ts`). CSS imports work through the bundler's CSS handling.
|
|
32
|
+
|
|
33
|
+
## Lit
|
|
34
|
+
|
|
35
|
+
Import what you render at the top of the component module (`import '@c2n/text-field'`). Extend a component for a tag variant (`class AppField extends TextField`). Re-emit child events with `redispatchEvent` from `@c2n/core/dom-helper.js`. Set child variables in your `static styles` on `:host` or on a class.
|
|
36
|
+
|
|
37
|
+
## Astro
|
|
38
|
+
|
|
39
|
+
- Islands (`@astrojs/lit`): `import { Button } from '@c2n/button'` in the frontmatter, `<Button client:load>` in the template. SSR'd with declarative shadow DOM, hydrated on load. Pass **kebab-case attributes** only (a prop whose name matches an element property is set as a property and forces `defer-hydration`). A `client:only` island must not contain islands: its children end up in a `<template>` and nothing hydrates; the parent module registers the children instead.
|
|
40
|
+
- Plain tags + client script: `<c2-button>` in the template and `import '@c2n/button'` inside a `<script>`. Cheaper for repeated markup (no shadow-DOM copy per instance); guard the flash with `c2-button:not(:defined) { visibility: hidden }`.
|
|
41
|
+
- Scoped `<style>` does not reach elements rendered by child components; use `is:global` (or `:global()`) for variant classes.
|
|
42
|
+
|
|
43
|
+
## React 19
|
|
44
|
+
|
|
45
|
+
Custom elements work as JSX tags. React 19 passes primitive props as attributes and functions as event listeners for `on*` names; for custom events attach listeners with a `ref` (`ref.current.addEventListener('selection-change', …)`).
|
|
46
|
+
|
|
47
|
+
Types: `import '@c2n/<name>/react'` — one line per package, in any `.d.ts` — declares the tags in `JSX.IntrinsicElements` with props derived from the element class. Do not hand-write the mapping. React 18 and older: pass attributes as strings and use refs for events and properties.
|
|
48
|
+
|
|
49
|
+
## Vue 3
|
|
50
|
+
|
|
51
|
+
Tell the compiler about the tags: `compilerOptions.isCustomElement = (tag) => tag.startsWith('c2-')` (in `@vitejs/plugin-vue`'s `template.compilerOptions`); without it every `c2-*` tag is treated as a Vue component and renders nothing. Register the elements at module scope before `mount()`: Vue chooses between a property and an attribute with `key in el`, so a binding on an element that has not upgraded yet falls back to an attribute.
|
|
52
|
+
|
|
53
|
+
Types: `import '@c2n/<name>/vue'` registers the tags with Volar, and `"extends": [..., "@c2n/framework-types/tsconfig.vue.json"]` supplies the matching `vueCompilerOptions` (`strictTemplates`, plus the `v-model` prop mapping so `v-model` binds `value`/`checked` rather than `modelValue`). Declare no local `vueCompilerOptions` next to it — a local one replaces the inherited object rather than merging.
|
|
54
|
+
|
|
55
|
+
- Events: `@selection-change`, `@submit-message` bind by their real kebab-case name — Vue calls `addEventListener` with the name as written. The handler gets a plain `Event`, so narrow it (`(event as CustomEvent<{ value: string[] }>).detail`).
|
|
56
|
+
- `v-model` works on `c2-text-field` / `c2-textarea`: on a custom element Vue compiles it to the plain-text model directive, which sets `el.value` and listens for `input`, and both components expose `value` and re-emit the native `input` event.
|
|
57
|
+
- `.prop` forces a DOM property (`:value.prop="selected"` for array/object values); `.attr` forces an attribute (`:align.attr="side"`), needed when a property is not reflected but the component styles it with `:host([attr])` — `c2-chat-message`'s `align` is the case to know.
|
|
58
|
+
- A static attribute stays an attribute, so spell it the way the component declares it (`row-key`, not `rowKey`).
|
|
59
|
+
|
|
60
|
+
## Angular
|
|
61
|
+
|
|
62
|
+
`CUSTOM_ELEMENTS_SCHEMA` on the component is the only required configuration. `[rows]="…"` writes a property with `setProperty`; `(selection-change)` binds the event by its real name. A **static** attribute stays an attribute, so a camelCase property needs `[rowKey]="'id'"` or the real attribute name (`row-key`) — the lowercase spelling `rowkey` is ignored and logs a warning.
|
|
63
|
+
|
|
64
|
+
Forms: Angular's built-in value accessors match `input`/`select`/`textarea` only, so `ngModel` and `formControlName` do nothing on a c2 control without `@c2n/angular`. Add `imports: [FormsModule, ...C2_FORM_ACCESSORS]`.
|
|
65
|
+
|
|
66
|
+
The schema turns off template type checking, so `$event` is a bare `Event`: take the component's event-map type in the handler rather than `$any` at the call site.
|
|
67
|
+
|
|
68
|
+
## Svelte
|
|
69
|
+
|
|
70
|
+
Svelte binds attributes and `on:` events directly.
|
|
71
|
+
|
|
72
|
+
## Table cells
|
|
73
|
+
|
|
74
|
+
`renderCell` cannot return framework markup (it is handed to Lit). Mark the column `cell-slot` and render one light-DOM child per row into `slot="cell:<row key>:<field>"`; the children stay in the document, so ordinary CSS reaches them. Requires `row-key`; `renderCell`/the column format is the fallback.
|
|
75
|
+
|
|
76
|
+
## Editor support outside TypeScript
|
|
77
|
+
|
|
78
|
+
`@c2n/framework-types` ships `dist/html-custom-data.json` (point `html.customData` at it for VS Code / Volar) and `dist/web-types.json` (picked up automatically by the JetBrains IDEs). Both are generated from the custom-elements manifests, and cover plain HTML, Angular templates and Vue SFCs.
|
|
79
|
+
|
|
80
|
+
## Server-side rendering and static HTML
|
|
81
|
+
|
|
82
|
+
- Components must not touch `document`/`window` at module scope; the c2 components guard with `isServer`.
|
|
83
|
+
- Without SSR of the shadow DOM, hide unregistered tags until they upgrade: `c2-modal:not(:defined) { display: none }` (dialogs, lists) or `visibility: hidden` (layout-stable chrome).
|
|
84
|
+
- Theme CSS is plain CSS; load it in the document head so the first paint is themed.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Theming c2n components
|
|
2
|
+
|
|
3
|
+
## Layers
|
|
4
|
+
|
|
5
|
+
1. **Tokens** `--c2-theme--<name>` (from `@c2n/theme/tokens.css`, ~35 of them). Set by the application.
|
|
6
|
+
2. **Base theme** `@c2n/theme/base.css`: `--c2-<component>__<part>--<prop>: var(--c2-theme--<token>, <component default>)` on `:root`/`:host` for every mapped component variable. Generated from the component manifests.
|
|
7
|
+
3. **Component variables** `--c2-<component>__<part>[__<state>]--<property>`: the fine-grained escape hatch. Set on an element, a class or any ancestor; they always win over the base theme.
|
|
8
|
+
4. **Variant components** built from 2 and 3.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import '@c2n/theme/theme.css' // tokens + base theme, once
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```css
|
|
17
|
+
:root {
|
|
18
|
+
--c2-theme--color-primary: #7c3aed;
|
|
19
|
+
--c2-theme--color-primary-hover: #6d28d9;
|
|
20
|
+
--c2-theme--radius-md: 10px;
|
|
21
|
+
--c2-theme--font-family: 'Inter', system-ui, sans-serif;
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Bring your own tokens
|
|
26
|
+
|
|
27
|
+
```css
|
|
28
|
+
@import '@c2n/theme/base.css';
|
|
29
|
+
:root {
|
|
30
|
+
--c2-theme--color-primary: var(--brand-600);
|
|
31
|
+
--c2-theme--color-on-primary: var(--brand-on-600);
|
|
32
|
+
--c2-theme--color-surface: var(--surface);
|
|
33
|
+
--c2-theme--color-on-surface: var(--text);
|
|
34
|
+
--c2-theme--color-outline: var(--border);
|
|
35
|
+
--c2-theme--radius-md: var(--radius);
|
|
36
|
+
--c2-theme--font-family: var(--font-sans);
|
|
37
|
+
--c2-theme--focus-ring: 2px solid var(--brand-600);
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Dark mode then follows the app's own switch because the bridged values flip.
|
|
42
|
+
|
|
43
|
+
## Dark mode with the shipped tokens
|
|
44
|
+
|
|
45
|
+
`tokens.css` sets light values on `:root` and dark values under `[data-theme='dark']` and `.c2-dark`, with `prefers-color-scheme: dark` as the fallback when no `data-theme` attribute is present. `data-theme="light"` / `.c2-light` opts a subtree out. Selectors are not tied to the root element, so any subtree can be inverted.
|
|
46
|
+
|
|
47
|
+
## Token groups
|
|
48
|
+
|
|
49
|
+
- Colour roles: `color-primary`, `color-primary-hover`, `color-primary-active`, `color-on-primary`, `color-primary-container`, `color-surface`, `color-surface-container-low`, `color-surface-container`, `color-on-surface`, `color-on-surface-variant`, `color-outline`, `color-outline-variant`, `color-outline-strong`, `color-error`, `color-scrim`, `color-inverse-surface`, `color-on-inverse-surface`.
|
|
50
|
+
- Typography: `font-family` (unset by default: components inherit the page font), `font-size-sm|md`, `font-weight-medium|semibold`.
|
|
51
|
+
- Shape: `radius-sm|md|lg|xl|full`.
|
|
52
|
+
- Borders: `border-width`, `border` (composite: the whole resting border shorthand; falls back to `border-width solid color-outline`).
|
|
53
|
+
- Interaction: `focus-ring` (outline shorthand), `disabled-opacity`, `motion-scale` (multiplies every duration; `0` disables motion).
|
|
54
|
+
- Elevation: `shadow-md`, `shadow-lg`.
|
|
55
|
+
|
|
56
|
+
Composite tokens wrap their primitives: `var(--c2-theme--border, var(--c2-theme--border-width, 1px) solid var(--c2-theme--color-outline, #bcbcc6))`, so set the shorthand or just the colour.
|
|
57
|
+
|
|
58
|
+
## What the base theme does not cover
|
|
59
|
+
|
|
60
|
+
Sizes, paddings, gaps, identity colours (avatar fallbacks), status colours, the code viewer's syntax theme, and variables without a default (they inherit). Use component variables or a variant for those. `@c2n/theme/report.json` lists what is mapped per package; MCP `get_theme` with a `tag` shows the mapping of one component.
|
|
61
|
+
|
|
62
|
+
## Grammar and states
|
|
63
|
+
|
|
64
|
+
`--c2-<component>__<part>[__<state>]--<property>`: `__` separates component / part / state, `--` separates the CSS property. States: `hover`, `active`, `focus`, `selected`, `disabled`, `open`, `error`, `read-only`. Example: `--c2-checkbox__container__selected--background-color`.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Variant and composed components
|
|
2
|
+
|
|
3
|
+
A variant is one c2 component with a fixed look (CSS variables) and, sometimes, fixed attributes, slots or accessible name. A composed component is several c2 components plus glue. Both normally set children's CSS variables on the host element or a class. Use `::part()` only for a part documented by `get_component` and only when the public variables cannot express the change.
|
|
4
|
+
|
|
5
|
+
## Shape 1: CSS-class variant
|
|
6
|
+
|
|
7
|
+
```css
|
|
8
|
+
.danger-button {
|
|
9
|
+
--c2-button__container--background-color: var(--c2-theme--color-error);
|
|
10
|
+
--c2-button__container__hover--background-color: color-mix(in srgb, var(--c2-theme--color-error), black 12%);
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<c2-button class="danger-button">Delete</c2-button>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Use when only the look differs. Keep variant classes in one stylesheet (`ui/variants.css`). Read variable names from the component API; do not invent them.
|
|
19
|
+
|
|
20
|
+
## Shape 2: wrapper component
|
|
21
|
+
|
|
22
|
+
When attributes, slots or the accessible name repeat as well. Framework-native, zero runtime cost beyond the c2 element. Example (Astro; the same idea in React/Vue/Svelte):
|
|
23
|
+
|
|
24
|
+
```astro
|
|
25
|
+
---
|
|
26
|
+
// SiteIconButton.astro — c2-icon-button with the app's size and a tooltip that defaults to the label.
|
|
27
|
+
interface Props {
|
|
28
|
+
label: string
|
|
29
|
+
size?: 'sm' | 'md'
|
|
30
|
+
class?: string
|
|
31
|
+
[attr: string]: unknown
|
|
32
|
+
}
|
|
33
|
+
const { label, size = 'md', class: className = '', ...rest } = Astro.props
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
<c2-icon-button class={`site-icon-button site-icon-button--${size} ${className}`} aria-label={label} tooltip={label} {...rest}>
|
|
37
|
+
<slot />
|
|
38
|
+
</c2-icon-button>
|
|
39
|
+
<style is:global>
|
|
40
|
+
.site-icon-button {
|
|
41
|
+
--c2-icon-button--border-radius: var(--c2-theme--radius-sm);
|
|
42
|
+
--c2-icon-button__hover--background-color: var(--c2-theme--color-surface-container);
|
|
43
|
+
}
|
|
44
|
+
.site-icon-button--sm {
|
|
45
|
+
--c2-icon-button__state-layer--size: 30px;
|
|
46
|
+
--c2-icon-button__icon--width: 16px;
|
|
47
|
+
--c2-icon-button__icon--height: 16px;
|
|
48
|
+
}
|
|
49
|
+
.site-icon-button--md {
|
|
50
|
+
--c2-icon-button__state-layer--size: 36px;
|
|
51
|
+
--c2-icon-button__icon--width: 18px;
|
|
52
|
+
--c2-icon-button__icon--height: 18px;
|
|
53
|
+
}
|
|
54
|
+
</style>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Forward unknown props as attributes (`{...rest}`) so callers keep the full c2 API (`disabled`, `data-*`, `aria-*`).
|
|
58
|
+
|
|
59
|
+
## Shape 3: Lit subclass
|
|
60
|
+
|
|
61
|
+
When the variant must be a tag of its own. The docs studio's Code tab and MCP `generate_variant` produce this:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { css } from 'lit'
|
|
65
|
+
import { Button } from '@c2n/button'
|
|
66
|
+
|
|
67
|
+
export class DangerButton extends Button {
|
|
68
|
+
static override styles = [
|
|
69
|
+
Button.styles,
|
|
70
|
+
css`
|
|
71
|
+
:host {
|
|
72
|
+
--c2-button__container--background-color: var(--c2-theme--color-error);
|
|
73
|
+
}
|
|
74
|
+
`,
|
|
75
|
+
]
|
|
76
|
+
override connectedCallback() {
|
|
77
|
+
super.connectedCallback()
|
|
78
|
+
this.setAttribute('running', '') // fixed attributes go here
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (!customElements.get('app-danger-button')) customElements.define('app-danger-button', DangerButton)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Import the class from the module that defines the element (`@c2n/tabs/tab.js` for `c2-tab`, `@c2n/feather-icons/icons/<name>.js` for icons). Importing it also registers the original `c2-*` tag, which is fine. Never `customElements.define` a `c2-` name.
|
|
85
|
+
Guard registration with `customElements.get()` so development HMR or repeated module evaluation cannot define the same tag twice.
|
|
86
|
+
|
|
87
|
+
## Composed components
|
|
88
|
+
|
|
89
|
+
Several c2 components and some logic repeat: a search field (`c2-text-field` + `c2-list`), a settings dialog (`c2-modal` + form controls), a toolbar (`c2-icon-button`s + `c2-tooltip`s).
|
|
90
|
+
|
|
91
|
+
- Set the children's variables on the composed component's class or `:host` (custom properties inherit through shadow boundaries): `.search-palette__field { --c2-text-field--border-top: none; }`.
|
|
92
|
+
- Forward the attributes callers should control as properties/attributes of the composed component.
|
|
93
|
+
- Re-emit the child events callers need. In Lit use `redispatchEvent(this, event)` from `@c2n/core/dom-helper.js`; elsewhere `dispatchEvent(new CustomEvent(...))`.
|
|
94
|
+
- Let the primitives keep their responsibilities (a `c2-modal` already traps focus, closes on Escape and backdrop click, locks scrolling and restores focus): write only the glue.
|
|
95
|
+
- Declare composition in JSDoc (`@internalcomponent c2-list`, `@slotcomponent c2-list-item`) when the composed component is itself a Lit element with a manifest.
|
|
96
|
+
|
|
97
|
+
## Naming and placement
|
|
98
|
+
|
|
99
|
+
- Own prefix: `app-*`, `site-*`, `my-*`. A custom element name must contain a hyphen.
|
|
100
|
+
- One file per variant, one directory (`src/components/ui/`), exported from a barrel.
|
|
101
|
+
- Three-line header comment: what it wraps, which variables it fixes, where it is used.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# The c2n application workflow
|
|
2
|
+
|
|
3
|
+
Build screens from `@c2n/*` web components with as little code as possible. Three layers, applied in this order.
|
|
4
|
+
|
|
5
|
+
## 1. Theme once
|
|
6
|
+
|
|
7
|
+
- Install `@c2n/theme` next to the component packages you use.
|
|
8
|
+
- Import `@c2n/theme/theme.css` once at the application root (tokens + base theme). If the app already owns a token system, import only `@c2n/theme/base.css` and bridge your tokens onto the `--c2-theme--*` names.
|
|
9
|
+
- Override the tokens that differ from the defaults on `:root` (light) and under your dark-mode selector. About 35 tokens (`--c2-theme--color-primary`, `--c2-theme--radius-md`, `--c2-theme--font-family`, `--c2-theme--focus-ring`, …) drive every component.
|
|
10
|
+
- Never set component variables globally when a token exists for the job.
|
|
11
|
+
|
|
12
|
+
## 2. Use the tags directly
|
|
13
|
+
|
|
14
|
+
- Register an element with a side-effect import (`import '@c2n/button'`) at the application entry, or in the module that renders it.
|
|
15
|
+
- Write plain markup: `<c2-button>Save</c2-button>`. Attributes, slots and events come from the component API (MCP `get_component`, or `node_modules/@c2n/<name>/custom-elements.json`).
|
|
16
|
+
- Retrieve `get_examples` next: begin from the unmodified Default sample, then select gallery examples by use case. Use `get_presets` when structured values are more useful than a complete pattern.
|
|
17
|
+
- Icons are components: `c2-feather-<name>` from `@c2n/feather-icons/icons/<name>.js`, sized and coloured through `--c2-feather-icon--size|color|stroke-width`.
|
|
18
|
+
- A component that appears once with the themed default look needs nothing else.
|
|
19
|
+
|
|
20
|
+
## 3. Name what repeats
|
|
21
|
+
|
|
22
|
+
The decision rule, cheapest first:
|
|
23
|
+
|
|
24
|
+
| Situation | Do this |
|
|
25
|
+
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
26
|
+
| The element appears once, default look is fine | plain tag |
|
|
27
|
+
| Same look repeats, markup is otherwise plain | **CSS-class variant**: a class that sets `--c2-<component>__…` variables |
|
|
28
|
+
| Same look **and** the same attributes / slots / accessible name repeat | **wrapper component** in your framework (Astro/React/Vue/Lit) that renders the c2 element |
|
|
29
|
+
| The variant must be its own tag (used from strings, other Lit templates, shipped) | **Lit subclass** of the component with the variables baked into `static styles` |
|
|
30
|
+
| Several c2 components plus some logic repeat | **composed component**: children's variables set on `:host`/a class, attributes forwarded, events re-emitted |
|
|
31
|
+
| No c2 component fits | app-level component styled with the same `--c2-theme--*` tokens; avoid patching installed packages locally |
|
|
32
|
+
|
|
33
|
+
Every variant lives in one directory (`src/components/ui/`, `src/ui/`…), one file per variant, under the app's own prefix (`app-*`, `site-*`, `my-*`), never `c2-*`.
|
|
34
|
+
|
|
35
|
+
## 4. Verify
|
|
36
|
+
|
|
37
|
+
- Every element used is registered (no `HTMLUnknownElement`, no empty tags).
|
|
38
|
+
- `@c2n/theme` is imported exactly once; tokens overridden on `:root` and the dark selector.
|
|
39
|
+
- Prefer CSS custom properties. Every `::part()` selector must name a CSS part documented by `get_component`; no repeated inline `style="--c2-…"`.
|
|
40
|
+
- Every variable, attribute, slot and event name exists in the component API.
|
|
41
|
+
- Variant tags contain a hyphen and do not start with `c2-`.
|
|
42
|
+
- Light and dark both look right; build, lint and type-check pass.
|