omnidesign 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/QUICKREF.md +150 -0
- package/README.md +576 -0
- package/bin/cli.js +390 -0
- package/bin/detect-ide.js +50 -0
- package/bin/install.js +8 -0
- package/logo.jpg +0 -0
- package/package.json +84 -0
- package/recipes/components/README.md +29 -0
- package/recipes/components/agent-card.md +314 -0
- package/recipes/components/ai-chat.md +252 -0
- package/recipes/components/bento-grid.md +186 -0
- package/recipes/components/code-block.md +503 -0
- package/recipes/components/file-upload.md +483 -0
- package/recipes/components/forms.md +238 -0
- package/recipes/components/hero-section.md +161 -0
- package/recipes/components/navbar.md +214 -0
- package/recipes/components/prompt-input.md +293 -0
- package/recipes/components/thinking-indicator.md +372 -0
- package/recipes/motion/README.md +3 -0
- package/recipes/motion/motion-system.md +437 -0
- package/recipes/patterns/README.md +3 -0
- package/skills/aider/omnidesign.md +67 -0
- package/skills/amp/SKILL.md +114 -0
- package/skills/antigravity/SKILL.md +114 -0
- package/skills/claude/omnidesign.md +111 -0
- package/skills/continue/omnidesign.yaml +29 -0
- package/skills/cursor/omnidesign.md +110 -0
- package/skills/kilo/SKILL.md +114 -0
- package/skills/opencode/omnidesign.md +110 -0
- package/skills/vscode/package.json +66 -0
- package/skills/zed/omnidesign.json +7 -0
- package/tokens/motion/README.md +3 -0
- package/tokens/primitives/README.md +3 -0
- package/tokens/primitives/color.json +219 -0
- package/tokens/primitives/motion.json +56 -0
- package/tokens/primitives/radii.json +37 -0
- package/tokens/primitives/shadows.json +34 -0
- package/tokens/primitives/spacing.json +67 -0
- package/tokens/primitives/typography.json +127 -0
- package/tokens/semantic/README.md +3 -0
- package/tokens/semantic/color.json +114 -0
- package/tokens/semantic/motion.json +44 -0
- package/tokens/semantic/radii.json +29 -0
- package/tokens/semantic/shadows.json +24 -0
- package/tokens/semantic/spacing.json +69 -0
- package/tokens/semantic/typography.json +118 -0
- package/tokens/shadows/README.md +3 -0
- package/tokens/themes/README.md +3 -0
- package/tokens/themes/berry.json +143 -0
- package/tokens/themes/brutalist.json +143 -0
- package/tokens/themes/coral.json +143 -0
- package/tokens/themes/corporate.json +143 -0
- package/tokens/themes/cream.json +143 -0
- package/tokens/themes/cyberpunk.json +143 -0
- package/tokens/themes/daylight.json +143 -0
- package/tokens/themes/deep-space.json +143 -0
- package/tokens/themes/forest.json +143 -0
- package/tokens/themes/graphite.json +143 -0
- package/tokens/themes/lavender.json +143 -0
- package/tokens/themes/midnight.json +143 -0
- package/tokens/themes/mint.json +143 -0
- package/tokens/themes/navy.json +143 -0
- package/tokens/themes/noir.json +143 -0
- package/tokens/themes/obsidian.json +143 -0
- package/tokens/themes/ocean.json +143 -0
- package/tokens/themes/paper.json +143 -0
- package/tokens/themes/ruby.json +143 -0
- package/tokens/themes/slate.json +143 -0
- package/tokens/themes/snow.json +143 -0
- package/tokens/themes/solar.json +143 -0
- package/tokens/themes/spring.json +143 -0
- package/tokens/themes/starry-night.json +143 -0
- package/tokens/themes/sunset.json +143 -0
- package/tokens/typography/FONT_GUIDE.md +381 -0
- package/tokens/typography/README.md +37 -0
- package/tokens/typography/font-collection.json +221 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# AI Agent Card
|
|
2
|
+
|
|
3
|
+
Display card for AI agents, models, or personas with capabilities and status.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
- Model selector interfaces
|
|
7
|
+
- AI marketplace/agent store
|
|
8
|
+
- Multi-agent systems
|
|
9
|
+
- Persona selection
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
15
|
+
│ AgentCard │
|
|
16
|
+
│ ├─ CardHeader (avatar, name, badge) │
|
|
17
|
+
│ ├─ CardBody (description, capabilities) │
|
|
18
|
+
│ ├─ CardFooter (status, actions) │
|
|
19
|
+
│ └─ CapabilityTags (skills, features) │
|
|
20
|
+
└─────────────────────────────────────────────────────────────┘
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Token Usage
|
|
24
|
+
|
|
25
|
+
```css
|
|
26
|
+
/* Card Container */
|
|
27
|
+
.agent-card {
|
|
28
|
+
background: var(--color-surface-raised);
|
|
29
|
+
border: 1px solid var(--color-border-default);
|
|
30
|
+
border-radius: var(--radius-xl);
|
|
31
|
+
padding: var(--spacing-card-padding);
|
|
32
|
+
transition: transform var(--duration-fast), box-shadow var(--duration-fast);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.agent-card:hover {
|
|
36
|
+
transform: translateY(-2px);
|
|
37
|
+
box-shadow: var(--shadow-card);
|
|
38
|
+
border-color: var(--color-border-strong);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.agent-card.selected {
|
|
42
|
+
border-color: var(--color-interactive-primary);
|
|
43
|
+
box-shadow: 0 0 0 2px var(--color-focus-ring);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Avatar */
|
|
47
|
+
.agent-avatar {
|
|
48
|
+
width: 48px;
|
|
49
|
+
height: 48px;
|
|
50
|
+
border-radius: var(--radius-full);
|
|
51
|
+
background: linear-gradient(135deg, var(--color-interactive-primary), var(--color-interactive-secondary));
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
font-weight: var(--font-weight-bold);
|
|
56
|
+
color: var(--color-text-inverted);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.agent-avatar img {
|
|
60
|
+
width: 100%;
|
|
61
|
+
height: 100%;
|
|
62
|
+
border-radius: var(--radius-full);
|
|
63
|
+
object-fit: cover;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Status Badge */
|
|
67
|
+
.status-badge {
|
|
68
|
+
font-size: var(--font-size-xs);
|
|
69
|
+
padding: var(--spacing-2xs) var(--spacing-xs);
|
|
70
|
+
border-radius: var(--radius-full);
|
|
71
|
+
font-weight: var(--font-weight-medium);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.status-badge.online {
|
|
75
|
+
background: rgba(34, 197, 94, 0.15);
|
|
76
|
+
color: #22C55E;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.status-badge.busy {
|
|
80
|
+
background: rgba(245, 158, 11, 0.15);
|
|
81
|
+
color: #F59E0B;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.status-badge.offline {
|
|
85
|
+
background: var(--color-surface-sunken);
|
|
86
|
+
color: var(--color-text-muted);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Capability Tags */
|
|
90
|
+
.capability-tag {
|
|
91
|
+
background: var(--color-surface-sunken);
|
|
92
|
+
border: 1px solid var(--color-border-subtle);
|
|
93
|
+
border-radius: var(--radius-sm);
|
|
94
|
+
padding: var(--spacing-2xs) var(--spacing-xs);
|
|
95
|
+
font-size: var(--font-size-xs);
|
|
96
|
+
color: var(--color-text-muted);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.capability-tag.highlight {
|
|
100
|
+
background: rgba(37, 99, 235, 0.1);
|
|
101
|
+
border-color: rgba(37, 99, 235, 0.3);
|
|
102
|
+
color: var(--color-interactive-primary);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Capability Icons */
|
|
106
|
+
.capability-icon {
|
|
107
|
+
width: 16px;
|
|
108
|
+
height: 16px;
|
|
109
|
+
color: var(--color-text-muted);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.capability-icon.active {
|
|
113
|
+
color: var(--color-interactive-primary);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## State Matrix
|
|
118
|
+
|
|
119
|
+
| Element | Default | Hover | Selected | Disabled |
|
|
120
|
+
|---------|---------|-------|----------|----------|
|
|
121
|
+
| Card | border-default | translateY(-2px) + shadow | border-primary + ring | opacity-50 |
|
|
122
|
+
| Avatar | gradient bg | - | ring-2 | grayscale |
|
|
123
|
+
| Status Badge | per status | - | - | muted |
|
|
124
|
+
| Capability Tag | sunken bg | border-strong | highlight | - |
|
|
125
|
+
| Select Button | ghost | subtle bg | primary | - |
|
|
126
|
+
|
|
127
|
+
## Accessibility
|
|
128
|
+
|
|
129
|
+
```html
|
|
130
|
+
<article
|
|
131
|
+
class="agent-card"
|
|
132
|
+
role="button"
|
|
133
|
+
tabindex="0"
|
|
134
|
+
aria-pressed={isSelected}
|
|
135
|
+
aria-label={`Select ${agent.name}`}
|
|
136
|
+
>
|
|
137
|
+
<header class="card-header">
|
|
138
|
+
<div class="agent-avatar" aria-hidden="true">
|
|
139
|
+
<img src={agent.avatar} alt="" />
|
|
140
|
+
</div>
|
|
141
|
+
<div class="agent-info">
|
|
142
|
+
<h3>{agent.name}</h3>
|
|
143
|
+
<span class="status-badge online" aria-label="Status: Online">
|
|
144
|
+
<span class="status-dot" aria-hidden="true"></span>
|
|
145
|
+
Online
|
|
146
|
+
</span>
|
|
147
|
+
</div>
|
|
148
|
+
</header>
|
|
149
|
+
|
|
150
|
+
<p class="agent-description">{agent.description}</p>
|
|
151
|
+
|
|
152
|
+
<div class="capabilities" role="list" aria-label="Capabilities">
|
|
153
|
+
{agent.capabilities.map(cap => (
|
|
154
|
+
<span key={cap} class="capability-tag" role="listitem">{cap}</span>
|
|
155
|
+
))}
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<footer class="card-footer">
|
|
159
|
+
<span class="model-info">{agent.model}</span>
|
|
160
|
+
<button aria-label={`Use ${agent.name}`}>Select</button>
|
|
161
|
+
</footer>
|
|
162
|
+
</article>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Example: Model Selector Grid
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
function ModelSelector({ models, selectedId, onSelect }) {
|
|
169
|
+
return (
|
|
170
|
+
<div className="model-grid" role="radiogroup" aria-label="Select AI model">
|
|
171
|
+
{models.map(model => (
|
|
172
|
+
<AgentCard
|
|
173
|
+
key={model.id}
|
|
174
|
+
model={model}
|
|
175
|
+
isSelected={model.id === selectedId}
|
|
176
|
+
onSelect={() => onSelect(model.id)}
|
|
177
|
+
/>
|
|
178
|
+
))}
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function AgentCard({ model, isSelected, onSelect }) {
|
|
184
|
+
return (
|
|
185
|
+
<article
|
|
186
|
+
className={`agent-card ${isSelected ? 'selected' : ''}`}
|
|
187
|
+
onClick={onSelect}
|
|
188
|
+
role="radio"
|
|
189
|
+
aria-checked={isSelected}
|
|
190
|
+
tabIndex={0}
|
|
191
|
+
onKeyDown={e => e.key === 'Enter' && onSelect()}
|
|
192
|
+
>
|
|
193
|
+
<header className="card-header">
|
|
194
|
+
<div className="agent-avatar">
|
|
195
|
+
{model.avatar ? (
|
|
196
|
+
<img src={model.avatar} alt="" />
|
|
197
|
+
) : (
|
|
198
|
+
model.name[0]
|
|
199
|
+
)}
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<div className="agent-meta">
|
|
203
|
+
<h3 className="agent-name">{model.name}</h3>
|
|
204
|
+
<span className={`status-badge ${model.status}`}>
|
|
205
|
+
<span className="status-dot"></span>
|
|
206
|
+
{model.status}
|
|
207
|
+
</span>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
{isSelected && <CheckIcon className="selected-icon" aria-hidden="true" />}
|
|
211
|
+
</header>
|
|
212
|
+
|
|
213
|
+
<p className="agent-description">{model.description}</p>
|
|
214
|
+
|
|
215
|
+
<div className="capability-list">
|
|
216
|
+
{model.capabilities.map(cap => (
|
|
217
|
+
<span key={cap} className="capability-tag">
|
|
218
|
+
{cap}
|
|
219
|
+
</span>
|
|
220
|
+
))}
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<footer className="card-footer">
|
|
224
|
+
<div className="stats">
|
|
225
|
+
<span title="Context window">{formatTokens(model.contextWindow)}</span>
|
|
226
|
+
<span title="Response time">~{model.avgResponseTime}s</span>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
<button className="select-btn">
|
|
230
|
+
{isSelected ? 'Selected' : 'Use Model'}
|
|
231
|
+
</button>
|
|
232
|
+
</footer>
|
|
233
|
+
</article>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Example: AI Agent Store
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
function AgentStore({ agents, onInstall }) {
|
|
242
|
+
const categories = groupByCategory(agents);
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<div className="agent-store">
|
|
246
|
+
{Object.entries(categories).map(([category, agents]) => (
|
|
247
|
+
<section key={category} className="agent-category">
|
|
248
|
+
<h2>{category}</h2>
|
|
249
|
+
<div className="agent-row">
|
|
250
|
+
{agents.map(agent => (
|
|
251
|
+
<AgentStoreCard
|
|
252
|
+
key={agent.id}
|
|
253
|
+
agent={agent}
|
|
254
|
+
onInstall={() => onInstall(agent.id)}
|
|
255
|
+
/>
|
|
256
|
+
))}
|
|
257
|
+
</div>
|
|
258
|
+
</section>
|
|
259
|
+
))}
|
|
260
|
+
</div>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function AgentStoreCard({ agent, onInstall }) {
|
|
265
|
+
return (
|
|
266
|
+
<article className="agent-card store-card">
|
|
267
|
+
<header className="card-header">
|
|
268
|
+
<div className="agent-avatar large">
|
|
269
|
+
<img src={agent.avatar} alt="" />
|
|
270
|
+
</div>
|
|
271
|
+
<div>
|
|
272
|
+
<h3>{agent.name}</h3>
|
|
273
|
+
<div className="rating">
|
|
274
|
+
<StarIcon />
|
|
275
|
+
<span>{agent.rating}</span>
|
|
276
|
+
<span className="installs">({formatNumber(agent.installs)} installs)</span>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
</header>
|
|
280
|
+
|
|
281
|
+
<p className="description">{agent.description}</p>
|
|
282
|
+
|
|
283
|
+
<div className="features">
|
|
284
|
+
{agent.features.map(feature => (
|
|
285
|
+
<div key={feature} className="feature-item">
|
|
286
|
+
<CheckIcon className="feature-icon" />
|
|
287
|
+
<span>{feature}</span>
|
|
288
|
+
</div>
|
|
289
|
+
))}
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
<footer className="card-footer">
|
|
293
|
+
<span className="price">{agent.price === 0 ? 'Free' : `$${agent.price}/mo`}</span>
|
|
294
|
+
<button
|
|
295
|
+
className="install-btn"
|
|
296
|
+
onClick={onInstall}
|
|
297
|
+
disabled={agent.installed}
|
|
298
|
+
>
|
|
299
|
+
{agent.installed ? 'Installed' : 'Install'}
|
|
300
|
+
</button>
|
|
301
|
+
</footer>
|
|
302
|
+
</article>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Tokens Used
|
|
308
|
+
|
|
309
|
+
- **color**: surface-raised, surface-sunken, interactive-primary, interactive-secondary, text-default, text-inverted, text-muted, border-default, border-strong, border-subtle, focus-ring
|
|
310
|
+
- **spacing**: 2xs, xs, card-padding
|
|
311
|
+
- **radii**: full, sm, xl
|
|
312
|
+
- **shadow**: card
|
|
313
|
+
- **typography**: size-xs, weight-medium, weight-bold
|
|
314
|
+
- **motion**: duration-fast
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# AI Chat Interface
|
|
2
|
+
|
|
3
|
+
Complete chat UI component for AI-powered applications (ChatGPT/Claude-style).
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
- AI assistant applications
|
|
7
|
+
- Customer support bots
|
|
8
|
+
- Code assistant interfaces
|
|
9
|
+
- Any conversational AI feature
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
15
|
+
│ ChatInterface │
|
|
16
|
+
│ ├─ ChatHeader (model selector, actions) │
|
|
17
|
+
│ ├─ ChatMessages (scrollable message list) │
|
|
18
|
+
│ │ ├─ UserMessage │
|
|
19
|
+
│ │ ├─ AssistantMessage │
|
|
20
|
+
│ │ │ ├─ MessageHeader (avatar, name, time) │
|
|
21
|
+
│ │ │ ├─ MessageContent (text, code, images) │
|
|
22
|
+
│ │ │ ├─ MessageActions (copy, regenerate, feedback) │
|
|
23
|
+
│ │ │ └─ ThinkingIndicator (streaming state) │
|
|
24
|
+
│ │ └─ SystemMessage (errors, notifications) │
|
|
25
|
+
│ ├─ ChatInput (composer with attachments) │
|
|
26
|
+
│ └─ SuggestedPrompts (quick-start prompts) │
|
|
27
|
+
└─────────────────────────────────────────────────────────────┘
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Token Usage
|
|
31
|
+
|
|
32
|
+
```css
|
|
33
|
+
/* Chat Container */
|
|
34
|
+
.chat-interface {
|
|
35
|
+
background: var(--color-surface-default);
|
|
36
|
+
border-radius: var(--radius-lg);
|
|
37
|
+
font-family: var(--font-sans);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Message Bubbles */
|
|
41
|
+
.user-message {
|
|
42
|
+
background: var(--color-interactive-primary);
|
|
43
|
+
color: var(--color-text-inverted);
|
|
44
|
+
border-radius: var(--radius-lg) var(--radius-lg) var(--radius-sm);
|
|
45
|
+
padding: var(--spacing-card-padding);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.assistant-message {
|
|
49
|
+
background: var(--color-surface-raised);
|
|
50
|
+
color: var(--color-text-default);
|
|
51
|
+
border: 1px solid var(--color-border-default);
|
|
52
|
+
border-radius: var(--radius-lg) var(--radius-lg) var(--radius-lg) var(--radius-sm);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Code Blocks */
|
|
56
|
+
.code-block {
|
|
57
|
+
background: var(--color-surface-sunken);
|
|
58
|
+
border: 1px solid var(--color-border-default);
|
|
59
|
+
border-radius: var(--radius-md);
|
|
60
|
+
font-family: var(--font-mono);
|
|
61
|
+
font-size: var(--font-size-sm);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Input Area */
|
|
65
|
+
.chat-input {
|
|
66
|
+
background: var(--color-surface-raised);
|
|
67
|
+
border: 1px solid var(--color-border-default);
|
|
68
|
+
border-radius: var(--radius-xl);
|
|
69
|
+
padding: var(--spacing-md);
|
|
70
|
+
box-shadow: var(--shadow-card);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Thinking Indicator */
|
|
74
|
+
.thinking {
|
|
75
|
+
color: var(--color-text-muted);
|
|
76
|
+
font-style: italic;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.thinking-dots::after {
|
|
80
|
+
content: '';
|
|
81
|
+
animation: dots 1.5s steps(4, end) infinite;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@keyframes dots {
|
|
85
|
+
0%, 20% { content: ''; }
|
|
86
|
+
40% { content: '.'; }
|
|
87
|
+
60% { content: '..'; }
|
|
88
|
+
80%, 100% { content: '...'; }
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## State Matrix
|
|
93
|
+
|
|
94
|
+
| Element | Default | Hover | Focus | Active | Loading |
|
|
95
|
+
|---------|---------|-------|-------|--------|---------|
|
|
96
|
+
| User Message | primary bg | - | - | - | - |
|
|
97
|
+
| Assistant Message | raised bg | border-strong | - | - | - |
|
|
98
|
+
| Send Button | primary | primary-hover | ring | pressed | spinner |
|
|
99
|
+
| Code Copy | subtle | default | - | - | checkmark |
|
|
100
|
+
| Regenerate | ghost | subtle bg | - | - | spinning |
|
|
101
|
+
| Feedback | ghost | icon color | - | selected | - |
|
|
102
|
+
| Input | raised | - | ring | - | disabled |
|
|
103
|
+
|
|
104
|
+
## Accessibility
|
|
105
|
+
|
|
106
|
+
```html
|
|
107
|
+
<!-- Semantic structure -->
|
|
108
|
+
<section aria-label="Chat conversation" role="log" aria-live="polite" aria-atomic="false">
|
|
109
|
+
<article aria-label="User message">
|
|
110
|
+
<header><span aria-label="You">User</span></header>
|
|
111
|
+
<div class="message-content"></div>
|
|
112
|
+
</article>
|
|
113
|
+
|
|
114
|
+
<article aria-label="Assistant message">
|
|
115
|
+
<header><span aria-label="AI Assistant">Claude</span></header>
|
|
116
|
+
<div class="message-content"></div>
|
|
117
|
+
<footer class="message-actions">
|
|
118
|
+
<button aria-label="Copy message">Copy</button>
|
|
119
|
+
<button aria-label="Thumbs up">👍</button>
|
|
120
|
+
<button aria-label="Thumbs down">👎</button>
|
|
121
|
+
</footer>
|
|
122
|
+
</article>
|
|
123
|
+
</section>
|
|
124
|
+
|
|
125
|
+
<!-- Screen reader announcements -->
|
|
126
|
+
<div aria-live="assertive" aria-atomic="true" class="sr-only">
|
|
127
|
+
Assistant is typing...
|
|
128
|
+
</div>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Responsive Behavior
|
|
132
|
+
|
|
133
|
+
```css
|
|
134
|
+
/* Mobile: Full width messages */
|
|
135
|
+
@media (max-width: 768px) {
|
|
136
|
+
.chat-interface {
|
|
137
|
+
border-radius: 0;
|
|
138
|
+
height: 100vh;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.message {
|
|
142
|
+
max-width: 90%;
|
|
143
|
+
margin: var(--spacing-sm);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.chat-input {
|
|
147
|
+
position: fixed;
|
|
148
|
+
bottom: 0;
|
|
149
|
+
left: 0;
|
|
150
|
+
right: 0;
|
|
151
|
+
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* Desktop: Centered with max-width */
|
|
156
|
+
@media (min-width: 1024px) {
|
|
157
|
+
.chat-messages {
|
|
158
|
+
max-width: 800px;
|
|
159
|
+
margin: 0 auto;
|
|
160
|
+
padding: var(--spacing-section-y);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Example: Basic Chat Message
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
function ChatMessage({ role, content, isStreaming }) {
|
|
169
|
+
return (
|
|
170
|
+
<article className={`message ${role}`}>
|
|
171
|
+
<header className="message-header">
|
|
172
|
+
<Avatar src={role === 'user' ? userAvatar : aiAvatar} />
|
|
173
|
+
<span className="sender-name">{role === 'user' ? 'You' : 'Assistant'}</span>
|
|
174
|
+
<time>{formatTime(timestamp)}</time>
|
|
175
|
+
</header>
|
|
176
|
+
|
|
177
|
+
<div className="message-content">
|
|
178
|
+
{isStreaming ? (
|
|
179
|
+
<span className="thinking">Thinking<span className="dots">...</span></span>
|
|
180
|
+
) : (
|
|
181
|
+
<Markdown content={content} />
|
|
182
|
+
)}
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{!isStreaming && role === 'assistant' && (
|
|
186
|
+
<footer className="message-actions">
|
|
187
|
+
<button aria-label="Copy" onClick={handleCopy}>
|
|
188
|
+
<CopyIcon />
|
|
189
|
+
</button>
|
|
190
|
+
<button aria-label="Regenerate" onClick={handleRegenerate}>
|
|
191
|
+
<RefreshIcon />
|
|
192
|
+
</button>
|
|
193
|
+
<FeedbackButtons onFeedback={handleFeedback} />
|
|
194
|
+
</footer>
|
|
195
|
+
)}
|
|
196
|
+
</article>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Example: Chat Input with Attachments
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
function ChatInput({ onSend, disabled }) {
|
|
205
|
+
const [input, setInput] = useState('');
|
|
206
|
+
const [files, setFiles] = useState([]);
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<div className="chat-input-container">
|
|
210
|
+
{files.length > 0 && (
|
|
211
|
+
<div className="attachment-list">
|
|
212
|
+
{files.map(file => (
|
|
213
|
+
<AttachmentChip key={file.id} file={file} onRemove={removeFile} />
|
|
214
|
+
))}
|
|
215
|
+
</div>
|
|
216
|
+
)}
|
|
217
|
+
|
|
218
|
+
<div className="input-row">
|
|
219
|
+
<button aria-label="Attach file" className="attach-btn">
|
|
220
|
+
<PaperclipIcon />
|
|
221
|
+
</button>
|
|
222
|
+
|
|
223
|
+
<textarea
|
|
224
|
+
value={input}
|
|
225
|
+
onChange={e => setInput(e.target.value)}
|
|
226
|
+
placeholder="Message Assistant..."
|
|
227
|
+
rows={1}
|
|
228
|
+
disabled={disabled}
|
|
229
|
+
aria-label="Message input"
|
|
230
|
+
/>
|
|
231
|
+
|
|
232
|
+
<button
|
|
233
|
+
aria-label="Send message"
|
|
234
|
+
disabled={!input.trim() || disabled}
|
|
235
|
+
className="send-btn"
|
|
236
|
+
onClick={() => onSend(input, files)}
|
|
237
|
+
>
|
|
238
|
+
{disabled ? <Spinner /> : <SendIcon />}
|
|
239
|
+
</button>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Tokens Used
|
|
247
|
+
|
|
248
|
+
- **color**: surface-default, surface-raised, surface-sunken, interactive-primary, text-default, text-inverted, text-muted, border-default
|
|
249
|
+
- **spacing**: card-padding, md, section-y
|
|
250
|
+
- **radii**: sm, md, lg, xl
|
|
251
|
+
- **shadow**: card
|
|
252
|
+
- **typography**: font-sans, font-mono, size-sm, size-base
|