@andersseen/skills 0.0.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Andersseen
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,72 @@
1
+ # @andersseen/skills
2
+
3
+ Installable **AI agent skills** for the Andersseen ecosystem — one focused skill
4
+ per library, plus an orchestrator that routes between them. Works with any agent
5
+ that reads `.claude/skills/` (Claude Code, and compatible tools).
6
+
7
+ Each skill teaches an agent how to use **one** library correctly (real package
8
+ names, verified APIs, no invented props), so you can install only what your
9
+ project needs.
10
+
11
+ ## Quick start
12
+
13
+ ```bash
14
+ # Interactive: pick skills, then choose install or download
15
+ npx @andersseen/skills
16
+
17
+ # Install everything (orchestrator + all libraries)
18
+ npx @andersseen/skills add --all
19
+
20
+ # Add just what you need
21
+ npx @andersseen/skills add andersseen web-components icon
22
+ ```
23
+
24
+ `pnpm dlx @andersseen/skills …` and `yarn dlx @andersseen/skills …` work too.
25
+
26
+ ## The two modes
27
+
28
+ | Mode | Command flag | Destination | Use when |
29
+ | ------------ | ------------------- | ---------------------- | ------------------------------------- |
30
+ | **Install** | _(default)_ | `./.claude/skills/` | Activate immediately for your agent |
31
+ | **Download** | `--download` / `-d` | `./andersseen-skills/` | Review the SKILL.md before activating |
32
+
33
+ ## Available skills
34
+
35
+ | Skill | Alias | Library |
36
+ | --------------------------- | ---------------- | --------------------------------- |
37
+ | `andersseen` | `orchestrator` | Ecosystem router (start here) |
38
+ | `andersseen-web-components` | `web-components` | `@andersseen/web-components` |
39
+ | `andersseen-headless-core` | `headless-core` | `@andersseen/headless-components` |
40
+ | `andersseen-icon` | `icon` | `@andersseen/icon` |
41
+ | `andersseen-motion` | `motion` | `@andersseen/motion` |
42
+ | `andersseen-layout` | `layout` | `@andersseen/layout` |
43
+
44
+ ```bash
45
+ npx @andersseen/skills list # print the same table with descriptions
46
+ ```
47
+
48
+ ## Commands
49
+
50
+ ```
51
+ npx @andersseen/skills Interactive picker
52
+ npx @andersseen/skills list List available skills
53
+ npx @andersseen/skills add <name...> Install into ./.claude/skills/
54
+ npx @andersseen/skills add --all Install every skill
55
+ npx @andersseen/skills add <name> --download Download into ./andersseen-skills/
56
+
57
+ Options: -d/--download -a/--all -f/--force -h/--help
58
+ ```
59
+
60
+ Names accept the full folder name (`andersseen-web-components`) or the short
61
+ alias (`web-components`). `all` selects everything.
62
+
63
+ ## Recommended: start with the orchestrator
64
+
65
+ Install `andersseen` alongside the per-library skills. It decides **which**
66
+ library fits a task and defers to the focused skill — so the agent reaches for
67
+ the right package instead of guessing.
68
+
69
+ ```bash
70
+ npx @andersseen/skills add andersseen web-components headless-core icon motion layout
71
+ # ≡ npx @andersseen/skills add --all
72
+ ```
package/bin/cli.mjs ADDED
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env node
2
+ // @andersseen/skills — install focused AI agent skills into a consumer project.
3
+ //
4
+ // Two modes, mirroring the two things a dev actually wants:
5
+ // install → copies the skill into ./.claude/skills/<name>/ (agent picks it up)
6
+ // download → copies the skill into ./andersseen-skills/<name>/ (review first)
7
+ //
8
+ // Zero runtime dependencies: Node built-ins only, so `npx @andersseen/skills`
9
+ // works offline once the package is fetched.
10
+
11
+ import { readdir, readFile, mkdir, cp } from 'node:fs/promises';
12
+ import { existsSync } from 'node:fs';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { dirname, join, resolve } from 'node:path';
15
+ import { createInterface } from 'node:readline';
16
+
17
+ const HERE = dirname(fileURLToPath(import.meta.url));
18
+ const SKILLS_DIR = resolve(HERE, '..', 'skills');
19
+ const INSTALL_DIR = resolve(process.cwd(), '.claude', 'skills');
20
+ const DOWNLOAD_DIR = resolve(process.cwd(), 'andersseen-skills');
21
+
22
+ const c = {
23
+ reset: '\x1b[0m',
24
+ bold: '\x1b[1m',
25
+ dim: '\x1b[2m',
26
+ cyan: '\x1b[36m',
27
+ green: '\x1b[32m',
28
+ yellow: '\x1b[33m',
29
+ red: '\x1b[31m',
30
+ };
31
+ const paint = (color, s) => (process.stdout.isTTY ? `${c[color]}${s}${c.reset}` : s);
32
+
33
+ /** Read every skill folder that has a SKILL.md, with its description. */
34
+ async function discoverSkills() {
35
+ if (!existsSync(SKILLS_DIR)) return [];
36
+ const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
37
+ const skills = [];
38
+ for (const entry of entries) {
39
+ if (!entry.isDirectory()) continue;
40
+ const skillMd = join(SKILLS_DIR, entry.name, 'SKILL.md');
41
+ if (!existsSync(skillMd)) continue;
42
+ const raw = await readFile(skillMd, 'utf8');
43
+ skills.push({
44
+ name: entry.name,
45
+ alias: entry.name.replace(/^andersseen-/, ''),
46
+ description: frontmatterDescription(raw),
47
+ dir: join(SKILLS_DIR, entry.name),
48
+ orchestrator: entry.name === 'andersseen',
49
+ });
50
+ }
51
+ // Orchestrator first, then alphabetical.
52
+ return skills.sort((a, b) => Number(b.orchestrator) - Number(a.orchestrator) || a.name.localeCompare(b.name));
53
+ }
54
+
55
+ function frontmatterDescription(raw) {
56
+ const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
57
+ if (!match) return '';
58
+ const body = match[1];
59
+ const line = /description:\s*(?:'([^']*)'|"([^"]*)"|(.*))/.exec(body);
60
+ if (!line) return '';
61
+ return (line[1] ?? line[2] ?? line[3] ?? '').replace(/\s+/g, ' ').trim();
62
+ }
63
+
64
+ /** Resolve user tokens (folder name, alias, or "all") to skill objects. */
65
+ function resolveSelection(tokens, skills) {
66
+ const wanted = new Set();
67
+ const unknown = [];
68
+ for (const tokenRaw of tokens) {
69
+ const token = tokenRaw.toLowerCase();
70
+ if (token === 'all' || token === '*') {
71
+ skills.forEach(s => wanted.add(s.name));
72
+ continue;
73
+ }
74
+ const hit = skills.find(s => s.name === token || s.alias === token);
75
+ if (hit) wanted.add(hit.name);
76
+ else unknown.push(tokenRaw);
77
+ }
78
+ return { selected: skills.filter(s => wanted.has(s.name)), unknown };
79
+ }
80
+
81
+ async function place(skill, mode, force) {
82
+ const root = mode === 'download' ? DOWNLOAD_DIR : INSTALL_DIR;
83
+ const target = join(root, skill.name);
84
+ if (existsSync(target) && !force) {
85
+ console.log(
86
+ ` ${paint('yellow', '•')} ${skill.name} ${paint('dim', 'already exists (skipped — pass --force to overwrite)')}`,
87
+ );
88
+ return 'skipped';
89
+ }
90
+ await mkdir(root, { recursive: true });
91
+ await cp(skill.dir, target, { recursive: true, force: true });
92
+ const shown = mode === 'download' ? join('andersseen-skills', skill.name) : join('.claude', 'skills', skill.name);
93
+ console.log(` ${paint('green', '✓')} ${skill.name} ${paint('dim', '→ ' + shown)}`);
94
+ return 'written';
95
+ }
96
+
97
+ function printSkillList(skills) {
98
+ const pad = Math.max(...skills.map(s => s.name.length));
99
+ for (const s of skills) {
100
+ const tag = s.orchestrator ? paint('cyan', ' [orchestrator]') : '';
101
+ console.log(` ${paint('bold', s.name.padEnd(pad))}${tag}`);
102
+ if (s.description) {
103
+ const short = s.description.length > 96 ? s.description.slice(0, 93) + '…' : s.description;
104
+ console.log(` ${paint('dim', ' '.repeat(pad) + ' ' + short)}`);
105
+ }
106
+ }
107
+ }
108
+
109
+ function help() {
110
+ console.log(`
111
+ ${paint('bold', '@andersseen/skills')} — install AI agent skills for the Andersseen ecosystem
112
+
113
+ ${paint('bold', 'Usage')}
114
+ npx @andersseen/skills ${paint('dim', 'interactive picker (choose skills + install/download)')}
115
+ npx @andersseen/skills list ${paint('dim', 'list available skills')}
116
+ npx @andersseen/skills add <name...> ${paint('dim', 'install into ./.claude/skills/')}
117
+ npx @andersseen/skills add --all ${paint('dim', 'install every skill')}
118
+ npx @andersseen/skills add <name> --download ${paint('dim', 'download into ./andersseen-skills/ (review first)')}
119
+
120
+ ${paint('bold', 'Options')}
121
+ -d, --download Download only — do not activate in .claude/skills
122
+ -a, --all Select every skill
123
+ -f, --force Overwrite an existing skill folder
124
+ -h, --help Show this help
125
+
126
+ ${paint('bold', 'Names')} accept the folder name or its short alias, e.g.
127
+ andersseen-web-components ${paint('dim', '≡')} web-components
128
+ andersseen ${paint('dim', '≡')} orchestrator ("andersseen" routes to the right skill)
129
+
130
+ ${paint('bold', 'Examples')}
131
+ npx @andersseen/skills add andersseen web-components icon
132
+ pnpm dlx @andersseen/skills add --all
133
+ yarn dlx @andersseen/skills add layout --download
134
+ `);
135
+ }
136
+
137
+ function ask(rl, question) {
138
+ return new Promise(res => rl.question(question, a => res(a.trim())));
139
+ }
140
+
141
+ async function interactive(skills) {
142
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
143
+ try {
144
+ console.log(paint('bold', '\nAndersseen agent skills\n'));
145
+ skills.forEach((s, i) => {
146
+ const tag = s.orchestrator ? paint('cyan', ' [orchestrator]') : '';
147
+ console.log(` ${paint('bold', String(i + 1).padStart(2))}. ${s.name}${tag}`);
148
+ if (s.description) {
149
+ const short = s.description.length > 88 ? s.description.slice(0, 85) + '…' : s.description;
150
+ console.log(` ${paint('dim', short)}`);
151
+ }
152
+ });
153
+ console.log(`\n ${paint('dim', "Enter numbers (e.g. 1,3), a name, or 'all'.")}`);
154
+ const pick = await ask(rl, paint('cyan', '\nSkills to add: '));
155
+ let selected;
156
+ if (/^(all|\*)$/i.test(pick.trim())) {
157
+ selected = skills.slice();
158
+ } else {
159
+ const byIndex = pick
160
+ .split(/[,\s]+/)
161
+ .filter(Boolean)
162
+ .map(t => (/^\d+$/.test(t) ? skills[Number(t) - 1]?.name : t))
163
+ .filter(Boolean);
164
+ selected = resolveSelection(byIndex, skills).selected;
165
+ }
166
+ if (selected.length === 0) {
167
+ console.log(paint('yellow', '\nNothing selected. Bye.'));
168
+ return 0;
169
+ }
170
+
171
+ console.log(
172
+ `\n ${paint('bold', '1')}. Install ${paint('dim', '→ ./.claude/skills/ (your agent uses it immediately)')}`,
173
+ );
174
+ console.log(
175
+ ` ${paint('bold', '2')}. Download ${paint('dim', '→ ./andersseen-skills/ (review before activating)')}`,
176
+ );
177
+ const action = await ask(rl, paint('cyan', '\nChoose 1 or 2 [1]: '));
178
+ const mode = action.trim() === '2' || /^d/i.test(action.trim()) ? 'download' : 'install';
179
+
180
+ console.log('');
181
+ for (const skill of selected) await place(skill, mode, false);
182
+ printDone(mode, selected);
183
+ return 0;
184
+ } finally {
185
+ rl.close();
186
+ }
187
+ }
188
+
189
+ function printDone(mode, selected) {
190
+ console.log('');
191
+ if (mode === 'install') {
192
+ console.log(paint('green', `Installed ${selected.length} skill(s) into .claude/skills/.`));
193
+ console.log(paint('dim', 'Restart your agent (or reload skills) to pick them up.'));
194
+ } else {
195
+ console.log(paint('green', `Downloaded ${selected.length} skill(s) into andersseen-skills/.`));
196
+ console.log(paint('dim', 'Move the folders into .claude/skills/ when you are ready to activate them.'));
197
+ }
198
+ }
199
+
200
+ async function main() {
201
+ const argv = process.argv.slice(2);
202
+ if (argv.includes('-h') || argv.includes('--help')) return help();
203
+
204
+ const skills = await discoverSkills();
205
+ if (skills.length === 0) {
206
+ console.error(paint('red', 'No skills bundled with this package.'));
207
+ process.exitCode = 1;
208
+ return;
209
+ }
210
+
211
+ const command = argv[0];
212
+
213
+ if (command === 'list' || command === 'ls') {
214
+ console.log(paint('bold', '\nAvailable skills\n'));
215
+ printSkillList(skills);
216
+ console.log('');
217
+ return;
218
+ }
219
+
220
+ if (command === undefined) return interactive(skills);
221
+
222
+ if (command === 'add' || command === 'install' || command === 'i') {
223
+ const rest = argv.slice(1);
224
+ const download = rest.includes('-d') || rest.includes('--download');
225
+ const all = rest.includes('-a') || rest.includes('--all');
226
+ const force = rest.includes('-f') || rest.includes('--force');
227
+ const names = rest.filter(a => !a.startsWith('-'));
228
+ const mode = download ? 'download' : 'install';
229
+
230
+ let selected;
231
+ if (all) {
232
+ selected = skills.slice();
233
+ } else {
234
+ if (names.length === 0) {
235
+ console.error(paint('red', 'Specify at least one skill name, or use --all.'));
236
+ console.error(paint('dim', 'Run `npx @andersseen/skills list` to see the options.'));
237
+ process.exitCode = 1;
238
+ return;
239
+ }
240
+ const { selected: sel, unknown } = resolveSelection(names, skills);
241
+ if (unknown.length) {
242
+ console.error(paint('red', `Unknown skill(s): ${unknown.join(', ')}`));
243
+ console.error(paint('dim', 'Run `npx @andersseen/skills list` to see the options.'));
244
+ process.exitCode = 1;
245
+ return;
246
+ }
247
+ selected = sel;
248
+ }
249
+
250
+ console.log('');
251
+ for (const skill of selected) await place(skill, mode, force);
252
+ printDone(mode, selected);
253
+ return;
254
+ }
255
+
256
+ console.error(paint('red', `Unknown command: ${command}`));
257
+ help();
258
+ process.exitCode = 1;
259
+ }
260
+
261
+ main().catch(err => {
262
+ console.error(paint('red', 'Error: ' + (err?.message ?? String(err))));
263
+ process.exitCode = 1;
264
+ });
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@andersseen/skills",
3
+ "version": "0.0.1",
4
+ "description": "Installable AI agent skills (Claude Code, Cursor, ...) for the Andersseen ecosystem. One focused skill per library, plus an orchestrator.",
5
+ "type": "module",
6
+ "bin": {
7
+ "andersseen-skills": "bin/cli.mjs",
8
+ "and-skills": "bin/cli.mjs"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "skills",
13
+ "README.md"
14
+ ],
15
+ "keywords": [
16
+ "claude-code",
17
+ "agent-skills",
18
+ "cursor",
19
+ "web-components",
20
+ "andersseen",
21
+ "ai"
22
+ ],
23
+ "author": "Andersseen",
24
+ "license": "MIT",
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/Andersseen/and-web-components.git",
31
+ "directory": "packages/skills"
32
+ },
33
+ "scripts": {
34
+ "lint": "node bin/cli.mjs list >/dev/null",
35
+ "format": "prettier --write \"**/*.{md,mjs,json}\"",
36
+ "format:check": "prettier --check \"**/*.{md,mjs,json}\""
37
+ }
38
+ }
@@ -0,0 +1,93 @@
1
+ ---
2
+ name: andersseen
3
+ description:
4
+ 'Orchestrator for the Andersseen front-end ecosystem. Use this to decide WHICH
5
+ Andersseen library to reach for and route to its focused skill before writing
6
+ code. Trigger phrases: andersseen, @andersseen, and-* component, andersseen
7
+ design system, which andersseen package, build a UI with andersseen, headless
8
+ logic, icon, motion animation, layout attributes. Routes to:
9
+ andersseen-web-components, andersseen-headless-core, andersseen-icon,
10
+ andersseen-motion, andersseen-layout.'
11
+ ---
12
+
13
+ # Andersseen — ecosystem orchestrator
14
+
15
+ You are working in a project that uses the **Andersseen** front-end ecosystem: a
16
+ set of small, framework-agnostic libraries that compose. Your job in this skill
17
+ is to **pick the right library for the task and defer to its focused skill**
18
+ instead of guessing an API.
19
+
20
+ Each library is documented by a dedicated skill (one library, one skill). Load
21
+ the matching skill's guidance before generating code for that library. If a
22
+ sibling skill is not installed, tell the user how to add it (see _Install_).
23
+
24
+ ## Routing table — task → library → skill
25
+
26
+ | The user wants to… | Library | Skill |
27
+ | ------------------------------------------------------------- | --------------------------------- | --------------------------- |
28
+ | Drop styled, ready-made UI (`<and-button>`, `<and-modal>`, …) | `@andersseen/web-components` | `andersseen-web-components` |
29
+ | Build a **custom** UI but reuse state/a11y/keyboard logic | `@andersseen/headless-components` | `andersseen-headless-core` |
30
+ | Render or register SVG icons (`<and-icon>`, `getIcon`) | `@andersseen/icon` | `andersseen-icon` |
31
+ | Animate on scroll/hover/tap via HTML attributes | `@andersseen/motion` | `andersseen-motion` |
32
+ | Compose layout & typography with `and-layout` / `and-text` | `@andersseen/layout` | `andersseen-layout` |
33
+
34
+ ## How the layers relate
35
+
36
+ ```
37
+ headless-core → pure TS state machines + ARIA + keyboard (no DOM, no CSS)
38
+
39
+ web-components → Stencil custom elements (Shadow DOM) built ON headless-core + icon
40
+ ```
41
+
42
+ `icon`, `motion`, and `layout` are independent satellites usable from any
43
+ framework or from plain HTML.
44
+
45
+ ## Decision guide
46
+
47
+ 1. **Need a component that already exists?** (button, card, modal, tabs, drawer,
48
+ navbar, select, input…) → use `web-components`. Do **not** re-implement it
49
+ with headless-core.
50
+ 2. **Building something the library doesn't ship, but that needs correct
51
+ open/close, focus, keyboard, or ARIA?** → use `headless-core` and provide
52
+ your own markup/styles.
53
+ 3. **Just icons?** → `icon`. Remember `registerIcons(COMPONENT_ICONS)` is
54
+ required whenever `web-components` is used.
55
+ 4. **Just motion/entrance animation?** → `motion` (attribute-driven, respects
56
+ `prefers-reduced-motion`).
57
+ 5. **Just layout/spacing/typography without a utility framework?** → `layout`.
58
+
59
+ You can combine several — e.g. `web-components` + `icon` + `motion`. When you
60
+ do, follow each library's own skill rules; never invent props, classes, or
61
+ attributes that a skill does not document.
62
+
63
+ ## Global rules for every Andersseen library
64
+
65
+ - Use the **real package names** exactly: `@andersseen/web-components`,
66
+ `@andersseen/headless-components`, `@andersseen/icon`, `@andersseen/motion`,
67
+ `@andersseen/layout`.
68
+ - Only use public props, slots, events, attributes, and exported functions that
69
+ the relevant skill documents. Never target internal Shadow DOM parts.
70
+ - Prefer design-token CSS variables over hardcoded colors.
71
+ - Clean up: call the `cleanup()`/`destroy()` handles that a library returns when
72
+ unmounting SPA views.
73
+
74
+ ## Install
75
+
76
+ Skills are distributed via the `@andersseen/skills` CLI:
77
+
78
+ ```bash
79
+ # install the orchestrator + everything
80
+ npx @andersseen/skills add --all
81
+
82
+ # or add just the ones you need
83
+ npx @andersseen/skills add andersseen web-components icon
84
+
85
+ # download to ./andersseen-skills/ to review before activating
86
+ npx @andersseen/skills add layout --download
87
+ ```
88
+
89
+ Runtime packages install normally, e.g.:
90
+
91
+ ```bash
92
+ npm i @andersseen/web-components @andersseen/icon
93
+ ```
@@ -0,0 +1,178 @@
1
+ ---
2
+ name: andersseen-headless-core
3
+ description:
4
+ 'Reuse Andersseen headless behavior primitives
5
+ (@andersseen/headless-components) for state, accessibility, and keyboard logic
6
+ when building a CUSTOM UI. Load when you need correct open/close, focus, ARIA,
7
+ or keyboard handling without a pre-styled component: createTabs,
8
+ createDropdown, createModal, createTooltip, createToastManager,
9
+ createAccordion, createDrawer, createNavbar, createSidebar. Trigger phrases:
10
+ headless, state machine, a11y logic, keyboard navigation, ARIA props, custom
11
+ component behavior.'
12
+ ---
13
+
14
+ # @andersseen/headless-components — headless behavior primitives
15
+
16
+ Framework-agnostic headless primitives for behavior, a11y, and keyboard
17
+ navigation. **No DOM rendering, no CSS, no framework lock-in.** You provide
18
+ markup/styles; the library provides state + ARIA + interaction logic. Reach for
19
+ this when the pre-built `@andersseen/web-components` element does not fit and
20
+ you must build custom markup that still behaves correctly.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ npm i @andersseen/headless-components
26
+ ```
27
+
28
+ ## Verified module exports
29
+
30
+ ```
31
+ @andersseen/headless-components
32
+ @andersseen/headless-components/button
33
+ @andersseen/headless-components/accordion
34
+ @andersseen/headless-components/tabs
35
+ @andersseen/headless-components/dropdown
36
+ @andersseen/headless-components/modal
37
+ @andersseen/headless-components/tooltip
38
+ @andersseen/headless-components/toast
39
+ @andersseen/headless-components/drawer
40
+ @andersseen/headless-components/alert
41
+ @andersseen/headless-components/navbar
42
+ @andersseen/headless-components/sidebar
43
+ @andersseen/headless-components/breadcrumb
44
+ @andersseen/headless-components/menu-list
45
+ @andersseen/headless-components/context-menu
46
+ ```
47
+
48
+ ## Canonical API contract
49
+
50
+ Most modules follow:
51
+
52
+ ```ts
53
+ const logic = createX(config);
54
+
55
+ logic.state; // read-only state snapshot
56
+ logic.actions; // imperative updates
57
+ logic.queries; // derived state (where available)
58
+ logic.get*Props(...); // ARIA/data attributes for semantic elements
59
+ logic.handle*KeyDown(...); // keyboard support
60
+ ```
61
+
62
+ ## Verified factories
63
+
64
+ `createButton`, `createAccordion`, `createTabs`, `createDropdown`,
65
+ `createModal`, `createTooltip`, `createDrawer`, `createAlert`, `createNavbar`,
66
+ `createSidebar`, `createBreadcrumb`, `createMenuList`, `createContextMenu`, and
67
+ `createToastManager` (**not** `createToast`).
68
+
69
+ ## Core usage examples
70
+
71
+ ### Tabs
72
+
73
+ ```ts
74
+ import { createTabs } from '@andersseen/headless-components/tabs';
75
+
76
+ const tabs = createTabs({
77
+ defaultValue: 'overview',
78
+ orientation: 'horizontal',
79
+ activationMode: 'automatic',
80
+ onValueChange: tabId => console.log(tabId),
81
+ });
82
+
83
+ const listProps = tabs.getTabListProps();
84
+ const triggerProps = tabs.getTabTriggerProps('overview');
85
+ const panelProps = tabs.getTabContentProps('overview');
86
+
87
+ tabs.handleTabKeyDown(event, 'overview', ['overview', 'settings']);
88
+ ```
89
+
90
+ ### Dropdown
91
+
92
+ ```ts
93
+ import { createDropdown } from '@andersseen/headless-components/dropdown';
94
+
95
+ const dropdown = createDropdown({
96
+ defaultOpen: false,
97
+ closeOnSelect: true,
98
+ placement: 'bottom',
99
+ onOpenChange: open => console.log(open),
100
+ });
101
+
102
+ const triggerProps = dropdown.getTriggerProps();
103
+ const menuProps = dropdown.getMenuProps();
104
+ const itemProps = dropdown.getItemProps('theme-light');
105
+
106
+ dropdown.handleTriggerKeyDown(event);
107
+ dropdown.handleMenuKeyDown(event, ['theme-light', 'theme-dark']);
108
+ ```
109
+
110
+ ### Modal
111
+
112
+ ```ts
113
+ import { createModal } from '@andersseen/headless-components/modal';
114
+
115
+ const modal = createModal({
116
+ closeOnEscape: true,
117
+ closeOnOverlayClick: true,
118
+ onOpenChange: open => console.log(open),
119
+ });
120
+
121
+ modal.actions.open();
122
+ const overlayProps = modal.getOverlayProps();
123
+ const contentProps = modal.getContentProps();
124
+ const closeBtnProps = modal.getCloseButtonProps();
125
+
126
+ window.addEventListener('keydown', modal.handleKeyDown);
127
+ ```
128
+
129
+ ### Tooltip
130
+
131
+ ```ts
132
+ import { createTooltip } from '@andersseen/headless-components/tooltip';
133
+
134
+ const tooltip = createTooltip({
135
+ placement: 'top',
136
+ openDelay: 120,
137
+ closeDelay: 80,
138
+ onVisibilityChange: visible => console.log(visible),
139
+ });
140
+
141
+ trigger.addEventListener('mouseenter', tooltip.handleMouseEnter);
142
+ trigger.addEventListener('mouseleave', tooltip.handleMouseLeave);
143
+ trigger.addEventListener('focusin', tooltip.handleFocusIn);
144
+ trigger.addEventListener('focusout', tooltip.handleFocusOut);
145
+
146
+ const triggerProps = tooltip.getTriggerProps();
147
+ const tooltipProps = tooltip.getTooltipProps();
148
+ ```
149
+
150
+ ### Toast manager
151
+
152
+ ```ts
153
+ import { createToastManager } from '@andersseen/headless-components/toast';
154
+
155
+ const toasts = createToastManager({
156
+ defaultDuration: 3000,
157
+ maxToasts: 5,
158
+ position: 'bottom-right',
159
+ onToastsChange: list => console.log(list),
160
+ });
161
+
162
+ const id = toasts.actions.present('Saved', 'success');
163
+ toasts.actions.dismiss(id);
164
+ toasts.actions.dismissAll();
165
+
166
+ const containerProps = toasts.getContainerProps();
167
+ const dismissProps = toasts.getDismissProps();
168
+ ```
169
+
170
+ ## Rules
171
+
172
+ - Use the real package name: `@andersseen/headless-components`.
173
+ - Do not invent rendering helpers; compose with semantic HTML and spread the
174
+ returned props objects onto your elements.
175
+ - Preserve the provided keyboard handlers and ARIA attributes — do not
176
+ re-implement focus/state logic already provided by `actions`/`queries`.
177
+ - Call `destroy()` where provided (tooltip / toast manager) during unmount
178
+ cleanup.