@archetypeai/ds-cli 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/commands/create.js +2 -2
- package/commands/init.js +2 -2
- package/files/AGENTS.md +128 -70
- package/files/CLAUDE.md +128 -70
- package/files/ds-manifest.json +998 -1023
- package/lib/add-ds-config-codeagent.js +3 -57
- package/package.json +2 -2
- package/files/LICENSE +0 -21
- package/files/rules/accessibility.md +0 -268
- package/files/rules/charts.md +0 -256
- package/files/rules/components.md +0 -251
- package/files/rules/design-principles.md +0 -71
- package/files/rules/frontend-architecture.md +0 -86
- package/files/rules/linting.md +0 -31
- package/files/rules/state.md +0 -373
- package/files/rules/styling.md +0 -142
- package/files/skills/apply-ds/SKILL.md +0 -121
- package/files/skills/apply-ds/scripts/audit.sh +0 -169
- package/files/skills/apply-ds/scripts/setup.sh +0 -153
- package/files/skills/build-component/SKILL.md +0 -153
- package/files/skills/create-dashboard/SKILL.md +0 -220
- package/files/skills/deploy-worker/SKILL.md +0 -231
- package/files/skills/deploy-worker/references/wrangler-commands.md +0 -327
- package/files/skills/fix-accessibility/SKILL.md +0 -232
- package/files/skills/fix-metadata/SKILL.md +0 -118
- package/files/skills/fix-metadata/assets/favicon.ico +0 -0
- package/files/skills/setup-chart/SKILL.md +0 -223
- package/files/skills/setup-chart/data/embedding.csv +0 -42
- package/files/skills/setup-chart/data/timeseries.csv +0 -173
- package/files/skills/setup-chart/references/scatter-chart.md +0 -229
- package/files/skills/setup-chart/references/sensor-chart.md +0 -156
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, copyFileSync } from 'fs';
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import * as p from '@clack/prompts';
|
|
@@ -6,29 +6,6 @@ import * as p from '@clack/prompts';
|
|
|
6
6
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
7
|
const FILES_DIR = join(__dirname, '..', 'files');
|
|
8
8
|
|
|
9
|
-
function copyDir(src, dest) {
|
|
10
|
-
if (!existsSync(src)) return;
|
|
11
|
-
|
|
12
|
-
mkdirSync(dest, { recursive: true });
|
|
13
|
-
|
|
14
|
-
const entries = readdirSync(src, { withFileTypes: true });
|
|
15
|
-
for (const entry of entries) {
|
|
16
|
-
const srcPath = join(src, entry.name);
|
|
17
|
-
const destPath = join(dest, entry.name);
|
|
18
|
-
|
|
19
|
-
if (entry.isDirectory()) {
|
|
20
|
-
copyDir(srcPath, destPath);
|
|
21
|
-
} else {
|
|
22
|
-
if (existsSync(destPath)) {
|
|
23
|
-
p.log.warn(`Skipped (exists): ${destPath}`);
|
|
24
|
-
} else {
|
|
25
|
-
copyFileSync(srcPath, destPath);
|
|
26
|
-
p.log.success(`Copied: ${destPath}`);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
9
|
function copyFile(src, dest) {
|
|
33
10
|
if (!existsSync(src)) {
|
|
34
11
|
p.log.warn(`Source not found: ${src}`);
|
|
@@ -52,23 +29,7 @@ export function setupCursor(projectDir) {
|
|
|
52
29
|
copyFile(join(FILES_DIR, 'AGENTS.md'), join(projectDir, 'AGENTS.md'));
|
|
53
30
|
copyFile(join(FILES_DIR, 'ds-manifest.json'), join(projectDir, 'ds-manifest.json'));
|
|
54
31
|
|
|
55
|
-
// copy skills to .cursor/skills/
|
|
56
|
-
const skillsSrc = join(FILES_DIR, 'skills');
|
|
57
|
-
if (existsSync(skillsSrc)) {
|
|
58
|
-
copyDir(skillsSrc, join(projectDir, '.cursor', 'skills'));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// copy rules to .cursor/rules/
|
|
62
|
-
const rulesSrc = join(FILES_DIR, 'rules');
|
|
63
|
-
if (existsSync(rulesSrc)) {
|
|
64
|
-
copyDir(rulesSrc, join(projectDir, '.cursor', 'rules'));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// copy LICENSE to .cursor/
|
|
68
|
-
copyFile(join(FILES_DIR, 'LICENSE'), join(projectDir, '.cursor', 'LICENSE'));
|
|
69
|
-
|
|
70
32
|
p.log.success('Cursor setup complete');
|
|
71
|
-
p.log.info('Tip: Reload Cursor window (Cmd+Shift+P > "Reload Window")');
|
|
72
33
|
}
|
|
73
34
|
|
|
74
35
|
export function setupClaude(projectDir) {
|
|
@@ -78,21 +39,6 @@ export function setupClaude(projectDir) {
|
|
|
78
39
|
copyFile(join(FILES_DIR, 'CLAUDE.md'), join(projectDir, 'CLAUDE.md'));
|
|
79
40
|
copyFile(join(FILES_DIR, 'ds-manifest.json'), join(projectDir, 'ds-manifest.json'));
|
|
80
41
|
|
|
81
|
-
// copy skills to .claude/skills/
|
|
82
|
-
const skillsSrc = join(FILES_DIR, 'skills');
|
|
83
|
-
if (existsSync(skillsSrc)) {
|
|
84
|
-
copyDir(skillsSrc, join(projectDir, '.claude', 'skills'));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// copy rules to .claude/rules/
|
|
88
|
-
const rulesSrc = join(FILES_DIR, 'rules');
|
|
89
|
-
if (existsSync(rulesSrc)) {
|
|
90
|
-
copyDir(rulesSrc, join(projectDir, '.claude', 'rules'));
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// copy LICENSE to .claude/
|
|
94
|
-
copyFile(join(FILES_DIR, 'LICENSE'), join(projectDir, '.claude', 'LICENSE'));
|
|
95
|
-
|
|
96
42
|
p.log.success('Claude Code setup complete');
|
|
97
43
|
}
|
|
98
44
|
|
|
@@ -130,8 +76,8 @@ Usage:
|
|
|
130
76
|
agent = await p.select({
|
|
131
77
|
message: 'Which agent configuration?',
|
|
132
78
|
options: [
|
|
133
|
-
{ value: 'cursor', label: 'Cursor', hint: 'AGENTS.md +
|
|
134
|
-
{ value: 'claude', label: 'Claude Code', hint: 'CLAUDE.md +
|
|
79
|
+
{ value: 'cursor', label: 'Cursor', hint: 'AGENTS.md + ds-manifest.json' },
|
|
80
|
+
{ value: 'claude', label: 'Claude Code', hint: 'CLAUDE.md + ds-manifest.json, default' }
|
|
135
81
|
]
|
|
136
82
|
});
|
|
137
83
|
if (p.isCancel(agent)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@archetypeai/ds-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Archetype AI Design System CLI Tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"@clack/prompts": "^0.10.0"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
|
-
"build": "rm -rf dist && mkdir -p dist/commands dist/lib dist/files && cp bin.js package.json ../LICENSE dist/ && cp commands/*.js dist/commands/ && cp lib/*.js dist/lib/ && cp
|
|
24
|
+
"build": "rm -rf dist && mkdir -p dist/commands dist/lib dist/files && cp bin.js package.json ../LICENSE dist/ && cp commands/*.js dist/commands/ && cp lib/*.js dist/lib/ && cp ../ds-config-codeagent/AGENTS.md ../ds-config-codeagent/CLAUDE.md ../ds-config-codeagent/ds-manifest.json dist/files/ && chmod +x dist/bin.js && cp ../docs/DS-CLI.md dist/README.md"
|
|
25
25
|
},
|
|
26
26
|
"repository": {
|
|
27
27
|
"type": "git",
|
package/files/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Archetype AI
|
|
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.
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
# Accessibility Rules
|
|
2
|
-
|
|
3
|
-
## Screen Reader Support
|
|
4
|
-
|
|
5
|
-
Use `sr-only` for text that should be accessible but visually hidden:
|
|
6
|
-
|
|
7
|
-
```svelte
|
|
8
|
-
<button>
|
|
9
|
-
<XIcon />
|
|
10
|
-
<span class="sr-only">Close</span>
|
|
11
|
-
</button>
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
The `sr-only` class hides content visually while keeping it accessible to screen readers.
|
|
15
|
-
|
|
16
|
-
## aria-label
|
|
17
|
-
|
|
18
|
-
### Icon-Only Buttons
|
|
19
|
-
|
|
20
|
-
Always provide `aria-label` for buttons that only contain an icon:
|
|
21
|
-
|
|
22
|
-
```svelte
|
|
23
|
-
<!-- Correct -->
|
|
24
|
-
<Button size="icon" aria-label="Search">
|
|
25
|
-
<SearchIcon />
|
|
26
|
-
</Button>
|
|
27
|
-
|
|
28
|
-
<!-- Wrong - no accessible name -->
|
|
29
|
-
<Button size="icon">
|
|
30
|
-
<SearchIcon />
|
|
31
|
-
</Button>
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### Interactive Groups
|
|
35
|
-
|
|
36
|
-
Provide `aria-label` for grouped interactive elements:
|
|
37
|
-
|
|
38
|
-
```svelte
|
|
39
|
-
<ButtonGroup.Root aria-label="Pagination">
|
|
40
|
-
<Button variant="outline" size="icon-sm" aria-label="Previous page">
|
|
41
|
-
<ChevronLeftIcon />
|
|
42
|
-
</Button>
|
|
43
|
-
<ButtonGroup.Text>Page 1 of 10</ButtonGroup.Text>
|
|
44
|
-
<Button variant="outline" size="icon-sm" aria-label="Next page">
|
|
45
|
-
<ChevronRightIcon />
|
|
46
|
-
</Button>
|
|
47
|
-
</ButtonGroup.Root>
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### Toggles
|
|
51
|
-
|
|
52
|
-
```svelte
|
|
53
|
-
<Toggle variant="outline" aria-label="Toggle bookmark">
|
|
54
|
-
<BookmarkIcon />
|
|
55
|
-
</Toggle>
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## aria-hidden
|
|
59
|
-
|
|
60
|
-
Use `aria-hidden="true"` for decorative elements that shouldn't be announced:
|
|
61
|
-
|
|
62
|
-
```svelte
|
|
63
|
-
<!-- Decorative icons next to text -->
|
|
64
|
-
<CardTitle>Sensor Status</CardTitle>
|
|
65
|
-
<Icon strokeWidth={1.25} class="text-muted-foreground size-6" aria-hidden="true" />
|
|
66
|
-
|
|
67
|
-
<!-- Icons in buttons WITH text labels -->
|
|
68
|
-
<Button>
|
|
69
|
-
<PlusIcon aria-hidden="true" />
|
|
70
|
-
Add Item
|
|
71
|
-
</Button>
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
Do NOT use `aria-hidden` on icon-only buttons - use `aria-label` instead.
|
|
75
|
-
|
|
76
|
-
## role Attributes
|
|
77
|
-
|
|
78
|
-
### Lists
|
|
79
|
-
|
|
80
|
-
```svelte
|
|
81
|
-
<div role="list" data-slot="item-group">
|
|
82
|
-
{#each items as item}
|
|
83
|
-
<div role="listitem">{item}</div>
|
|
84
|
-
{/each}
|
|
85
|
-
</div>
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Groups
|
|
89
|
-
|
|
90
|
-
```svelte
|
|
91
|
-
<!-- Input groups -->
|
|
92
|
-
<div role="group" data-slot="input-group">
|
|
93
|
-
<Input />
|
|
94
|
-
<Button>Submit</Button>
|
|
95
|
-
</div>
|
|
96
|
-
|
|
97
|
-
<!-- Button groups -->
|
|
98
|
-
<div role="group" aria-label="Text formatting" data-slot="button-group">
|
|
99
|
-
<Button>Bold</Button>
|
|
100
|
-
<Button>Italic</Button>
|
|
101
|
-
</div>
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Status
|
|
105
|
-
|
|
106
|
-
```svelte
|
|
107
|
-
<!-- Spinners/loading indicators -->
|
|
108
|
-
<svg role="status" aria-label="Loading" class="animate-spin"> ... </svg>
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## aria-invalid Styling
|
|
112
|
-
|
|
113
|
-
Components use `aria-invalid` for validation states. The styling pattern:
|
|
114
|
-
|
|
115
|
-
```svelte
|
|
116
|
-
<input
|
|
117
|
-
class={cn(
|
|
118
|
-
'border-input focus-visible:ring-ring/50',
|
|
119
|
-
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',
|
|
120
|
-
'aria-invalid:border-destructive'
|
|
121
|
-
)}
|
|
122
|
-
aria-invalid={hasError}
|
|
123
|
-
/>
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
This applies to: inputs, textareas, selects, checkboxes, buttons, badges.
|
|
127
|
-
|
|
128
|
-
To trigger invalid styling, set `aria-invalid="true"`:
|
|
129
|
-
|
|
130
|
-
```svelte
|
|
131
|
-
<Input aria-invalid={errors.email ? true : undefined} />
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
## Focus Management
|
|
135
|
-
|
|
136
|
-
### Focus Ring Pattern
|
|
137
|
-
|
|
138
|
-
All interactive elements use this focus pattern:
|
|
139
|
-
|
|
140
|
-
```svelte
|
|
141
|
-
<button
|
|
142
|
-
class={cn(
|
|
143
|
-
'outline-none',
|
|
144
|
-
'focus-visible:border-ring',
|
|
145
|
-
'focus-visible:ring-ring/50',
|
|
146
|
-
'focus-visible:ring-[3px]'
|
|
147
|
-
)}
|
|
148
|
-
>
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Key points:
|
|
152
|
-
|
|
153
|
-
- `outline-none` removes default browser outline
|
|
154
|
-
- `focus-visible` only shows on keyboard focus (not mouse clicks)
|
|
155
|
-
- `ring-[3px]` provides visible focus indicator
|
|
156
|
-
- `ring-ring/50` uses semantic ring color at 50% opacity
|
|
157
|
-
|
|
158
|
-
### Disabled States
|
|
159
|
-
|
|
160
|
-
```svelte
|
|
161
|
-
<button
|
|
162
|
-
class="disabled:pointer-events-none disabled:opacity-50"
|
|
163
|
-
disabled={isDisabled}
|
|
164
|
-
>
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
For links styled as buttons (can't use `disabled` attribute):
|
|
168
|
-
|
|
169
|
-
```svelte
|
|
170
|
-
<a
|
|
171
|
-
href={disabled ? undefined : href}
|
|
172
|
-
aria-disabled={disabled}
|
|
173
|
-
role={disabled ? 'button' : undefined}
|
|
174
|
-
tabindex={disabled ? -1 : undefined}
|
|
175
|
-
class="aria-disabled:pointer-events-none aria-disabled:opacity-50"
|
|
176
|
-
>
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
## Keyboard Navigation
|
|
180
|
-
|
|
181
|
-
### bits-ui Primitives
|
|
182
|
-
|
|
183
|
-
Most keyboard navigation is handled automatically by bits-ui:
|
|
184
|
-
|
|
185
|
-
- Dialog: Escape to close, focus trap
|
|
186
|
-
- Select: Arrow keys to navigate, Enter to select
|
|
187
|
-
- Command: Arrow keys, Enter, type to filter
|
|
188
|
-
- Popover: Escape to close
|
|
189
|
-
|
|
190
|
-
### Custom Components
|
|
191
|
-
|
|
192
|
-
For custom interactive elements, ensure:
|
|
193
|
-
|
|
194
|
-
```svelte
|
|
195
|
-
<div
|
|
196
|
-
role="button"
|
|
197
|
-
tabindex="0"
|
|
198
|
-
onkeydown={(e) => {
|
|
199
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
200
|
-
e.preventDefault();
|
|
201
|
-
handleClick();
|
|
202
|
-
}
|
|
203
|
-
}}
|
|
204
|
-
onclick={handleClick}
|
|
205
|
-
>
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
## Page Structure
|
|
209
|
-
|
|
210
|
-
### Skip Link
|
|
211
|
-
|
|
212
|
-
Every page should have a skip link as the first focusable element:
|
|
213
|
-
|
|
214
|
-
```svelte
|
|
215
|
-
<a
|
|
216
|
-
href="#main-content"
|
|
217
|
-
class="sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-50 focus:rounded-md focus:bg-background focus:px-4 focus:py-2 focus:text-foreground focus:ring-2 focus:ring-ring"
|
|
218
|
-
>
|
|
219
|
-
Skip to content
|
|
220
|
-
</a>
|
|
221
|
-
|
|
222
|
-
<main id="main-content">
|
|
223
|
-
<!-- page content -->
|
|
224
|
-
</main>
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### Semantic Landmarks
|
|
228
|
-
|
|
229
|
-
Use semantic HTML elements instead of generic `<div>` wrappers:
|
|
230
|
-
|
|
231
|
-
```svelte
|
|
232
|
-
<!-- Before -->
|
|
233
|
-
<div class="header">...</div>
|
|
234
|
-
<div class="nav">...</div>
|
|
235
|
-
<div class="content">...</div>
|
|
236
|
-
|
|
237
|
-
<!-- After -->
|
|
238
|
-
<header>...</header>
|
|
239
|
-
<nav>...</nav>
|
|
240
|
-
<main id="main-content">...</main>
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
### Heading Hierarchy
|
|
244
|
-
|
|
245
|
-
Every page needs an `<h1>`. If the visual design doesn't include one, add it as screen-reader-only:
|
|
246
|
-
|
|
247
|
-
```svelte
|
|
248
|
-
<h1 class="sr-only">Dashboard</h1>
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
Never skip heading levels (e.g. `<h1>` → `<h3>`). Use the correct level for the document outline.
|
|
252
|
-
|
|
253
|
-
## Checklist
|
|
254
|
-
|
|
255
|
-
When building components, verify:
|
|
256
|
-
|
|
257
|
-
- [ ] Page has a skip-to-content link as the first focusable element
|
|
258
|
-
- [ ] Page uses semantic landmarks (`<main>`, `<header>`, `<nav>`)
|
|
259
|
-
- [ ] Page has an `<h1>` (visible or `sr-only`)
|
|
260
|
-
- [ ] Heading hierarchy doesn't skip levels
|
|
261
|
-
- [ ] Icon-only buttons have `aria-label`
|
|
262
|
-
- [ ] Decorative icons have `aria-hidden="true"`
|
|
263
|
-
- [ ] Interactive groups have `aria-label`
|
|
264
|
-
- [ ] Form inputs support `aria-invalid` styling
|
|
265
|
-
- [ ] Focus states are visible (`focus-visible:ring-*`)
|
|
266
|
-
- [ ] Disabled states prevent interaction and reduce opacity
|
|
267
|
-
- [ ] Loading states use `role="status"`
|
|
268
|
-
- [ ] Lists use `role="list"` and `role="listitem"`
|
package/files/rules/charts.md
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
paths:
|
|
3
|
-
- '**/chart*/**'
|
|
4
|
-
- '**/*Chart*.svelte'
|
|
5
|
-
- '**/*chart*.svelte'
|
|
6
|
-
- '**/dashboard*/**'
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Chart Rules
|
|
10
|
-
|
|
11
|
-
## Stack
|
|
12
|
-
|
|
13
|
-
- **layerchart** - Svelte-native charting library
|
|
14
|
-
- **d3-scale** - Scale functions (scaleUtc, scaleLinear, scaleOrdinal)
|
|
15
|
-
- **d3-shape** - Curve functions (curveNatural, curveMonotoneX)
|
|
16
|
-
- **Chart primitives** - Chart.Container, Chart.Tooltip from the design system
|
|
17
|
-
|
|
18
|
-
## Chart.Container
|
|
19
|
-
|
|
20
|
-
Wrap all charts in Chart.Container with a config prop:
|
|
21
|
-
|
|
22
|
-
```svelte
|
|
23
|
-
<script>
|
|
24
|
-
import * as Chart from '@archetypeai/ds-ui-svelte-labs/primitives/chart';
|
|
25
|
-
|
|
26
|
-
const chartConfig = {
|
|
27
|
-
temperature: { label: 'Temperature', color: 'var(--chart-1)' },
|
|
28
|
-
humidity: { label: 'Humidity', color: 'var(--chart-2)' }
|
|
29
|
-
};
|
|
30
|
-
</script>
|
|
31
|
-
|
|
32
|
-
<Chart.Container config={chartConfig} class="aspect-auto h-[220px] w-full">
|
|
33
|
-
<LineChart ... />
|
|
34
|
-
</Chart.Container>
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
The config maps data keys to labels and colors for tooltips and legends.
|
|
38
|
-
|
|
39
|
-
## Chart Token Colors
|
|
40
|
-
|
|
41
|
-
Use semantic chart colors from the theme:
|
|
42
|
-
|
|
43
|
-
```javascript
|
|
44
|
-
const chartColors = [
|
|
45
|
-
'var(--chart-1)', // purple
|
|
46
|
-
'var(--chart-2)', // red-orange
|
|
47
|
-
'var(--chart-3)', // green
|
|
48
|
-
'var(--chart-4)', // yellow
|
|
49
|
-
'var(--chart-5)' // coral
|
|
50
|
-
];
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
In series config:
|
|
54
|
-
|
|
55
|
-
```javascript
|
|
56
|
-
let series = $derived(
|
|
57
|
-
Object.entries(signals).map(([key, label], i) => ({
|
|
58
|
-
key,
|
|
59
|
-
label,
|
|
60
|
-
color: chartColors[i % chartColors.length]
|
|
61
|
-
}))
|
|
62
|
-
);
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## LineChart Pattern
|
|
66
|
-
|
|
67
|
-
Basic line chart with layerchart:
|
|
68
|
-
|
|
69
|
-
```svelte
|
|
70
|
-
<script>
|
|
71
|
-
import { LineChart } from 'layerchart';
|
|
72
|
-
import { scaleUtc, scaleLinear } from 'd3-scale';
|
|
73
|
-
import { curveNatural } from 'd3-shape';
|
|
74
|
-
|
|
75
|
-
let { data, xKey = 'timestamp', yMin, yMax } = $props();
|
|
76
|
-
|
|
77
|
-
const series = [{ key: 'value', label: 'Value', color: 'var(--chart-1)' }];
|
|
78
|
-
</script>
|
|
79
|
-
|
|
80
|
-
<LineChart
|
|
81
|
-
{data}
|
|
82
|
-
x={xKey}
|
|
83
|
-
xScale={scaleUtc()}
|
|
84
|
-
yScale={scaleLinear()}
|
|
85
|
-
yDomain={[yMin, yMax]}
|
|
86
|
-
{series}
|
|
87
|
-
props={{
|
|
88
|
-
spline: {
|
|
89
|
-
curve: curveNatural,
|
|
90
|
-
strokeWidth: 1.5
|
|
91
|
-
}
|
|
92
|
-
}}
|
|
93
|
-
/>
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## D3 Scale Usage
|
|
97
|
-
|
|
98
|
-
### Time Scale (x-axis with dates)
|
|
99
|
-
|
|
100
|
-
```javascript
|
|
101
|
-
import { scaleUtc } from 'd3-scale';
|
|
102
|
-
|
|
103
|
-
xScale={scaleUtc()}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### Linear Scale (numeric values)
|
|
107
|
-
|
|
108
|
-
```javascript
|
|
109
|
-
import { scaleLinear } from 'd3-scale';
|
|
110
|
-
|
|
111
|
-
yScale={scaleLinear()}
|
|
112
|
-
yDomain={[0, 100]} // fixed domain
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Dynamic Domain
|
|
116
|
-
|
|
117
|
-
```javascript
|
|
118
|
-
// Let layerchart calculate domain from data
|
|
119
|
-
yDomain = { undefined };
|
|
120
|
-
|
|
121
|
-
// Or calculate manually
|
|
122
|
-
let yDomain = $derived([
|
|
123
|
-
Math.min(...data.map((d) => d.value)),
|
|
124
|
-
Math.max(...data.map((d) => d.value))
|
|
125
|
-
]);
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Streaming Data Pattern
|
|
129
|
-
|
|
130
|
-
For real-time charts with a sliding window:
|
|
131
|
-
|
|
132
|
-
```svelte
|
|
133
|
-
<script>
|
|
134
|
-
let {
|
|
135
|
-
data = [],
|
|
136
|
-
maxPoints = 100, // sliding window size
|
|
137
|
-
...
|
|
138
|
-
} = $props();
|
|
139
|
-
|
|
140
|
-
// Slice to maxPoints for display
|
|
141
|
-
let displayData = $derived(
|
|
142
|
-
maxPoints && data.length > maxPoints
|
|
143
|
-
? data.slice(-maxPoints)
|
|
144
|
-
: data
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
// Add index for stable x positioning
|
|
148
|
-
let indexedData = $derived(
|
|
149
|
-
displayData.map((d, i) => ({ ...d, _index: i }))
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
// Use index-based x scale for streaming
|
|
153
|
-
let useIndexX = $derived(maxPoints !== undefined);
|
|
154
|
-
let xDomain = $derived(
|
|
155
|
-
useIndexX ? [0, (maxPoints || displayData.length) - 1] : undefined
|
|
156
|
-
);
|
|
157
|
-
</script>
|
|
158
|
-
|
|
159
|
-
<LineChart
|
|
160
|
-
data={indexedData}
|
|
161
|
-
x={useIndexX ? '_index' : xKey}
|
|
162
|
-
xScale={useIndexX ? scaleLinear() : scaleUtc()}
|
|
163
|
-
{xDomain}
|
|
164
|
-
...
|
|
165
|
-
/>
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## Axis Configuration
|
|
169
|
-
|
|
170
|
-
```svelte
|
|
171
|
-
<LineChart
|
|
172
|
-
axis="both" // "both" | "x" | "y" | "none"
|
|
173
|
-
props={{
|
|
174
|
-
xAxis: {
|
|
175
|
-
format: (date) => formatTime(date) // custom formatter
|
|
176
|
-
},
|
|
177
|
-
yAxis: {
|
|
178
|
-
ticks: [0, 25, 50, 75, 100], // explicit tick values
|
|
179
|
-
format: (v) => `${v}%`
|
|
180
|
-
},
|
|
181
|
-
grid: {
|
|
182
|
-
y: true,
|
|
183
|
-
x: false,
|
|
184
|
-
yTicks: [0, 25, 50, 75, 100]
|
|
185
|
-
}
|
|
186
|
-
}}
|
|
187
|
-
/>
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
## Time Formatting
|
|
191
|
-
|
|
192
|
-
Relative time (MM:SS from start):
|
|
193
|
-
|
|
194
|
-
```javascript
|
|
195
|
-
let baseTimestamp = $derived(
|
|
196
|
-
displayData.length > 0 && displayData[0][xKey] ? new Date(displayData[0][xKey]).getTime() : 0
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
function formatTime(date) {
|
|
200
|
-
if (!baseTimestamp) return '00:00';
|
|
201
|
-
const diffMs = date.getTime() - baseTimestamp;
|
|
202
|
-
const totalSeconds = Math.floor(diffMs / 1000);
|
|
203
|
-
const minutes = Math.floor(totalSeconds / 60);
|
|
204
|
-
const seconds = totalSeconds % 60;
|
|
205
|
-
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
|
206
|
-
}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
## Curve Types
|
|
210
|
-
|
|
211
|
-
```javascript
|
|
212
|
-
import { curveNatural, curveMonotoneX, curveStep, curveLinear } from 'd3-shape';
|
|
213
|
-
|
|
214
|
-
props={{
|
|
215
|
-
spline: {
|
|
216
|
-
curve: curveNatural, // smooth natural curve
|
|
217
|
-
// curve: curveMonotoneX, // monotonic (no overshooting)
|
|
218
|
-
// curve: curveStep, // stepped line
|
|
219
|
-
// curve: curveLinear, // straight lines
|
|
220
|
-
strokeWidth: 1.5
|
|
221
|
-
}
|
|
222
|
-
}}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
## Tooltip Integration
|
|
226
|
-
|
|
227
|
-
```svelte
|
|
228
|
-
<LineChart
|
|
229
|
-
tooltip={false} // disable default tooltip
|
|
230
|
-
...
|
|
231
|
-
/>
|
|
232
|
-
|
|
233
|
-
<!-- Or use custom Chart.Tooltip -->
|
|
234
|
-
<Chart.Container config={chartConfig}>
|
|
235
|
-
<LineChart tooltip={{ ... }} />
|
|
236
|
-
</Chart.Container>
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
## Legend Pattern
|
|
240
|
-
|
|
241
|
-
Manual legend below chart:
|
|
242
|
-
|
|
243
|
-
```svelte
|
|
244
|
-
{#if series.length > 0}
|
|
245
|
-
<div class="flex items-center justify-center gap-10">
|
|
246
|
-
{#each series as s (s.key)}
|
|
247
|
-
<div class="flex items-center gap-2">
|
|
248
|
-
<div class="size-2 rounded-full bg-(--legend-color)" style:--legend-color={s.color}></div>
|
|
249
|
-
<span class="text-foreground text-sm">{s.label}</span>
|
|
250
|
-
</div>
|
|
251
|
-
{/each}
|
|
252
|
-
</div>
|
|
253
|
-
{/if}
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
For a complete chart example, see the `sensor-chart` component (import from `@archetypeai/ds-ui-svelte-labs/primitives/sensor-chart`; editable source at https://design-system-labs.archetypeai.workers.dev/r/sensor-chart.json).
|