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,186 @@
|
|
|
1
|
+
# Bento Grid
|
|
2
|
+
|
|
3
|
+
A flexible, variable-size card grid layout inspired by bento box design. Supports mixed card dimensions (1×1, 2×1, 1×2, 2×2) with hover lift effects, focus states, and content variations.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
**Use bento grids for:**
|
|
8
|
+
- Portfolio or case study showcases
|
|
9
|
+
- Feature highlights with visual variety
|
|
10
|
+
- Product gallery with mixed content types
|
|
11
|
+
- Dashboard layouts with different widget sizes
|
|
12
|
+
- Blog or article collections with featured posts
|
|
13
|
+
|
|
14
|
+
**Don't use for:**
|
|
15
|
+
- Data tables (use proper table semantics)
|
|
16
|
+
- Uniform card lists (use standard grid)
|
|
17
|
+
- Content requiring strict reading order
|
|
18
|
+
- Mobile-first experiences (complex layouts break on small screens)
|
|
19
|
+
|
|
20
|
+
## Component Anatomy
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
┌──────────────────────────────────────────────────────┐
|
|
24
|
+
│ [1×1 Card] [2×1 Card - Featured] │
|
|
25
|
+
│ [Spans 2 columns] │
|
|
26
|
+
├──────────────────────────────────────────────────────┤
|
|
27
|
+
│ [1×2 Card] [1×1 Card] [1×1 Card] │
|
|
28
|
+
│ [Spans 2 [Standard] [Standard] │
|
|
29
|
+
│ rows] │
|
|
30
|
+
├──────────────────────────────────────────────────────┤
|
|
31
|
+
│ [2×2 Card - Hero] │
|
|
32
|
+
│ [Spans 2×2 grid] │
|
|
33
|
+
└──────────────────────────────────────────────────────┘
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## State Matrix
|
|
37
|
+
|
|
38
|
+
| State | Visual Treatment | Tokens Used |
|
|
39
|
+
|-------|------------------|-------------|
|
|
40
|
+
| **Default** | Card at rest, subtle border, shadow.card applied | `color.surface.raised`, `shadow.card`, `radius.card` |
|
|
41
|
+
| **Hover** | Card lifts 4px (transform: translateY(-4px)), shadow deepens, opacity 100% | `motion.hover`, `shadow.card` (elevated) |
|
|
42
|
+
| **Focus** | Focus ring around card (2px, inset), keyboard navigation visible | `color.focus.ring`, `space.inlineSm` |
|
|
43
|
+
| **Active** | Card background slightly darkened, border color strengthened | `color.surface.sunken`, `color.border.strong` |
|
|
44
|
+
| **Disabled** | Opacity 50%, cursor not-allowed, no hover effect | `color.text.muted`, `motion.hover` (disabled) |
|
|
45
|
+
|
|
46
|
+
## Token Usage
|
|
47
|
+
|
|
48
|
+
### Colors
|
|
49
|
+
- **Card Background**: `color.surface.raised` for elevated appearance
|
|
50
|
+
- **Card Border**: `color.border.default` for subtle separation
|
|
51
|
+
- **Text**: `color.text.default` for headings, `color.text.muted` for descriptions
|
|
52
|
+
- **Overlay**: `color.surface.overlay` with 10% opacity for hover darkening
|
|
53
|
+
|
|
54
|
+
### Spacing
|
|
55
|
+
- **Card Padding**: `space.cardPadding` (6px) internal spacing
|
|
56
|
+
- **Grid Gap**: `space.cardGap` (4px) between cards
|
|
57
|
+
- **Content Stack**: `space.stackMd` (4px) between card title and description
|
|
58
|
+
|
|
59
|
+
### Typography
|
|
60
|
+
- **Card Title**: `typography.heading.card` (xl, semibold)
|
|
61
|
+
- **Card Description**: `typography.body.default` or `typography.body.small`
|
|
62
|
+
- **Card Meta**: `typography.ui.caption` for dates, tags, or metadata
|
|
63
|
+
|
|
64
|
+
### Shadows & Radii
|
|
65
|
+
- **Card Radius**: `radius.card` for rounded corners
|
|
66
|
+
- **Card Shadow**: `shadow.card` at rest, elevated shadow on hover
|
|
67
|
+
- **Focus Ring**: 2px inset ring with `color.focus.ring`
|
|
68
|
+
|
|
69
|
+
## Implementation Notes
|
|
70
|
+
|
|
71
|
+
### CSS/HTML Approach
|
|
72
|
+
|
|
73
|
+
**Structure:**
|
|
74
|
+
```
|
|
75
|
+
<div class="bento-grid">
|
|
76
|
+
<article class="bento-card bento-card--1x1">
|
|
77
|
+
<img src="..." alt="..." class="bento-card__image">
|
|
78
|
+
<div class="bento-card__content">
|
|
79
|
+
<h3 class="bento-card__title">Title</h3>
|
|
80
|
+
<p class="bento-card__description">Description</p>
|
|
81
|
+
</div>
|
|
82
|
+
</article>
|
|
83
|
+
|
|
84
|
+
<article class="bento-card bento-card--2x1">
|
|
85
|
+
<!-- Featured card content -->
|
|
86
|
+
</article>
|
|
87
|
+
|
|
88
|
+
<!-- More cards... -->
|
|
89
|
+
</div>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Key CSS patterns:**
|
|
93
|
+
- Use CSS Grid with `display: grid; grid-template-columns: repeat(4, 1fr)` (4-column base)
|
|
94
|
+
- Apply `grid-column: span 1/2/4` and `grid-row: span 1/2` for sizing variants
|
|
95
|
+
- Use `transition: all 200ms ease-out` for smooth hover effects
|
|
96
|
+
- Apply `transform: translateY(-4px)` on hover for lift effect
|
|
97
|
+
- Use `box-shadow` with `shadow.card` token for depth
|
|
98
|
+
- Implement focus ring with `outline: 2px solid color.focus.ring; outline-offset: 2px`
|
|
99
|
+
|
|
100
|
+
**Responsive grid:**
|
|
101
|
+
- Desktop (1024px+): 4-column grid with full size variations
|
|
102
|
+
- Tablet (768px): 2-column grid, reduce card sizes (1×1 only, no 2×2)
|
|
103
|
+
- Mobile (375px): 1-column grid, all cards 1×1, stack vertically
|
|
104
|
+
|
|
105
|
+
**Content variations:**
|
|
106
|
+
- Image-only cards: Full-height image with overlay text
|
|
107
|
+
- Text-only cards: Heading + description + optional CTA
|
|
108
|
+
- Mixed cards: Image + text side-by-side or stacked
|
|
109
|
+
- Interactive cards: Hover reveals additional content or CTA
|
|
110
|
+
|
|
111
|
+
### React Approach
|
|
112
|
+
|
|
113
|
+
**Component structure:**
|
|
114
|
+
- Create `<BentoGrid>` wrapper managing grid layout and responsive behavior
|
|
115
|
+
- Create `<BentoCard>` component accepting `size` prop (1x1, 2x1, 1x2, 2x2)
|
|
116
|
+
- Use `children` prop for flexible content (image, text, CTA, etc.)
|
|
117
|
+
- Implement `onHover` state for lift effect and shadow elevation
|
|
118
|
+
- Use CSS Grid with CSS-in-JS or Tailwind for responsive sizing
|
|
119
|
+
|
|
120
|
+
**State management:**
|
|
121
|
+
- Track hover state per card for visual feedback
|
|
122
|
+
- Use CSS `:hover` and `:focus-visible` for interactive states
|
|
123
|
+
- Implement `useCallback` for hover handlers to prevent unnecessary re-renders
|
|
124
|
+
- Consider `IntersectionObserver` for lazy-loading card images
|
|
125
|
+
|
|
126
|
+
**Responsive behavior:**
|
|
127
|
+
- Use `useMediaQuery` hook to detect breakpoints
|
|
128
|
+
- Adjust grid columns and card sizes based on viewport
|
|
129
|
+
- Stack cards vertically on mobile, maintain grid on desktop
|
|
130
|
+
|
|
131
|
+
## Acceptance Criteria
|
|
132
|
+
|
|
133
|
+
- [ ] Grid uses CSS Grid with proper column/row spanning
|
|
134
|
+
- [ ] Cards support 1×1, 2×1, 1×2, and 2×2 sizes
|
|
135
|
+
- [ ] Hover effect lifts card 4px with shadow elevation
|
|
136
|
+
- [ ] Focus ring visible on keyboard navigation (2px, `color.focus.ring`)
|
|
137
|
+
- [ ] Card padding uses `space.cardPadding` token
|
|
138
|
+
- [ ] Grid gap uses `space.cardGap` token
|
|
139
|
+
- [ ] Responsive: 4-column desktop, 2-column tablet, 1-column mobile
|
|
140
|
+
- [ ] All card titles use `typography.heading.card` token
|
|
141
|
+
- [ ] All descriptions use `typography.body.default` or `typography.body.small`
|
|
142
|
+
- [ ] Card background uses `color.surface.raised` token
|
|
143
|
+
- [ ] Respects `prefers-reduced-motion` (no lift effect)
|
|
144
|
+
- [ ] All text passes color contrast ratio (4.5:1 for body, 3:1 for large text)
|
|
145
|
+
|
|
146
|
+
## Accessibility
|
|
147
|
+
|
|
148
|
+
### ARIA & Semantics
|
|
149
|
+
- Use `<article>` for each card (semantic content container)
|
|
150
|
+
- Heading hierarchy: `<h2>` or `<h3>` for card titles (depends on page context)
|
|
151
|
+
- Use `<img>` with descriptive `alt` text for images
|
|
152
|
+
- If card is clickable, use `<a>` or `<button>` with clear link text
|
|
153
|
+
|
|
154
|
+
### Keyboard Navigation
|
|
155
|
+
- Tab order: Left-to-right, top-to-bottom through cards
|
|
156
|
+
- Focus ring must be visible with 2px width, `color.focus.ring`
|
|
157
|
+
- Ensure focus ring has sufficient contrast against card background
|
|
158
|
+
- Clickable cards must be keyboard accessible (use `<a>` or `<button>`)
|
|
159
|
+
|
|
160
|
+
### Screen Readers
|
|
161
|
+
- Card titles must be descriptive and unique
|
|
162
|
+
- Image `alt` text must describe content, not just "image"
|
|
163
|
+
- If card contains multiple interactive elements, use `aria-label` for context
|
|
164
|
+
- Use `aria-current="page"` for active/selected cards
|
|
165
|
+
|
|
166
|
+
### Color & Contrast
|
|
167
|
+
- Text must meet WCAG AA (4.5:1 for body, 3:1 for large text)
|
|
168
|
+
- Don't rely on color alone to indicate card state
|
|
169
|
+
- Ensure sufficient contrast between card background and text
|
|
170
|
+
- Test with WebAIM Contrast Checker
|
|
171
|
+
|
|
172
|
+
### Motion
|
|
173
|
+
- Respect `prefers-reduced-motion: reduce` by disabling lift effect
|
|
174
|
+
- Hover animations should not auto-play
|
|
175
|
+
- Avoid rapid or flashing animations
|
|
176
|
+
|
|
177
|
+
### Touch Targets
|
|
178
|
+
- Cards must be minimum 48px × 48px for touch targets
|
|
179
|
+
- Maintain `space.cardGap` (4px) between cards for touch accuracy
|
|
180
|
+
- Ensure clickable areas are easily tappable on mobile
|
|
181
|
+
|
|
182
|
+
### Content Considerations
|
|
183
|
+
- Avoid relying on image alone to convey meaning
|
|
184
|
+
- Provide text alternative for image-heavy cards
|
|
185
|
+
- Ensure card order makes sense without visual layout
|
|
186
|
+
- Test with screen readers (NVDA, JAWS, VoiceOver)
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
# AI Code Block
|
|
2
|
+
|
|
3
|
+
Syntax-highlighted code display with copy, run, and diff capabilities for AI-generated code.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
- Displaying AI-generated code
|
|
7
|
+
- Code explanations with syntax highlighting
|
|
8
|
+
- Before/after code comparisons
|
|
9
|
+
- Interactive code playgrounds
|
|
10
|
+
- Terminal/command output
|
|
11
|
+
|
|
12
|
+
## Anatomy
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
16
|
+
│ CodeBlock │
|
|
17
|
+
│ ├─ BlockHeader (language, filename, actions) │
|
|
18
|
+
│ │ ├─ LanguageBadge │
|
|
19
|
+
│ │ ├─ Filename │
|
|
20
|
+
│ │ └─ ActionButtons (copy, run, download) │
|
|
21
|
+
│ ├─ BlockContent (syntax highlighted code) │
|
|
22
|
+
│ │ ├─ LineNumbers (optional) │
|
|
23
|
+
│ │ └─ CodeContent (highlighted) │
|
|
24
|
+
│ └─ BlockFooter (execution result, apply button) │
|
|
25
|
+
└─────────────────────────────────────────────────────────────┘
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Token Usage
|
|
29
|
+
|
|
30
|
+
```css
|
|
31
|
+
/* Code Block Container */
|
|
32
|
+
.code-block {
|
|
33
|
+
background: var(--color-surface-sunken);
|
|
34
|
+
border: 1px solid var(--color-border-default);
|
|
35
|
+
border-radius: var(--radius-lg);
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
font-family: var(--font-mono);
|
|
38
|
+
font-size: var(--font-size-sm);
|
|
39
|
+
line-height: var(--line-height-snug);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Block Header */
|
|
43
|
+
.code-header {
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: space-between;
|
|
47
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
48
|
+
background: rgba(0, 0, 0, 0.2);
|
|
49
|
+
border-bottom: 1px solid var(--color-border-default);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.language-badge {
|
|
53
|
+
display: inline-flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
padding: var(--spacing-2xs) var(--spacing-xs);
|
|
56
|
+
background: rgba(37, 99, 235, 0.15);
|
|
57
|
+
color: var(--color-interactive-primary);
|
|
58
|
+
border-radius: var(--radius-sm);
|
|
59
|
+
font-size: var(--font-size-xs);
|
|
60
|
+
font-weight: var(--font-weight-medium);
|
|
61
|
+
text-transform: uppercase;
|
|
62
|
+
letter-spacing: 0.05em;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.filename {
|
|
66
|
+
font-family: var(--font-mono);
|
|
67
|
+
font-size: var(--font-size-xs);
|
|
68
|
+
color: var(--color-text-muted);
|
|
69
|
+
margin-left: var(--spacing-sm);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Action Buttons */
|
|
73
|
+
.code-actions {
|
|
74
|
+
display: flex;
|
|
75
|
+
gap: var(--spacing-xs);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.code-action-btn {
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
gap: var(--spacing-2xs);
|
|
82
|
+
padding: var(--spacing-2xs) var(--spacing-xs);
|
|
83
|
+
background: transparent;
|
|
84
|
+
border: 1px solid var(--color-border-default);
|
|
85
|
+
border-radius: var(--radius-md);
|
|
86
|
+
color: var(--color-text-muted);
|
|
87
|
+
font-size: var(--font-size-xs);
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
transition: all var(--duration-fast);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.code-action-btn:hover {
|
|
93
|
+
background: var(--color-surface-raised);
|
|
94
|
+
border-color: var(--color-border-strong);
|
|
95
|
+
color: var(--color-text-default);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.code-action-btn.success {
|
|
99
|
+
background: rgba(34, 197, 94, 0.15);
|
|
100
|
+
border-color: rgba(34, 197, 94, 0.3);
|
|
101
|
+
color: #22C55E;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Code Content */
|
|
105
|
+
.code-content {
|
|
106
|
+
padding: var(--spacing-md);
|
|
107
|
+
overflow-x: auto;
|
|
108
|
+
background: var(--color-surface-sunken);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Line Numbers */
|
|
112
|
+
.code-with-lines {
|
|
113
|
+
display: flex;
|
|
114
|
+
gap: var(--spacing-md);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.line-numbers {
|
|
118
|
+
display: flex;
|
|
119
|
+
flex-direction: column;
|
|
120
|
+
color: var(--color-text-muted);
|
|
121
|
+
font-size: var(--font-size-xs);
|
|
122
|
+
text-align: right;
|
|
123
|
+
user-select: none;
|
|
124
|
+
border-right: 1px solid var(--color-border-default);
|
|
125
|
+
padding-right: var(--spacing-sm);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.line-number {
|
|
129
|
+
line-height: 1.8;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* Syntax Highlighting (Shiki/Prism compatible) */
|
|
133
|
+
.code-content pre {
|
|
134
|
+
margin: 0;
|
|
135
|
+
background: transparent !important;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.code-content code {
|
|
139
|
+
font-family: var(--font-mono);
|
|
140
|
+
font-size: inherit;
|
|
141
|
+
background: transparent;
|
|
142
|
+
padding: 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Diff View */
|
|
146
|
+
.code-diff {
|
|
147
|
+
display: flex;
|
|
148
|
+
flex-direction: column;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.diff-line {
|
|
152
|
+
display: flex;
|
|
153
|
+
padding: 0 var(--spacing-md);
|
|
154
|
+
line-height: 1.8;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.diff-line.added {
|
|
158
|
+
background: rgba(34, 197, 94, 0.1);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.diff-line.added::before {
|
|
162
|
+
content: '+';
|
|
163
|
+
color: #22C55E;
|
|
164
|
+
margin-right: var(--spacing-sm);
|
|
165
|
+
font-weight: var(--font-weight-bold);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.diff-line.removed {
|
|
169
|
+
background: rgba(239, 68, 68, 0.1);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.diff-line.removed::before {
|
|
173
|
+
content: '-';
|
|
174
|
+
color: var(--color-status-error);
|
|
175
|
+
margin-right: var(--spacing-sm);
|
|
176
|
+
font-weight: var(--font-weight-bold);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.diff-line.unchanged {
|
|
180
|
+
color: var(--color-text-muted);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.diff-line.unchanged::before {
|
|
184
|
+
content: ' ';
|
|
185
|
+
margin-right: var(--spacing-sm);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Apply Changes Button */
|
|
189
|
+
.code-footer {
|
|
190
|
+
display: flex;
|
|
191
|
+
align-items: center;
|
|
192
|
+
justify-content: space-between;
|
|
193
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
194
|
+
background: var(--color-surface-raised);
|
|
195
|
+
border-top: 1px solid var(--color-border-default);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.apply-btn {
|
|
199
|
+
display: flex;
|
|
200
|
+
align-items: center;
|
|
201
|
+
gap: var(--spacing-xs);
|
|
202
|
+
padding: var(--spacing-xs) var(--spacing-md);
|
|
203
|
+
background: var(--color-interactive-primary);
|
|
204
|
+
color: var(--color-text-inverted);
|
|
205
|
+
border: none;
|
|
206
|
+
border-radius: var(--radius-md);
|
|
207
|
+
font-size: var(--font-size-sm);
|
|
208
|
+
font-weight: var(--font-weight-medium);
|
|
209
|
+
cursor: pointer;
|
|
210
|
+
transition: all var(--duration-fast);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.apply-btn:hover {
|
|
214
|
+
background: var(--color-interactive-primary-hover);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* Terminal Style */
|
|
218
|
+
.terminal-block {
|
|
219
|
+
background: #0D1117;
|
|
220
|
+
border-radius: var(--radius-lg);
|
|
221
|
+
font-family: var(--font-mono);
|
|
222
|
+
font-size: var(--font-size-sm);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.terminal-header {
|
|
226
|
+
display: flex;
|
|
227
|
+
align-items: center;
|
|
228
|
+
gap: var(--spacing-sm);
|
|
229
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
230
|
+
background: rgba(255, 255, 255, 0.05);
|
|
231
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.terminal-buttons {
|
|
235
|
+
display: flex;
|
|
236
|
+
gap: 6px;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.terminal-btn {
|
|
240
|
+
width: 12px;
|
|
241
|
+
height: 12px;
|
|
242
|
+
border-radius: var(--radius-full);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.terminal-btn.red { background: #FF5F56; }
|
|
246
|
+
.terminal-btn.yellow { background: #FFBD2E; }
|
|
247
|
+
.terminal-btn.green { background: #27C93F; }
|
|
248
|
+
|
|
249
|
+
.terminal-content {
|
|
250
|
+
padding: var(--spacing-md);
|
|
251
|
+
color: #E6EDF3;
|
|
252
|
+
line-height: 1.6;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.terminal-prompt {
|
|
256
|
+
color: #7EE787;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.terminal-command {
|
|
260
|
+
color: #E6EDF3;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.terminal-output {
|
|
264
|
+
color: #8B949E;
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## State Matrix
|
|
269
|
+
|
|
270
|
+
| Element | Default | Hover | Copied | Running |
|
|
271
|
+
|---------|---------|-------|--------|---------|
|
|
272
|
+
| Copy Button | ghost | raised | success green | - |
|
|
273
|
+
| Run Button | ghost | raised | - | spinner |
|
|
274
|
+
| Apply Button | primary | primary-hover | - | loading |
|
|
275
|
+
| Code Block | sunken bg | - | - | - |
|
|
276
|
+
| Diff Line | context-based | - | - | - |
|
|
277
|
+
|
|
278
|
+
## Accessibility
|
|
279
|
+
|
|
280
|
+
```html
|
|
281
|
+
<figure class="code-block" role="region" aria-label="Code example: example.tsx">
|
|
282
|
+
<figcaption class="code-header">
|
|
283
|
+
<div class="file-info">
|
|
284
|
+
<span class="language-badge">TSX</span>
|
|
285
|
+
<span class="filename">example.tsx</span>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
<div class="code-actions">
|
|
289
|
+
<button
|
|
290
|
+
class="code-action-btn"
|
|
291
|
+
aria-label="Copy code to clipboard"
|
|
292
|
+
onClick={handleCopy}
|
|
293
|
+
>
|
|
294
|
+
<CopyIcon aria-hidden="true" />
|
|
295
|
+
<span>Copy</span>
|
|
296
|
+
</button>
|
|
297
|
+
|
|
298
|
+
<button
|
|
299
|
+
class="code-action-btn"
|
|
300
|
+
aria-label="Download file"
|
|
301
|
+
onClick={handleDownload}
|
|
302
|
+
>
|
|
303
|
+
<DownloadIcon aria-hidden="true" />
|
|
304
|
+
<span>Download</span>
|
|
305
|
+
</button>
|
|
306
|
+
</div>
|
|
307
|
+
</figcaption>
|
|
308
|
+
|
|
309
|
+
<div class="code-with-lines">
|
|
310
|
+
<div class="line-numbers" aria-hidden="true">
|
|
311
|
+
<span class="line-number">1</span>
|
|
312
|
+
<span class="line-number">2</span>
|
|
313
|
+
<span class="line-number">3</span>
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
<pre class="code-content">
|
|
317
|
+
<code>const example = "Hello World";</code>
|
|
318
|
+
</pre>
|
|
319
|
+
</div>
|
|
320
|
+
</figure>
|
|
321
|
+
|
|
322
|
+
<!-- Screen reader announcement -->
|
|
323
|
+
<div aria-live="polite" aria-atomic="true" class="sr-only">
|
|
324
|
+
Code copied to clipboard
|
|
325
|
+
</div>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Example: Basic Code Block
|
|
329
|
+
|
|
330
|
+
```tsx
|
|
331
|
+
function CodeBlock({ code, language, filename, onCopy }) {
|
|
332
|
+
const [copied, setCopied] = useState(false);
|
|
333
|
+
|
|
334
|
+
const handleCopy = async () => {
|
|
335
|
+
await navigator.clipboard.writeText(code);
|
|
336
|
+
setCopied(true);
|
|
337
|
+
setTimeout(() => setCopied(false), 2000);
|
|
338
|
+
onCopy?.();
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<figure className="code-block">
|
|
343
|
+
<figcaption className="code-header">
|
|
344
|
+
<div className="file-info">
|
|
345
|
+
<span className="language-badge">{language}</span>
|
|
346
|
+
{filename && <span className="filename">{filename}</span>}
|
|
347
|
+
</div>
|
|
348
|
+
|
|
349
|
+
<div className="code-actions">
|
|
350
|
+
<button
|
|
351
|
+
className={`code-action-btn ${copied ? 'success' : ''}`}
|
|
352
|
+
onClick={handleCopy}
|
|
353
|
+
aria-label={copied ? 'Copied' : 'Copy code'}
|
|
354
|
+
>
|
|
355
|
+
{copied ? <CheckIcon /> : <CopyIcon />}
|
|
356
|
+
<span>{copied ? 'Copied!' : 'Copy'}</span>
|
|
357
|
+
</button>
|
|
358
|
+
</div>
|
|
359
|
+
</figcaption>
|
|
360
|
+
|
|
361
|
+
<div className="code-content">
|
|
362
|
+
<pre>
|
|
363
|
+
<code dangerouslySetInnerHTML={{ __html: highlightedCode }} />
|
|
364
|
+
</pre>
|
|
365
|
+
</div>
|
|
366
|
+
</figure>
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Example: Code Diff View
|
|
372
|
+
|
|
373
|
+
```tsx
|
|
374
|
+
function CodeDiff({ original, modified, filename }) {
|
|
375
|
+
const diff = computeDiff(original, modified);
|
|
376
|
+
|
|
377
|
+
return (
|
|
378
|
+
<figure className="code-block diff-view">
|
|
379
|
+
<figcaption className="code-header">
|
|
380
|
+
<span className="filename">{filename}</span>
|
|
381
|
+
<span className="diff-stats">
|
|
382
|
+
<span className="added">+{diff.additions}</span>
|
|
383
|
+
<span className="removed">-{diff.deletions}</span>
|
|
384
|
+
</span>
|
|
385
|
+
</figcaption>
|
|
386
|
+
|
|
387
|
+
<div className="code-diff">
|
|
388
|
+
{diff.lines.map((line, i) => (
|
|
389
|
+
<div
|
|
390
|
+
key={i}
|
|
391
|
+
className={`diff-line ${line.type}`}
|
|
392
|
+
aria-label={line.type === 'added' ? 'Added line' : line.type === 'removed' ? 'Removed line' : 'Unchanged line'}
|
|
393
|
+
>
|
|
394
|
+
<span className="diff-content">{line.content}</span>
|
|
395
|
+
</div>
|
|
396
|
+
))}
|
|
397
|
+
</div>
|
|
398
|
+
|
|
399
|
+
<footer className="code-footer">
|
|
400
|
+
<span className="diff-summary">
|
|
401
|
+
{diff.additions} additions, {diff.deletions} deletions
|
|
402
|
+
</span>
|
|
403
|
+
<button className="apply-btn">
|
|
404
|
+
<CheckIcon />
|
|
405
|
+
Apply Changes
|
|
406
|
+
</button>
|
|
407
|
+
</footer>
|
|
408
|
+
</figure>
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Example: Terminal Output
|
|
414
|
+
|
|
415
|
+
```tsx
|
|
416
|
+
function TerminalBlock({ command, output, workingDir = '~' }) {
|
|
417
|
+
return (
|
|
418
|
+
<div className="terminal-block" role="region" aria-label="Terminal output">
|
|
419
|
+
<div className="terminal-header">
|
|
420
|
+
<div className="terminal-buttons" aria-hidden="true">
|
|
421
|
+
<span className="terminal-btn red"></span>
|
|
422
|
+
<span className="terminal-btn yellow"></span>
|
|
423
|
+
<span className="terminal-btn green"></span>
|
|
424
|
+
</div>
|
|
425
|
+
<span className="terminal-title">Terminal</span>
|
|
426
|
+
</div>
|
|
427
|
+
|
|
428
|
+
<div className="terminal-content">
|
|
429
|
+
<div className="terminal-line">
|
|
430
|
+
<span className="terminal-prompt">➜ {workingDir}</span>
|
|
431
|
+
<span className="terminal-command"> {command}</span>
|
|
432
|
+
</div>
|
|
433
|
+
|
|
434
|
+
<div className="terminal-output">
|
|
435
|
+
{output.split('\n').map((line, i) => (
|
|
436
|
+
<div key={i}>{line}</div>
|
|
437
|
+
))}
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## Example: Runnable Code Block
|
|
446
|
+
|
|
447
|
+
```tsx
|
|
448
|
+
function RunnableCodeBlock({ code, language, onRun }) {
|
|
449
|
+
const [isRunning, setIsRunning] = useState(false);
|
|
450
|
+
const [result, setResult] = useState(null);
|
|
451
|
+
|
|
452
|
+
const handleRun = async () => {
|
|
453
|
+
setIsRunning(true);
|
|
454
|
+
try {
|
|
455
|
+
const output = await onRun(code);
|
|
456
|
+
setResult({ success: true, output });
|
|
457
|
+
} catch (error) {
|
|
458
|
+
setResult({ success: false, error: error.message });
|
|
459
|
+
} finally {
|
|
460
|
+
setIsRunning(false);
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
return (
|
|
465
|
+
<figure className="code-block runnable">
|
|
466
|
+
<figcaption className="code-header">
|
|
467
|
+
<span className="language-badge">{language}</span>
|
|
468
|
+
|
|
469
|
+
<button
|
|
470
|
+
className="code-action-btn run-btn"
|
|
471
|
+
onClick={handleRun}
|
|
472
|
+
disabled={isRunning}
|
|
473
|
+
>
|
|
474
|
+
{isRunning ? <Spinner /> : <PlayIcon />}
|
|
475
|
+
<span>{isRunning ? 'Running...' : 'Run'}</span>
|
|
476
|
+
</button>
|
|
477
|
+
</figcaption>
|
|
478
|
+
|
|
479
|
+
<div className="code-content">
|
|
480
|
+
<pre><code>{code}</code></pre>
|
|
481
|
+
</div>
|
|
482
|
+
|
|
483
|
+
{result && (
|
|
484
|
+
<footer className="code-footer execution-result">
|
|
485
|
+
{result.success ? (
|
|
486
|
+
<div className="success-output">{result.output}</div>
|
|
487
|
+
) : (
|
|
488
|
+
<div className="error-output">{result.error}</div>
|
|
489
|
+
)}
|
|
490
|
+
</footer>
|
|
491
|
+
)}
|
|
492
|
+
</figure>
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
## Tokens Used
|
|
498
|
+
|
|
499
|
+
- **color**: surface-sunken, surface-raised, interactive-primary, interactive-primary-hover, text-default, text-inverted, text-muted, border-default, border-strong, status-success, status-error
|
|
500
|
+
- **spacing**: 2xs, xs, sm, md
|
|
501
|
+
- **radii**: sm, md, lg
|
|
502
|
+
- **typography**: font-mono, size-xs, size-sm, weight-medium
|
|
503
|
+
- **motion**: duration-fast
|