@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 +21 -0
- package/README.md +72 -0
- package/bin/cli.mjs +264 -0
- package/package.json +38 -0
- package/skills/andersseen/SKILL.md +93 -0
- package/skills/andersseen-headless-core/SKILL.md +178 -0
- package/skills/andersseen-icon/SKILL.md +124 -0
- package/skills/andersseen-layout/SKILL.md +198 -0
- package/skills/andersseen-motion/SKILL.md +166 -0
- package/skills/andersseen-web-components/SKILL.md +478 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: andersseen-web-components
|
|
3
|
+
description:
|
|
4
|
+
'Use ready-made, styled Andersseen UI custom elements
|
|
5
|
+
(@andersseen/web-components). Load when placing or wiring and-* elements —
|
|
6
|
+
and-button, and-card, and-modal, and-tabs, and-drawer, and-dropdown,
|
|
7
|
+
and-select, and-input, and-navbar, and-sidebar, and-toast, and-tooltip,
|
|
8
|
+
and-accordion, and-alert, and-badge, and-breadcrumb, and-carousel,
|
|
9
|
+
and-pagination — in plain HTML, Angular, React, Vue, or Astro. Trigger
|
|
10
|
+
phrases: and-* component, andersseen web component, stencil custom element,
|
|
11
|
+
shadow dom UI, design tokens.'
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# @andersseen/web-components — styled UI custom elements
|
|
15
|
+
|
|
16
|
+
Stencil custom-elements package. All tags share the `and-*` prefix and render in
|
|
17
|
+
**Shadow DOM** — never depend on internal shadow markup or classes. Built on top
|
|
18
|
+
of `@andersseen/headless-core` and `@andersseen/icon`. Framework-agnostic: works
|
|
19
|
+
in plain HTML, Angular, React, Vue, Astro.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm i @andersseen/web-components @andersseen/icon
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Setup (bundled app)
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
// Register all custom elements
|
|
31
|
+
import '@andersseen/web-components/components/all';
|
|
32
|
+
import '@andersseen/web-components/dist/web-components/web-components.css';
|
|
33
|
+
|
|
34
|
+
// Register icons used by internal components (minimum required)
|
|
35
|
+
import { registerIcons, COMPONENT_ICONS } from '@andersseen/icon';
|
|
36
|
+
registerIcons(COMPONENT_ICONS);
|
|
37
|
+
|
|
38
|
+
// Optional: enable CSS transitions across all components
|
|
39
|
+
import { enableAnimations } from '@andersseen/web-components';
|
|
40
|
+
enableAnimations();
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Design tokens
|
|
44
|
+
|
|
45
|
+
All tokens use HSL and are applied via CSS variables on `:root`. Dark mode: add
|
|
46
|
+
class `dark` (or `data-theme="dark"`) to `<html>` or any ancestor.
|
|
47
|
+
|
|
48
|
+
Key semantic tokens:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
--primary / --primary-foreground --secondary / --secondary-foreground
|
|
52
|
+
--background / --foreground --card / --card-foreground
|
|
53
|
+
--muted / --muted-foreground --accent / --accent-foreground
|
|
54
|
+
--destructive / --destructive-foreground
|
|
55
|
+
--border / --input / --ring
|
|
56
|
+
--success / --warning / --info (each with a -foreground pair)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Prefer `hsl(var(--token))` over hardcoded colors when extending styles.
|
|
60
|
+
|
|
61
|
+
## Component contracts
|
|
62
|
+
|
|
63
|
+
### and-button
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
Props : variant (default|destructive|outline|secondary|ghost|link)
|
|
67
|
+
size (default|sm|lg|icon) type (button|submit|reset)
|
|
68
|
+
disabled loading href target rel customClass
|
|
69
|
+
Events : andButtonClick → MouseEvent
|
|
70
|
+
Slots : [start] label text [end]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<and-button variant="default">Save</and-button>
|
|
75
|
+
<and-button variant="outline" size="sm" loading>
|
|
76
|
+
<and-icon slot="start" name="sparkles" size="16"></and-icon>
|
|
77
|
+
Generate
|
|
78
|
+
</and-button>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### and-card
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
Props : variant (default|destructive|elevated|outline|ghost) padded customClass
|
|
85
|
+
Sub-components: and-card-header · and-card-title · and-card-description
|
|
86
|
+
and-card-content · and-card-footer
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```html
|
|
90
|
+
<and-card variant="elevated" padded>
|
|
91
|
+
<and-card-header>
|
|
92
|
+
<and-card-title>Title</and-card-title>
|
|
93
|
+
<and-card-description>Subtitle</and-card-description>
|
|
94
|
+
</and-card-header>
|
|
95
|
+
<and-card-content>Body content</and-card-content>
|
|
96
|
+
<and-card-footer>Footer</and-card-footer>
|
|
97
|
+
</and-card>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### and-accordion
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
Props : allowMultiple (bool) defaultValue (string[]) orientation disabled
|
|
104
|
+
Sub-components: and-accordion-item (value, disabled)
|
|
105
|
+
and-accordion-trigger (default slot → label)
|
|
106
|
+
and-accordion-content (default slot → body)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```html
|
|
110
|
+
<and-accordion allow-multiple>
|
|
111
|
+
<and-accordion-item value="q1">
|
|
112
|
+
<and-accordion-trigger>Question 1</and-accordion-trigger>
|
|
113
|
+
<and-accordion-content>Answer 1</and-accordion-content>
|
|
114
|
+
</and-accordion-item>
|
|
115
|
+
</and-accordion>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### and-alert
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
Props : variant (default|destructive|success|warning|info) dismissible
|
|
122
|
+
Events : andDismiss → void
|
|
123
|
+
Slots : [icon] body text
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```html
|
|
127
|
+
<and-alert variant="success" dismissible>
|
|
128
|
+
<and-icon slot="icon" name="check-circle" size="16"></and-icon>
|
|
129
|
+
Profile saved successfully.
|
|
130
|
+
</and-alert>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### and-badge
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Props : variant (default|secondary|destructive|outline) customClass
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<and-badge variant="secondary">Beta</and-badge>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### and-breadcrumb + and-breadcrumb-item
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
Props (container) : size (sm|md|lg) navLabel customClass
|
|
147
|
+
Props (item) : href current (bool) size hideSeparator customClass
|
|
148
|
+
Events (item) : andBreadcrumbNavigate → string (href)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```html
|
|
152
|
+
<and-breadcrumb>
|
|
153
|
+
<and-breadcrumb-item href="/">Home</and-breadcrumb-item>
|
|
154
|
+
<and-breadcrumb-item href="/products">Products</and-breadcrumb-item>
|
|
155
|
+
<and-breadcrumb-item current>Laptop</and-breadcrumb-item>
|
|
156
|
+
</and-breadcrumb>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### and-carousel + and-carousel-item
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
Props (container) : autoplay (bool) interval (ms) label (ARIA)
|
|
163
|
+
Events : andSlideChange → number (0-based index)
|
|
164
|
+
Props (item) : label (ARIA slide label)
|
|
165
|
+
Keyboard : ArrowLeft / ArrowRight
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```html
|
|
169
|
+
<and-carousel autoplay interval="5000">
|
|
170
|
+
<and-carousel-item label="Slide 1"
|
|
171
|
+
><img src="..." alt=""
|
|
172
|
+
/></and-carousel-item>
|
|
173
|
+
<and-carousel-item label="Slide 2"
|
|
174
|
+
><img src="..." alt=""
|
|
175
|
+
/></and-carousel-item>
|
|
176
|
+
</and-carousel>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### and-context-menu
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
Props : open (mutable bool) customClass
|
|
183
|
+
Events : andContextMenuOpenChange → boolean
|
|
184
|
+
Slots : [trigger] (right-click target) default (menu items)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```html
|
|
188
|
+
<and-context-menu>
|
|
189
|
+
<div slot="trigger">Right-click me</div>
|
|
190
|
+
<and-menu-item value="copy">Copy</and-menu-item>
|
|
191
|
+
<and-menu-item value="delete" intent="destructive">Delete</and-menu-item>
|
|
192
|
+
</and-context-menu>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### and-drawer
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
Props : open (mutable bool) placement (left|right|top|bottom) showClose (bool)
|
|
199
|
+
Events : andDrawerOpen · andDrawerClose
|
|
200
|
+
Slots : [header] [close-icon] default (body) [footer]
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
```html
|
|
204
|
+
<and-drawer id="nav" placement="right">
|
|
205
|
+
<span slot="header">Settings</span>
|
|
206
|
+
<p>Drawer body content</p>
|
|
207
|
+
</and-drawer>
|
|
208
|
+
<and-button onclick="document.getElementById('nav').open = true"
|
|
209
|
+
>Open</and-button
|
|
210
|
+
>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### and-dropdown
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
Props : items (DropdownItem[]) variant label placement (top|bottom|left|right) closeOnSelect
|
|
217
|
+
DropdownItem: { text: string; value: string; disabled?: boolean }
|
|
218
|
+
Events : andDropdownSelect → string (value) andDropdownOpenChange → boolean
|
|
219
|
+
Slots : [trigger] (custom trigger element)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
```html
|
|
223
|
+
<and-dropdown id="theme" label="Theme"></and-dropdown>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
const dd = document.querySelector('#theme');
|
|
228
|
+
dd.items = [
|
|
229
|
+
{ text: 'Light', value: 'light' },
|
|
230
|
+
{ text: 'Dark', value: 'dark' },
|
|
231
|
+
];
|
|
232
|
+
dd.addEventListener('andDropdownSelect', e => {
|
|
233
|
+
document.documentElement.className = e.detail;
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### and-icon
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
Props : name (IconName — must be registered) size (default 24) color (default currentColor) strokeWidth (default 2)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
```html
|
|
244
|
+
<and-icon name="home" size="20"></and-icon>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### and-input
|
|
248
|
+
|
|
249
|
+
Renders in **light DOM** (not Shadow DOM) — the real `<input>` is a descendant
|
|
250
|
+
of any wrapping `<form>`, so `FormData`, native validation, and autofill work
|
|
251
|
+
with no extra wiring. Set `name` for it to show up in `FormData`.
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
Props : value (mutable string) type name placeholder disabled required hasError label describedBy customClass
|
|
255
|
+
Events : andInputChange → string (current value) andInputBlur → void
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
```html
|
|
259
|
+
<and-input
|
|
260
|
+
id="email"
|
|
261
|
+
name="email"
|
|
262
|
+
type="email"
|
|
263
|
+
placeholder="you@example.com"
|
|
264
|
+
required
|
|
265
|
+
></and-input>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### and-select
|
|
269
|
+
|
|
270
|
+
Custom `role="combobox"` select — styleable unlike a native `<select>`, full
|
|
271
|
+
keyboard support (arrow keys, Home/End, typeahead). Also renders in light DOM:
|
|
272
|
+
set `name` and its hidden mirror input shows up in `FormData`.
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
Props : options (array or JSON string of {text,value,disabled?}) value (mutable string) name placeholder
|
|
276
|
+
disabled required hasError menuPlacement (auto|top|bottom) label describedBy customClass
|
|
277
|
+
Events : andSelectChange → string (selected value) andSelectBlur → void
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```html
|
|
281
|
+
<and-select name="country" placeholder="Choose a country"></and-select>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
document.querySelector('and-select').options = [{ text: 'Spain', value: 'es' }];
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### and-control
|
|
289
|
+
|
|
290
|
+
Generic form-field wrapper for anything and-input/and-select don't cover — it
|
|
291
|
+
renders no control of its own. Slot in a native `<textarea>`, native `<select>`,
|
|
292
|
+
a third-party widget, or and-input/and-select, and it wires the label's
|
|
293
|
+
`for`/`id` and the message's `aria-describedby` to the first slotted element
|
|
294
|
+
automatically.
|
|
295
|
+
|
|
296
|
+
```
|
|
297
|
+
Props : label hint error (shown instead of hint, sets aria-invalid on the control) required (visual only)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
```html
|
|
301
|
+
<and-control label="Bio" hint="Max 200 characters">
|
|
302
|
+
<textarea name="bio" maxlength="200"></textarea>
|
|
303
|
+
</and-control>
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### and-code
|
|
307
|
+
|
|
308
|
+
Read-only command/code snippet block with a copy-to-clipboard button. Plain text
|
|
309
|
+
only — no syntax highlighting; `prompt` is just a display hint. Prompt is hidden
|
|
310
|
+
unless `show-prompt` is set.
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
Props : value prompt (default `$`) showPrompt (default false) copyable (default true) height customClass
|
|
314
|
+
Events : andCodeCopy → { value: string; success: boolean }
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
```html
|
|
318
|
+
<and-code show-prompt="true" prompt="$" value="pnpm install"></and-code>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### and-menu-list + and-menu-item
|
|
322
|
+
|
|
323
|
+
```
|
|
324
|
+
Props (list) : ariaMenuLabel customClass
|
|
325
|
+
Props (item) : intent (default|destructive) disabled value customClass
|
|
326
|
+
Events (item): andMenuItemSelect → string (value)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
```html
|
|
330
|
+
<and-menu-list>
|
|
331
|
+
<and-menu-item value="edit">Edit</and-menu-item>
|
|
332
|
+
<and-menu-item value="delete" intent="destructive">Delete</and-menu-item>
|
|
333
|
+
</and-menu-list>
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### and-modal
|
|
337
|
+
|
|
338
|
+
```
|
|
339
|
+
Props : open (mutable bool)
|
|
340
|
+
Events : andClose → void (backdrop, X button, or Escape)
|
|
341
|
+
Slots : default (body — close button auto-rendered top-right)
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
```html
|
|
345
|
+
<and-modal id="confirm">
|
|
346
|
+
<h2>Are you sure?</h2>
|
|
347
|
+
<and-button onclick="document.getElementById('confirm').open = false"
|
|
348
|
+
>Cancel</and-button
|
|
349
|
+
>
|
|
350
|
+
</and-modal>
|
|
351
|
+
<and-button onclick="document.getElementById('confirm').open = true"
|
|
352
|
+
>Open</and-button
|
|
353
|
+
>
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### and-navbar
|
|
357
|
+
|
|
358
|
+
```
|
|
359
|
+
Props : items (NavItem[] | JSON string) activeItem (mutable)
|
|
360
|
+
variant (default|filled|floating|glass) position (static|sticky|fixed)
|
|
361
|
+
itemVariant (default|underline|filled) scrollSpy scrollSpyOffset
|
|
362
|
+
mobileBreakpoint minimalBreakpoint compactBreakpoint
|
|
363
|
+
NavItem : { id, label, icon?, href?, target?, disabled? }
|
|
364
|
+
Events : navItemClick → string navLinkClick → { id, href }
|
|
365
|
+
mobileMenuChange → boolean responsiveStageChange → stage
|
|
366
|
+
Slots : [start/brand] [main/nav] [end/actions] [toggle-icon] [mobile-title] [mobile-nav] [mobile-actions]
|
|
367
|
+
Responsive stages (auto): full → compact → minimal → mobile
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
const nav = document.getElementById('main-nav');
|
|
372
|
+
nav.items = [
|
|
373
|
+
{ id: 'home', label: 'Home', href: '#home' },
|
|
374
|
+
{ id: 'docs', label: 'Docs', href: '#docs' },
|
|
375
|
+
];
|
|
376
|
+
nav.addEventListener('navItemClick', e => (nav.activeItem = e.detail));
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### and-pagination
|
|
380
|
+
|
|
381
|
+
```
|
|
382
|
+
Props : totalPages (number) currentPage (mutable number)
|
|
383
|
+
Events : andPageChange → number (1-based page)
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
```html
|
|
387
|
+
<and-pagination id="pager" total-pages="10" current-page="1"></and-pagination>
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### and-sidebar
|
|
391
|
+
|
|
392
|
+
```
|
|
393
|
+
Props : items (SidebarItem[] | JSON string) activeItem (mutable) collapsed (mutable bool)
|
|
394
|
+
variant (default|filled|floating|glass) itemVariant (default|underline|filled)
|
|
395
|
+
mobileCollapse mobileBreakpoint expandedWidth ('16rem') collapsedWidth ('4rem')
|
|
396
|
+
SidebarItem: { id, label, icon?, disabled?, section?: 'main'|'bottom' }
|
|
397
|
+
Events : andSidebarItemClick → string andSidebarToggle → boolean (collapsed)
|
|
398
|
+
Slots : [header] [top] [nav] [footer]
|
|
399
|
+
CSS vars : --sidebar-expanded-width, --sidebar-collapsed-width
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
const sidebar = document.getElementById('sidebar');
|
|
404
|
+
sidebar.items = [
|
|
405
|
+
{ id: 'home', label: 'Home', icon: 'home' },
|
|
406
|
+
{ id: 'settings', label: 'Settings', icon: 'settings', section: 'bottom' },
|
|
407
|
+
];
|
|
408
|
+
sidebar.addEventListener(
|
|
409
|
+
'andSidebarItemClick',
|
|
410
|
+
e => (sidebar.activeItem = e.detail),
|
|
411
|
+
);
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### and-tabs
|
|
415
|
+
|
|
416
|
+
```
|
|
417
|
+
Props : value (mutable — controlled) defaultValue (uncontrolled)
|
|
418
|
+
orientation (horizontal|vertical) activationMode (automatic|manual)
|
|
419
|
+
Events : andTabChange → string (selected value)
|
|
420
|
+
Sub-components: and-tabs-list → and-tabs-trigger (value, disabled) and-tabs-content (value)
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
```html
|
|
424
|
+
<and-tabs default-value="overview">
|
|
425
|
+
<and-tabs-list>
|
|
426
|
+
<and-tabs-trigger value="overview">Overview</and-tabs-trigger>
|
|
427
|
+
<and-tabs-trigger value="settings">Settings</and-tabs-trigger>
|
|
428
|
+
</and-tabs-list>
|
|
429
|
+
<and-tabs-content value="overview">Overview panel content</and-tabs-content>
|
|
430
|
+
<and-tabs-content value="settings">Settings panel content</and-tabs-content>
|
|
431
|
+
</and-tabs>
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### and-toast
|
|
435
|
+
|
|
436
|
+
Programmatic only (no props / slots).
|
|
437
|
+
|
|
438
|
+
```
|
|
439
|
+
Method : present(message, type?, duration?) → Promise<number>
|
|
440
|
+
type: 'default'|'destructive'|'success'|'error'|'info'|'warning'
|
|
441
|
+
duration: ms (default 3000)
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
```html
|
|
445
|
+
<and-toast id="toaster"></and-toast>
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
const toaster = document.getElementById('toaster');
|
|
450
|
+
await toaster.present('Profile saved', 'success');
|
|
451
|
+
await toaster.present('Connection failed', 'error', 5000);
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### and-tooltip
|
|
455
|
+
|
|
456
|
+
```
|
|
457
|
+
Props : content (string) placement (top|bottom|left|right) openDelay (ms) closeDelay (ms)
|
|
458
|
+
Slots : default (trigger element) [content] (when no content prop)
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
```html
|
|
462
|
+
<and-tooltip content="Copy to clipboard" placement="top">
|
|
463
|
+
<and-button size="icon"
|
|
464
|
+
><and-icon name="copy" size="16"></and-icon
|
|
465
|
+
></and-button>
|
|
466
|
+
</and-tooltip>
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
## Rules
|
|
470
|
+
|
|
471
|
+
- Use public props, slots, and CustomEvent APIs only. Never target internal
|
|
472
|
+
shadow parts.
|
|
473
|
+
- Prefer CSS token variables (e.g. `hsl(var(--primary))`) over hex/rgb values.
|
|
474
|
+
- Icon `name` must match a registered key; always pair with
|
|
475
|
+
`registerIcons`/`registerAllIcons` (see the `andersseen-icon` skill).
|
|
476
|
+
- For array-type props (`items`, `defaultValue`) set them via JS, not attribute
|
|
477
|
+
strings, when possible.
|
|
478
|
+
- Mutable props marked above can be set directly as element properties.
|