feat: Add comprehensive reference documentation for Pencil design skill, detailing its build system, code conversion, file format, and atomic design principles.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
565
.agent/skills/pencil-design/references/ATOMIC_DESIGN.md
Normal file
565
.agent/skills/pencil-design/references/ATOMIC_DESIGN.md
Normal file
@@ -0,0 +1,565 @@
|
||||
# Atomic Design Architecture Reference
|
||||
|
||||
Enterprise-scale UI Kit organization using Atomic Design methodology for Pencil.
|
||||
|
||||
## Overview
|
||||
|
||||
**Problem**: Large monolithic `.pen` files (5000+ lines) cause:
|
||||
- ❌ Slow performance (2-3 second load times)
|
||||
- ❌ Maintenance challenges (duplicate components)
|
||||
- ❌ Collaboration bottlenecks (merge conflicts)
|
||||
- ❌ Poor reusability
|
||||
- ❌ Difficult to scale for enterprise applications
|
||||
|
||||
**Solution**: Atomic Design Hierarchy - Industry standard for enterprise UI Kits
|
||||
|
||||
## Atomic Design Structure
|
||||
|
||||
```
|
||||
project-name/
|
||||
├── src/
|
||||
│ ├── foundations/
|
||||
│ │ └── design-tokens.pen # Design system variables
|
||||
│ │
|
||||
│ ├── atoms/ # Smallest building blocks
|
||||
│ │ ├── buttons.pen # All button variants & states
|
||||
│ │ ├── inputs.pen # Text fields, checkboxes, radio
|
||||
│ │ ├── badges.pen # Badges, chips, pills, tags
|
||||
│ │ ├── icons.pen # Icon library (if custom)
|
||||
│ │ └── typography.pen # Headings, body text, labels
|
||||
│ │
|
||||
│ ├── molecules/ # Combinations of atoms
|
||||
│ │ ├── form-fields.pen # Label + Input + Error
|
||||
│ │ ├── search-bar.pen # Input + Icon + Button
|
||||
│ │ ├── card-headers.pen # Icon + Title + Badge
|
||||
│ │ └── navigation-items.pen # Icon + Text + Badge
|
||||
│ │
|
||||
│ ├── organisms/ # Complex UI sections
|
||||
│ │ ├── cards.pen # Feature, pricing, step cards
|
||||
│ │ ├── navigation.pen # Top nav, sidebar, mobile menu
|
||||
│ │ ├── headers.pen # Page & section headers
|
||||
│ │ ├── footers.pen # Footer variants
|
||||
│ │ └── forms.pen # Complete form layouts
|
||||
│ │
|
||||
│ ├── templates/ # Page layouts (optional)
|
||||
│ │ ├── dashboard-layout.pen
|
||||
│ │ ├── landing-layout.pen
|
||||
│ │ └── auth-layout.pen
|
||||
│ │
|
||||
│ └── pages/ # Complete pages
|
||||
│ ├── desktop-home.pen
|
||||
│ ├── mobile-home.pen
|
||||
│ └── tablet-home.pen
|
||||
│
|
||||
├── build/ # Build output (gitignore)
|
||||
├── scripts/
|
||||
│ └── build.py # Build script
|
||||
├── pencil.config.json
|
||||
└── USAGE_GUIDE.md
|
||||
```
|
||||
|
||||
## Why Atomic Design?
|
||||
|
||||
### Benefits
|
||||
|
||||
- ✅ **Industry Standard** - Used by Figma, Material Design, Ant Design
|
||||
- ✅ **Maximum Scalability** - Easy to add components at any level
|
||||
- ✅ **Clear Hierarchy** - Atoms → Molecules → Organisms → Templates → Pages
|
||||
- ✅ **Team Collaboration** - Different designers own different atomic levels
|
||||
- ✅ **Reusability** - Components compose naturally from smaller pieces
|
||||
- ✅ **Discoverability** - Intuitive folder structure for finding components
|
||||
- ✅ **Maintainability** - Small, focused files (<500 lines each)
|
||||
- ✅ **Version Control** - Minimal merge conflicts with parallel work
|
||||
|
||||
### Recommended For
|
||||
|
||||
- ✅ Enterprise applications with 50+ components
|
||||
- ✅ Design systems shared across multiple products
|
||||
- ✅ Teams of 3+ designers working in parallel
|
||||
- ✅ Long-term maintenance (2+ years)
|
||||
- ✅ Component documentation & showcases
|
||||
- ✅ Cross-platform design consistency
|
||||
|
||||
## Component Naming Convention
|
||||
|
||||
Atomic Design uses strict hierarchical naming:
|
||||
|
||||
```
|
||||
{AtomicLevel}/{Component}/{Variant}/{State}
|
||||
```
|
||||
|
||||
### Atoms (Smallest Building Blocks)
|
||||
|
||||
```
|
||||
Atom/Button/Primary/Default
|
||||
Atom/Button/Primary/Hover
|
||||
Atom/Button/Primary/Disabled
|
||||
Atom/Button/Secondary/Default
|
||||
Atom/Badge/Section/Default
|
||||
Atom/Input/Text/Default
|
||||
Atom/Input/Text/Error
|
||||
Atom/Input/Text/Disabled
|
||||
```
|
||||
|
||||
### Molecules (Combinations of Atoms)
|
||||
|
||||
```
|
||||
Molecule/FormField/Text/Default
|
||||
Molecule/FormField/Text/WithError
|
||||
Molecule/SearchBar/Default
|
||||
Molecule/SearchBar/Focused
|
||||
Molecule/CardHeader/WithIcon
|
||||
Molecule/CardHeader/Plain
|
||||
```
|
||||
|
||||
### Organisms (Complex UI Sections)
|
||||
|
||||
```
|
||||
Organism/Card/Feature/Default
|
||||
Organism/Card/Pricing/Enterprise
|
||||
Organism/Navigation/Desktop/Default
|
||||
Organism/Navigation/Mobile/Collapsed
|
||||
Organism/Navigation/Mobile/Expanded
|
||||
Organism/Footer/TwoColumn/Default
|
||||
Organism/Footer/FourColumn/Default
|
||||
```
|
||||
|
||||
### Naming Rules
|
||||
|
||||
1. ✅ **Always include atomic level** (Atom/Molecule/Organism)
|
||||
2. ✅ **Use PascalCase** for all parts
|
||||
3. ✅ **Include variant** even if only one exists (use "Default")
|
||||
4. ✅ **Add state** for interactive components (Default/Hover/Active/Focus/Disabled/Loading)
|
||||
5. ✅ **Be descriptive** - Name should explain purpose without seeing component
|
||||
|
||||
## Component States & Variants
|
||||
|
||||
### Interactive Component States
|
||||
|
||||
Every interactive component should have states:
|
||||
|
||||
```
|
||||
Button/Primary/
|
||||
├── Default # Resting state
|
||||
├── Hover # Mouse over
|
||||
├── Active # Pressed/clicked
|
||||
├── Focus # Keyboard focus
|
||||
├── Disabled # Non-interactive
|
||||
└── Loading # Processing state (with spinner)
|
||||
```
|
||||
|
||||
### Implementation Example
|
||||
|
||||
```json
|
||||
// Default state
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "BtnPrimary_Default",
|
||||
"name": "Atom/Button/Primary/Default",
|
||||
"reusable": true,
|
||||
"fill": {
|
||||
"type": "gradient",
|
||||
"colors": [
|
||||
{"color": "#FF5C00", "position": 0},
|
||||
{"color": "#FF8A4C", "position": 1}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Hover state - slightly brighter
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "BtnPrimary_Hover",
|
||||
"name": "Atom/Button/Primary/Hover",
|
||||
"reusable": true,
|
||||
"fill": {
|
||||
"type": "gradient",
|
||||
"colors": [
|
||||
{"color": "#FF6E1A", "position": 0},
|
||||
{"color": "#FF9C66", "position": 1}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Disabled state - reduced opacity
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "BtnPrimary_Disabled",
|
||||
"name": "Atom/Button/Primary/Disabled",
|
||||
"reusable": true,
|
||||
"fill": "#6B6B70",
|
||||
"opacity": 0.5
|
||||
}
|
||||
```
|
||||
|
||||
### Responsive Variants
|
||||
|
||||
```
|
||||
Organism/Navigation/
|
||||
├── Desktop/
|
||||
│ ├── Default # Full horizontal nav with all items
|
||||
│ └── Scrolled # Sticky/collapsed on scroll
|
||||
├── Tablet/
|
||||
│ ├── Default # Hybrid with some collapsed items
|
||||
│ └── MenuOpen # Sidebar drawer
|
||||
└── Mobile/
|
||||
├── Collapsed # Hamburger icon only
|
||||
└── Expanded # Full-screen menu overlay
|
||||
```
|
||||
|
||||
## Design Tokens Foundation
|
||||
|
||||
### design-tokens.pen Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "2.6",
|
||||
"children": [{
|
||||
"type": "frame",
|
||||
"name": "Design Tokens Reference",
|
||||
"layout": "vertical",
|
||||
"gap": 20,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"content": "Design System Tokens",
|
||||
"fontSize": 32,
|
||||
"fill": "$text-primary"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"variables": {
|
||||
// Colors - Semantic naming
|
||||
"bg-page": {"type": "color", "value": "#0A0A0B"},
|
||||
"bg-surface": {"type": "color", "value": "#1A1A1C"},
|
||||
"bg-elevated": {"type": "color", "value": "#222225"},
|
||||
"text-primary": {"type": "color", "value": "#FFFFFF"},
|
||||
"text-secondary": {"type": "color", "value": "#ADADB0"},
|
||||
"text-tertiary": {"type": "color", "value": "#8B8B90"},
|
||||
"orange-primary": {"type": "color", "value": "#FF5C00"},
|
||||
"orange-light": {"type": "color", "value": "#FF8A4C"},
|
||||
"border-default": {"type": "color", "value": "#3B3B40"},
|
||||
"border-subtle": {"type": "color", "value": "#27272A"},
|
||||
|
||||
// Typography
|
||||
"font-primary": {"type": "string", "value": "Roboto"},
|
||||
"text-xs": {"type": "number", "value": 11},
|
||||
"text-sm": {"type": "number", "value": 13},
|
||||
"text-base": {"type": "number", "value": 14},
|
||||
"text-lg": {"type": "number", "value": 16},
|
||||
"text-xl": {"type": "number", "value": 18},
|
||||
"text-2xl": {"type": "number", "value": 24},
|
||||
"text-3xl": {"type": "number", "value": 32},
|
||||
|
||||
// Spacing (8px grid)
|
||||
"space-1": {"type": "number", "value": 4},
|
||||
"space-2": {"type": "number", "value": 8},
|
||||
"space-3": {"type": "number", "value": 12},
|
||||
"space-4": {"type": "number", "value": 16},
|
||||
"space-5": {"type": "number", "value": 20},
|
||||
"space-6": {"type": "number", "value": 24},
|
||||
"space-8": {"type": "number", "value": 32},
|
||||
"space-10": {"type": "number", "value": 40},
|
||||
|
||||
// Border Radius
|
||||
"radius-sm": {"type": "number", "value": 6},
|
||||
"radius-md": {"type": "number", "value": 10},
|
||||
"radius-lg": {"type": "number", "value": 16},
|
||||
"radius-full": {"type": "number", "value": 100},
|
||||
|
||||
// Breakpoints
|
||||
"breakpoint-mobile": {"type": "number", "value": 390},
|
||||
"breakpoint-tablet": {"type": "number", "value": 768},
|
||||
"breakpoint-desktop": {"type": "number", "value": 1440}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Extraction Workflow
|
||||
|
||||
### Step 1: Audit Components
|
||||
|
||||
```bash
|
||||
# Analyze current structure
|
||||
grep '"name":' tPOS-ui-kit.pen | grep -i "button" > buttons-list.txt
|
||||
grep '"name":' tPOS-ui-kit.pen | grep -i "badge" > badges-list.txt
|
||||
grep '"name":' tPOS-ui-kit.pen | grep -i "card" > cards-list.txt
|
||||
|
||||
# Count components by category
|
||||
echo "Buttons: $(grep -c button buttons-list.txt)"
|
||||
echo "Badges: $(grep -c badge badges-list.txt)"
|
||||
echo "Cards: $(grep -c card cards-list.txt)"
|
||||
```
|
||||
|
||||
### Step 2: Create Structure
|
||||
|
||||
```bash
|
||||
mkdir -p src/foundations src/atoms src/molecules src/organisms src/pages
|
||||
```
|
||||
|
||||
### Step 3: Extract to Atomic Files
|
||||
|
||||
```python
|
||||
import json
|
||||
import os
|
||||
|
||||
# Read monolithic UI Kit
|
||||
with open('tPOS-ui-kit.pen', 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
variables = data['variables']
|
||||
main_page = data['children'][0] # Component Library page
|
||||
sections = main_page['children'] # All sections
|
||||
|
||||
# 1. Extract design tokens
|
||||
tokens_file = {
|
||||
'version': '2.6',
|
||||
'children': [{
|
||||
'type': 'frame',
|
||||
'name': 'Design Tokens Reference',
|
||||
'layout': 'vertical',
|
||||
'gap': 20,
|
||||
'children': [
|
||||
{
|
||||
'type': 'text',
|
||||
'name': 'Title',
|
||||
'content': 'Design System Tokens',
|
||||
'fontSize': 32,
|
||||
'fill': '$text-primary'
|
||||
}
|
||||
]
|
||||
}],
|
||||
'variables': variables
|
||||
}
|
||||
|
||||
with open('src/foundations/design-tokens.pen', 'w') as f:
|
||||
json.dump(tokens_file, f, indent=2)
|
||||
|
||||
# 2. Categorize components by atomic level
|
||||
atomic_mapping = {
|
||||
'atoms': {
|
||||
'patterns': ['button', 'badge', 'input', 'icon', 'typography', 'chip'],
|
||||
'components': []
|
||||
},
|
||||
'molecules': {
|
||||
'patterns': ['formfield', 'searchbar', 'cardheader'],
|
||||
'components': []
|
||||
},
|
||||
'organisms': {
|
||||
'patterns': ['card', 'navigation', 'header', 'footer', 'form'],
|
||||
'components': []
|
||||
}
|
||||
}
|
||||
|
||||
# Categorize each section
|
||||
for section in sections:
|
||||
section_name = section.get('name', '').lower()
|
||||
|
||||
# Determine atomic level
|
||||
matched = False
|
||||
for level, config in atomic_mapping.items():
|
||||
for pattern in config['patterns']:
|
||||
if pattern in section_name:
|
||||
config['components'].append(section)
|
||||
matched = True
|
||||
break
|
||||
if matched:
|
||||
break
|
||||
|
||||
# 3. Generate atomic files
|
||||
for level, config in atomic_mapping.items():
|
||||
if not config['components']:
|
||||
continue
|
||||
|
||||
# Group by component type
|
||||
grouped = {}
|
||||
for component in config['components']:
|
||||
name = component.get('name', '')
|
||||
# Extract type (e.g., "Buttons & Actions Section" -> "buttons")
|
||||
comp_type = name.lower().split()[0].replace('&', '').strip()
|
||||
|
||||
if comp_type not in grouped:
|
||||
grouped[comp_type] = []
|
||||
grouped[comp_type].append(component)
|
||||
|
||||
# Create file for each type
|
||||
for comp_type, components in grouped.items():
|
||||
output_file = {
|
||||
'version': '2.6',
|
||||
'children': components,
|
||||
'variables': variables
|
||||
}
|
||||
|
||||
filepath = f'src/{level}/{comp_type}.pen'
|
||||
with open(filepath, 'w') as f:
|
||||
json.dump(output_file, f, indent=2)
|
||||
|
||||
print(f'✅ Created {filepath} with {len(components)} sections')
|
||||
|
||||
print(f'\n🎉 Atomic Design structure created!')
|
||||
print(f'📁 Foundations: src/foundations/design-tokens.pen')
|
||||
print(f'⚛️ Atoms: {len(atomic_mapping["atoms"]["components"])} files')
|
||||
print(f'🧬 Molecules: {len(atomic_mapping["molecules"]["components"])} files')
|
||||
print(f'🦠 Organisms: {len(atomic_mapping["organisms"]["components"])} files')
|
||||
```
|
||||
|
||||
### Step 4: Create Showcase Files
|
||||
|
||||
```python
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
for atomic_dir in ['src/atoms', 'src/molecules', 'src/organisms']:
|
||||
for filepath in Path(atomic_dir).glob('*.pen'):
|
||||
with open(filepath, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
# Wrap components in a showcase page
|
||||
showcase = {
|
||||
'type': 'frame',
|
||||
'name': f'{filepath.stem.title()} Showcase',
|
||||
'width': 1440,
|
||||
'fill': '$bg-page',
|
||||
'layout': 'vertical',
|
||||
'gap': 80,
|
||||
'padding': [80, 120],
|
||||
'children': data['children']
|
||||
}
|
||||
|
||||
data['children'] = [showcase]
|
||||
|
||||
with open(filepath, 'w') as f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
print(f'✅ Added showcase to {filepath}')
|
||||
```
|
||||
|
||||
### Step 5: Validate Structure
|
||||
|
||||
```python
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
required_structure = [
|
||||
'src/foundations/design-tokens.pen',
|
||||
'src/atoms',
|
||||
'src/molecules',
|
||||
'src/organisms'
|
||||
]
|
||||
|
||||
for path in required_structure:
|
||||
if not os.path.exists(path):
|
||||
print(f'❌ Missing: {path}')
|
||||
else:
|
||||
if path.endswith('.pen'):
|
||||
with open(path, 'r') as f:
|
||||
data = json.load(f)
|
||||
assert 'version' in data
|
||||
assert 'variables' in data
|
||||
print(f'✅ Valid: {path}')
|
||||
else:
|
||||
files = list(Path(path).glob('*.pen'))
|
||||
print(f'✅ {path}: {len(files)} files')
|
||||
|
||||
print('\n🎉 Atomic Design structure validated!')
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Foundations First** - Design tokens (`design-tokens.pen`) is single source of truth
|
||||
2. **Build Bottom-Up** - Atoms → Molecules → Organisms → Templates → Pages
|
||||
3. **Component States** - Every interactive component needs ≥3 states (default/hover/disabled)
|
||||
4. **Naming Consistency** - Strict format: `{Level}/{Component}/{Variant}/{State}`
|
||||
5. **File Organization** - Each atomic level in separate folder (`src/atoms/`, `src/molecules/`)
|
||||
6. **Token Synchronization** - Copy `variables` object from `design-tokens.pen` to all files
|
||||
7. **Showcase Pages** - Each atomic file needs showcase page to demo components
|
||||
8. **No Cross-Contamination** - Atoms don't reference molecules, molecules don't reference organisms
|
||||
9. **Reusable Flag** - Set `"reusable": true` for all reusable components
|
||||
10. **Responsive Variants** - Create variants for Desktop/Tablet/Mobile (not breakpoint logic in Pencil)
|
||||
11. **Documentation** - Document component props, states, usage in USAGE_GUIDE.md
|
||||
12. **Version Control**:
|
||||
- Commit: `src/` (all atomic files)
|
||||
- Gitignore: `build/` (generated files)
|
||||
- Tag releases: `v1.0.0` for breaking changes in design tokens
|
||||
13. **Component Library** - Update atomic files before building pages
|
||||
14. **Validation** - Validate JSON structure after extraction
|
||||
15. **Backup Strategy** - Keep `original.pen.backup` before refactoring to Atomic Design
|
||||
|
||||
## Usage Guide Template
|
||||
|
||||
```markdown
|
||||
# [Project] Design System - Atomic Design Structure
|
||||
|
||||
## Structure Overview
|
||||
|
||||
\`\`\`
|
||||
src/
|
||||
├── foundations/
|
||||
│ └── design-tokens.pen # Colors, typography, spacing, breakpoints
|
||||
├── atoms/ # Smallest building blocks
|
||||
├── molecules/ # Simple combinations
|
||||
├── organisms/ # Complex components
|
||||
└── pages/ # Complete pages
|
||||
\`\`\`
|
||||
|
||||
## Atomic Levels
|
||||
|
||||
### Foundations
|
||||
- **File**: `design-tokens.pen`
|
||||
- **Contains**: CSS variables, color palette, typography scale, spacing system
|
||||
- **Usage**: Copy variables to all component files
|
||||
|
||||
### Atoms
|
||||
- **Examples**: Buttons, inputs, badges, icons, typography
|
||||
- **Characteristics**: Cannot be broken down further
|
||||
- **States**: Default, Hover, Active, Disabled
|
||||
|
||||
### Molecules
|
||||
- **Examples**: Form fields, search bars, card headers
|
||||
- **Characteristics**: Combinations of 2-3 atoms
|
||||
- **Usage**: Import atoms and compose
|
||||
|
||||
### Organisms
|
||||
- **Examples**: Cards, navigation, headers, footers
|
||||
- **Characteristics**: Complex sections with multiple molecules
|
||||
- **Usage**: Complete, reusable UI sections
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Update Foundation**: Edit `design-tokens.pen` for colors/typography changes
|
||||
2. **Sync Tokens**: Copy `variables` object to all modified component files
|
||||
3. **Build Components**: Edit atomic files with proper naming (Atom/Button/Primary/Default)
|
||||
4. **Compose Up**: Molecules use atoms, organisms use molecules
|
||||
5. **Test in Pages**: Reference components in page files
|
||||
6. **Build**: Run `python3 scripts/build.py -m` for monolithic output
|
||||
|
||||
## Component Naming
|
||||
|
||||
\`\`\`
|
||||
{Level}/{Component}/{Variant}/{State}
|
||||
|
||||
Examples:
|
||||
- Atom/Button/Primary/Default
|
||||
- Molecule/FormField/Text/WithError
|
||||
- Organism/Card/Feature/Default
|
||||
\`\`\`
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. ✅ **Design Tokens First**: Always update foundation before components
|
||||
2. ✅ **Build Up**: Atoms → Molecules → Organisms → Pages
|
||||
3. ✅ **Consistent States**: All interactive components need states
|
||||
4. ✅ **Reusable**: Set `"reusable": true` on all shareable components
|
||||
5. ✅ **Version Control**: Commit src/ files, gitignore build/
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- Back to [Pencil Design Skill](../SKILL.md)
|
||||
- [File Format Reference](./FILE_FORMAT.md)
|
||||
- [Build System Reference](./BUILD_SYSTEM.md)
|
||||
- [Code Conversion Reference](./CODE_CONVERSION.md)
|
||||
- [Atomic Design Book by Brad Frost](https://atomicdesign.bradfrost.com/)
|
||||
- [Material Design System](https://m3.material.io/)
|
||||
- [Figma Design Systems](https://www.figma.com/best-practices/design-systems/)
|
||||
424
.agent/skills/pencil-design/references/BUILD_SYSTEM.md
Normal file
424
.agent/skills/pencil-design/references/BUILD_SYSTEM.md
Normal file
@@ -0,0 +1,424 @@
|
||||
# Build System Reference
|
||||
|
||||
Complete guide to the Pencil build system for component linking and monolithic builds.
|
||||
|
||||
## Overview
|
||||
|
||||
The build system enables:
|
||||
- **Component Linking**: Reference components across files
|
||||
- **Monolithic Builds**: Merge all pages + components into one file
|
||||
- **Standard Builds**: Inject components into individual pages
|
||||
- **Validation**: Ensure output structure is valid
|
||||
|
||||
## Configuration
|
||||
|
||||
### pencil.config.json
|
||||
|
||||
Create in project root:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0",
|
||||
"sourceDir": "src",
|
||||
"buildDir": "build",
|
||||
"componentLibrary": "src/components/ui-kit.pen",
|
||||
"buildOptions": {
|
||||
"minify": false,
|
||||
"validateAfterBuild": true,
|
||||
"preserveComments": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `sourceDir` | Source files directory |
|
||||
| `buildDir` | Build output directory |
|
||||
| `componentLibrary` | Path to component library .pen file |
|
||||
| `minify` | Minify JSON output |
|
||||
| `validateAfterBuild` | Validate JSON after build |
|
||||
| `preserveComments` | Keep JSON comments |
|
||||
|
||||
## Build Modes
|
||||
|
||||
### Monolithic Build ⭐ (Recommended)
|
||||
|
||||
Merges all pages + component library into one file.
|
||||
|
||||
**Commands:**
|
||||
```bash
|
||||
python3 scripts/build.py --monolithic
|
||||
python3 scripts/build.py -m
|
||||
python3 scripts/build.py --mode monolithic
|
||||
|
||||
# Custom output name
|
||||
python3 scripts/build.py -m --output myDesign.pen
|
||||
```
|
||||
|
||||
**Output:**
|
||||
- **File**: `tPOS.pen` (project root)
|
||||
- **Contains**: All pages + component library as separate frames
|
||||
- **Size**: ~326KB (example)
|
||||
- **Use case**: Opening complete design, sharing, archiving
|
||||
|
||||
**Example structure:**
|
||||
```json
|
||||
{
|
||||
"version": "2.6",
|
||||
"children": [
|
||||
{
|
||||
"type": "frame",
|
||||
"name": "Component Library",
|
||||
"children": [ /* All reusable components */ ]
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"name": "Desktop Home Page",
|
||||
"children": [ /* Page content */ ]
|
||||
},
|
||||
{
|
||||
"type": "frame",
|
||||
"name": "Mobile Home Page",
|
||||
"children": [ /* Page content */ ]
|
||||
}
|
||||
],
|
||||
"variables": { /* Design tokens */ }
|
||||
}
|
||||
```
|
||||
|
||||
### Standard Build
|
||||
|
||||
Builds individual pages with component injection.
|
||||
|
||||
**Command:**
|
||||
```bash
|
||||
python3 scripts/build.py
|
||||
python3 scripts/build.py --mode standard
|
||||
```
|
||||
|
||||
**Output:**
|
||||
- **Directory**: `build/`
|
||||
- **Files**: Separate `.pen` files with injected components
|
||||
- **Use case**: Development workflow, testing component refs
|
||||
|
||||
**Example:**
|
||||
```
|
||||
build/
|
||||
├── desktop-home.pen # With injected components
|
||||
├── mobile-home.pen # With injected components
|
||||
└── tablet-home.pen # With injected components
|
||||
```
|
||||
|
||||
## Build Process
|
||||
|
||||
### What the Build Script Does
|
||||
|
||||
1. **Load Component Library**: Read `ui-kit.pen`
|
||||
2. **Extract Components**: Find all `reusable: true` components
|
||||
3. **Extract Variables**: Get design tokens
|
||||
4. **Find Component Refs**: Search for `component_ref` types in pages
|
||||
5. **Resolve Paths**: Match `ui-kit#Button/Primary` to actual component
|
||||
6. **Inject Components**: Add component definitions to pages
|
||||
7. **Convert Refs**: Transform `component_ref` → `ref` with proper IDs
|
||||
8. **Validate**: Check output structure
|
||||
|
||||
### Transformation Example
|
||||
|
||||
**Before build (source file):**
|
||||
```json
|
||||
{
|
||||
"type": "component_ref",
|
||||
"component": "ui-kit#Button/Primary",
|
||||
"name": "submitButton",
|
||||
"overrides": {
|
||||
"label_id": {
|
||||
"content": "Submit"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**After build (output file):**
|
||||
|
||||
Step 1: Component injected at top of children array
|
||||
```json
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "abc123",
|
||||
"name": "Button/Primary",
|
||||
"reusable": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "label_id",
|
||||
"content": "Button"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Step 2: Reference converted
|
||||
```json
|
||||
{
|
||||
"type": "ref",
|
||||
"ref": "abc123",
|
||||
"name": "submitButton",
|
||||
"descendants": {
|
||||
"label_id": {
|
||||
"content": "Submit"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
### Recommended Structure
|
||||
|
||||
```
|
||||
project-name/
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ │ └── ui-kit.pen # Component library + design tokens
|
||||
│ └── pages/
|
||||
│ ├── desktop-home.pen # Desktop (1440px)
|
||||
│ ├── mobile-home.pen # Mobile (390px)
|
||||
│ └── tablet-home.pen # Tablet (768px)
|
||||
├── build/ # Standard build output (gitignore)
|
||||
│ ├── desktop-home.pen # With injected components
|
||||
│ └── ...
|
||||
├── scripts/
|
||||
│ └── build.py # Build script
|
||||
├── pencil.config.json # Build configuration
|
||||
├── tPOS.pen # Monolithic build output
|
||||
└── USAGE_GUIDE.md # Documentation
|
||||
```
|
||||
|
||||
### Atomic Design Structure
|
||||
|
||||
For enterprise-scale UI Kits:
|
||||
|
||||
```
|
||||
project-name/
|
||||
├── src/
|
||||
│ ├── foundations/
|
||||
│ │ └── design-tokens.pen # Design system variables
|
||||
│ ├── atoms/ # Smallest building blocks
|
||||
│ │ ├── buttons.pen
|
||||
│ │ ├── inputs.pen
|
||||
│ │ └── badges.pen
|
||||
│ ├── molecules/ # Combinations of atoms
|
||||
│ │ ├── form-fields.pen
|
||||
│ │ └── search-bar.pen
|
||||
│ ├── organisms/ # Complex UI sections
|
||||
│ │ ├── cards.pen
|
||||
│ │ └── navigation.pen
|
||||
│ └── pages/ # Complete pages
|
||||
│ ├── desktop-home.pen
|
||||
│ └── mobile-home.pen
|
||||
├── build/
|
||||
├── scripts/
|
||||
│ └── build.py
|
||||
├── pencil.config.json
|
||||
└── tPOS.pen
|
||||
```
|
||||
|
||||
## Build Commands Reference
|
||||
|
||||
### Basic Commands
|
||||
|
||||
```bash
|
||||
# Help
|
||||
python3 scripts/build.py --help
|
||||
|
||||
# Monolithic build
|
||||
python3 scripts/build.py -m
|
||||
|
||||
# Standard build
|
||||
python3 scripts/build.py
|
||||
|
||||
# Custom output
|
||||
python3 scripts/build.py -m --output custom.pen
|
||||
```
|
||||
|
||||
### Validation Commands
|
||||
|
||||
```bash
|
||||
# Validate JSON
|
||||
jq empty tPOS.pen && echo "✅ Valid JSON"
|
||||
|
||||
# Check structure
|
||||
jq '{version, childrenCount: (.children | length)}' tPOS.pen
|
||||
|
||||
# List frame names
|
||||
jq '[.children[].name]' tPOS.pen
|
||||
|
||||
# Extract design tokens
|
||||
jq '.variables' tPOS.pen
|
||||
|
||||
# Count components
|
||||
jq '[.children[0].children[] | select(.reusable == true)] | length' tPOS.pen
|
||||
```
|
||||
|
||||
## Workflows
|
||||
|
||||
### Development Cycle
|
||||
|
||||
```markdown
|
||||
1. Design in modular files:
|
||||
- Edit components in `src/components/ui-kit.pen`
|
||||
- Edit pages in `src/pages/*.pen`
|
||||
- Use `component_ref` to reference components
|
||||
|
||||
2. Build for testing:
|
||||
python3 scripts/build.py # Standard build
|
||||
# Open files in build/ to test
|
||||
|
||||
3. Build for production:
|
||||
python3 scripts/build.py -m # Monolithic
|
||||
# Share tPOS.pen with team/clients
|
||||
|
||||
4. Convert to code:
|
||||
# Use agent to convert tPOS.pen to HTML/CSS/React/Blazor
|
||||
```
|
||||
|
||||
### Component Library Updates
|
||||
|
||||
```markdown
|
||||
1. Update component in ui-kit.pen
|
||||
2. Save file
|
||||
3. Rebuild all pages:
|
||||
python3 scripts/build.py -m
|
||||
4. Test in Pencil app
|
||||
5. If OK, commit changes
|
||||
```
|
||||
|
||||
### Adding New Components
|
||||
|
||||
```markdown
|
||||
1. Open ui-kit.pen in Pencil
|
||||
2. Create component with:
|
||||
- Unique ID
|
||||
- Descriptive name (e.g., "Button/Secondary")
|
||||
- `reusable: true`
|
||||
3. Save file
|
||||
4. Use in pages with component_ref:
|
||||
{
|
||||
"type": "component_ref",
|
||||
"component": "ui-kit#Button/Secondary"
|
||||
}
|
||||
5. Build and test
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Component References
|
||||
|
||||
```markdown
|
||||
✅ DO: Use component_ref in source files
|
||||
❌ DON'T: Hardcode components into pages
|
||||
|
||||
✅ DO: Keep components in ui-kit.pen
|
||||
❌ DON'T: Duplicate components across files
|
||||
```
|
||||
|
||||
### 2. Build Before Testing
|
||||
|
||||
```markdown
|
||||
✅ DO: Build before opening in Pencil
|
||||
❌ DON'T: Open source files directly
|
||||
|
||||
# Correct workflow:
|
||||
python3 scripts/build.py -m
|
||||
open tPOS.pen
|
||||
|
||||
# Component refs will be resolved ✅
|
||||
```
|
||||
|
||||
### 3. Version Control
|
||||
|
||||
```markdown
|
||||
✅ DO: Commit source files (src/)
|
||||
✅ DO: Gitignore build output (build/, tPOS.pen)
|
||||
|
||||
# .gitignore
|
||||
build/
|
||||
tPOS.pen
|
||||
*.pen.backup
|
||||
```
|
||||
|
||||
### 4. Validation
|
||||
|
||||
```markdown
|
||||
✅ DO: Validate after build
|
||||
❌ DON'T: Skip validation
|
||||
|
||||
# Always validate:
|
||||
jq empty tPOS.pen && echo "✅ Valid"
|
||||
```
|
||||
|
||||
### 5. Configuration Consistency
|
||||
|
||||
```markdown
|
||||
✅ DO: Keep pencil.config.json in sync
|
||||
❌ DON'T: Hardcode paths in scripts
|
||||
|
||||
# Update config when structure changes
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Script Errors
|
||||
|
||||
**Error: Component library not found**
|
||||
```bash
|
||||
# Check config
|
||||
cat pencil.config.json
|
||||
|
||||
# Verify path exists
|
||||
ls -la src/components/ui-kit.pen
|
||||
```
|
||||
|
||||
**Error: Invalid JSON in source file**
|
||||
```bash
|
||||
# Validate source files
|
||||
jq empty src/components/ui-kit.pen
|
||||
jq empty src/pages/desktop-home.pen
|
||||
```
|
||||
|
||||
**Error: Component ref not resolved**
|
||||
```bash
|
||||
# Check component exists in library
|
||||
jq '[.children[0].children[] | select(.name == "Button/Primary")]' src/components/ui-kit.pen
|
||||
|
||||
# Check component path syntax
|
||||
# Correct: "ui-kit#Button/Primary"
|
||||
# Wrong: "Button/Primary" (missing library prefix)
|
||||
```
|
||||
|
||||
### Output Validation
|
||||
|
||||
```bash
|
||||
# Check file was created
|
||||
ls -lh tPOS.pen
|
||||
|
||||
# Validate JSON structure
|
||||
jq . tPOS.pen > /dev/null && echo "✅ Valid"
|
||||
|
||||
# Check version
|
||||
jq '.version' tPOS.pen
|
||||
|
||||
# Count frames
|
||||
jq '.children | length' tPOS.pen
|
||||
|
||||
# List frame names
|
||||
jq '[.children[].name]' tPOS.pen
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- Back to [Pencil Design Skill](../SKILL.md)
|
||||
- [File Format Reference](./FILE_FORMAT.md)
|
||||
- [Atomic Design Reference](./ATOMIC_DESIGN.md)
|
||||
- [Code Conversion Reference](./CODE_CONVERSION.md)
|
||||
- [Workflow: /pencil-build](../../../.agent/workflows/pencil-build.md)
|
||||
616
.agent/skills/pencil-design/references/CODE_CONVERSION.md
Normal file
616
.agent/skills/pencil-design/references/CODE_CONVERSION.md
Normal file
@@ -0,0 +1,616 @@
|
||||
# Code Conversion Reference
|
||||
|
||||
Convert Pencil designs to production code (HTML/CSS/React/Blazor).
|
||||
|
||||
## Overview
|
||||
|
||||
This guide covers converting `.pen` files to production code with proper structure, responsive design, and design system integration.
|
||||
|
||||
## Element Mapping
|
||||
|
||||
### Pencil to HTML/CSS
|
||||
|
||||
| Pencil Element | HTML | CSS Layout | Notes |
|
||||
|----------------|------|------------|-------|
|
||||
| `frame` (layout:vertical) | `<div>` | `flex-direction: column` | Flexbox container |
|
||||
| `frame` (layout:horizontal) | `<div>` | `flex-direction: row` | Flexbox container |
|
||||
| `frame` (layout:none) | `<div>` | `position: relative` | Free positioning |
|
||||
| `text` | `<p>`, `<h1>`-`<h6>`, `<span>` | - | Based on fontSize |
|
||||
| `rectangle` | `<div>` | - | With background |
|
||||
| `ellipse` | `<div>` | `border-radius: 50%` | Circular shape |
|
||||
| `icon_font` | `<i class="lucide-{name}">` | - | Lucide icons |
|
||||
|
||||
### Pencil to React/Blazor
|
||||
|
||||
| Pencil | React | Blazor |
|
||||
|--------|-------|--------|
|
||||
| `frame` | `<div className="">` | `<div class="">` |
|
||||
| `text` | `<p>{content}</p>` | `<p>@content</p>` |
|
||||
| `icon_font` | `<Icon name={iconName} />` | `<i class="lucide-@iconName"></i>` |
|
||||
| Component ref | `<Button>` | `<Button />` |
|
||||
|
||||
## Property Mapping
|
||||
|
||||
### Layout Properties → CSS
|
||||
|
||||
```javascript
|
||||
// Pencil layout properties
|
||||
{
|
||||
"layout": "vertical",
|
||||
"gap": 16,
|
||||
"padding": [24, 32],
|
||||
"justifyContent": "center",
|
||||
"alignItems": "center"
|
||||
}
|
||||
|
||||
// CSS output
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
padding: 24px 32px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
```
|
||||
|
||||
### Size Properties → CSS
|
||||
|
||||
```javascript
|
||||
// Fixed size
|
||||
{ "width": 400, "height": 200 }
|
||||
// CSS
|
||||
.element { width: 400px; height: 200px; }
|
||||
|
||||
// Fill container
|
||||
{ "width": "fill_container", "height": 200 }
|
||||
// CSS
|
||||
.element { width: 100%; height: 200px; }
|
||||
```
|
||||
|
||||
### Color Properties → CSS
|
||||
|
||||
```javascript
|
||||
// Solid color
|
||||
{ "fill": "#FF5C00" }
|
||||
// CSS
|
||||
.element { background: #FF5C00; }
|
||||
|
||||
// Design token
|
||||
{ "fill": "$bg-page" }
|
||||
// CSS
|
||||
.element { background: var(--bg-page); }
|
||||
|
||||
// Gradient
|
||||
{
|
||||
"fill": {
|
||||
"type": "gradient",
|
||||
"colors": [
|
||||
{"color": "#FF5C00", "position": 0},
|
||||
{"color": "#FF8A4C", "position": 1}
|
||||
]
|
||||
}
|
||||
}
|
||||
// CSS
|
||||
.element {
|
||||
background: linear-gradient(180deg, #FF5C00 0%, #FF8A4C 100%);
|
||||
}
|
||||
```
|
||||
|
||||
### Border Properties → CSS
|
||||
|
||||
```javascript
|
||||
// Corner radius
|
||||
{ "cornerRadius": 10 }
|
||||
// CSS
|
||||
.element { border-radius: 10px; }
|
||||
|
||||
// Stroke
|
||||
{ "stroke": "$border-default", "strokeWidth": 2 }
|
||||
// CSS
|
||||
.element { border: 2px solid var(--border-default); }
|
||||
```
|
||||
|
||||
## Conversion Workflow
|
||||
|
||||
### Step 1: Extract Design Tokens
|
||||
|
||||
```javascript
|
||||
import pencilData from './design.pen';
|
||||
|
||||
// Extract variables
|
||||
const tokens = pencilData.variables;
|
||||
|
||||
// Generate CSS Variables
|
||||
const cssTokens = Object.entries(tokens)
|
||||
.map(([key, value]) => {
|
||||
if (value.type === 'color') {
|
||||
return ` --${key}: ${value.value};`;
|
||||
}
|
||||
if (value.type === 'number') {
|
||||
return ` --${key}: ${value.value}px;`;
|
||||
}
|
||||
return ` --${key}: ${value.value};`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
console.log(`:root {\n${cssTokens}\n}`);
|
||||
```
|
||||
|
||||
**Output:**
|
||||
```css
|
||||
:root {
|
||||
--bg-page: #0A0A0B;
|
||||
--text-primary: #FFFFFF;
|
||||
--orange-primary: #FF5C00;
|
||||
--space-4: 16px;
|
||||
--radius-md: 10px;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Convert Layout Structure
|
||||
|
||||
```javascript
|
||||
function convertElement(element) {
|
||||
if (element.type === 'frame') {
|
||||
return convertFrame(element);
|
||||
}
|
||||
if (element.type === 'text') {
|
||||
return convertText(element);
|
||||
}
|
||||
if (element.type === 'icon_font') {
|
||||
return convertIcon(element);
|
||||
}
|
||||
if (element.type === 'ref') {
|
||||
return convertRef(element);
|
||||
}
|
||||
}
|
||||
|
||||
function convertFrame(frame) {
|
||||
const className = frame.name.toLowerCase().replace(/\s+/g, '-');
|
||||
const children = frame.children?.map(convertElement).join('\n') || '';
|
||||
|
||||
return `<div class="${className}">\n${children}\n</div>`;
|
||||
}
|
||||
|
||||
function convertText(text) {
|
||||
const tag = getTextTag(text.fontSize);
|
||||
return `<${tag}>${text.content}</${tag}>`;
|
||||
}
|
||||
|
||||
function getTextTag(fontSize) {
|
||||
if (fontSize >= 32) return 'h1';
|
||||
if (fontSize >= 24) return 'h2';
|
||||
if (fontSize >= 18) return 'h3';
|
||||
if (fontSize >= 16) return 'h4';
|
||||
return 'p';
|
||||
}
|
||||
|
||||
function convertIcon(icon) {
|
||||
return `<i class="lucide-${icon.iconFontName}"></i>`;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Generate CSS Classes
|
||||
|
||||
```javascript
|
||||
function generateCSS(element, className) {
|
||||
const styles = [];
|
||||
|
||||
// Layout
|
||||
if (element.layout) {
|
||||
styles.push('display: flex');
|
||||
styles.push(`flex-direction: ${element.layout === 'vertical' ? 'column' : 'row'}`);
|
||||
}
|
||||
|
||||
// Gap
|
||||
if (element.gap) {
|
||||
styles.push(`gap: ${element.gap}px`);
|
||||
}
|
||||
|
||||
// Padding
|
||||
if (element.padding) {
|
||||
const padding = Array.isArray(element.padding)
|
||||
? `${element.padding[0]}px ${element.padding[1]}px`
|
||||
: `${element.padding}px`;
|
||||
styles.push(`padding: ${padding}`);
|
||||
}
|
||||
|
||||
// Size
|
||||
if (element.width) {
|
||||
const width = element.width === 'fill_container' ? '100%' : `${element.width}px`;
|
||||
styles.push(`width: ${width}`);
|
||||
}
|
||||
if (element.height) {
|
||||
styles.push(`height: ${element.height}px`);
|
||||
}
|
||||
|
||||
// Fill (background)
|
||||
if (element.fill) {
|
||||
if (element.fill.startsWith('$')) {
|
||||
// Design token
|
||||
const token = element.fill.substring(1);
|
||||
styles.push(`background: var(--${token})`);
|
||||
} else if (typeof element.fill === 'object' && element.fill.type === 'gradient') {
|
||||
// Gradient
|
||||
const gradient = convertGradient(element.fill);
|
||||
styles.push(`background: ${gradient}`);
|
||||
} else {
|
||||
// Solid color
|
||||
styles.push(`background: ${element.fill}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Border radius
|
||||
if (element.cornerRadius) {
|
||||
styles.push(`border-radius: ${element.cornerRadius}px`);
|
||||
}
|
||||
|
||||
// Alignment
|
||||
if (element.justifyContent) {
|
||||
const value = element.justifyContent.replace('_', '-');
|
||||
styles.push(`justify-content: ${value}`);
|
||||
}
|
||||
if (element.alignItems) {
|
||||
styles.push(`align-items: ${element.alignItems}`);
|
||||
}
|
||||
|
||||
return `.${className} {\n ${styles.join(';\n ')};\n}`;
|
||||
}
|
||||
|
||||
function convertGradient(fill) {
|
||||
const colors = fill.colors
|
||||
.map(c => `${c.color} ${c.position * 100}%`)
|
||||
.join(', ');
|
||||
return `linear-gradient(180deg, ${colors})`;
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Semantic HTML
|
||||
|
||||
```html
|
||||
<!-- ❌ BAD: All divs -->
|
||||
<div class="page">
|
||||
<div class="header">...</div>
|
||||
<div class="content">...</div>
|
||||
<div class="footer">...</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ GOOD: Semantic elements -->
|
||||
<main class="page">
|
||||
<header class="header">...</header>
|
||||
<section class="content">...</section>
|
||||
<footer class="footer">...</footer>
|
||||
</main>
|
||||
```
|
||||
|
||||
### 2. CSS Variables for Tokens
|
||||
|
||||
```css
|
||||
/* ❌ BAD: Hardcoded colors */
|
||||
.button {
|
||||
background: #FF5C00;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* ✅ GOOD: CSS variables */
|
||||
.button {
|
||||
background: var(--orange-primary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Mobile-First Responsive
|
||||
|
||||
```css
|
||||
/* ✅ GOOD: Mobile-first */
|
||||
.container {
|
||||
padding: 16px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
padding: 24px;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.container {
|
||||
padding: 80px 120px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Component Structure
|
||||
|
||||
```jsx
|
||||
// ✅ GOOD: Match Pencil component hierarchy
|
||||
// Atom/Button/Primary/Default → Button.jsx
|
||||
|
||||
function Button({ children, variant = 'primary', state = 'default' }) {
|
||||
return (
|
||||
<button className={`button button--${variant} button--${state}`}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// CSS
|
||||
.button {
|
||||
/* Base styles */
|
||||
}
|
||||
.button--primary {
|
||||
background: var(--orange-primary);
|
||||
}
|
||||
.button--default {
|
||||
/* Default state */
|
||||
}
|
||||
.button--hover {
|
||||
background: var(--orange-light);
|
||||
}
|
||||
```
|
||||
|
||||
## Framework-Specific Examples
|
||||
|
||||
### React Component
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import './HeroSection.css';
|
||||
|
||||
export function HeroSection({ title, subtitle, ctaText }) {
|
||||
return (
|
||||
<section className="hero-section">
|
||||
<div className="hero-content">
|
||||
<h1 className="hero-title">{title}</h1>
|
||||
<p className="hero-subtitle">{subtitle}</p>
|
||||
<button className="hero-cta">{ctaText}</button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```css
|
||||
/* HeroSection.css */
|
||||
.hero-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 80px 120px;
|
||||
background: var(--bg-page);
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
max-width: 800px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 18px;
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.hero-cta {
|
||||
padding: 16px 32px;
|
||||
background: var(--orange-primary);
|
||||
color: var(--text-primary);
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hero-cta:hover {
|
||||
background: var(--orange-light);
|
||||
}
|
||||
```
|
||||
|
||||
### Blazor Component
|
||||
|
||||
```razor
|
||||
@* HeroSection.razor *@
|
||||
<section class="hero-section">
|
||||
<div class="hero-content">
|
||||
<h1 class="hero-title">@Title</h1>
|
||||
<p class="hero-subtitle">@Subtitle</p>
|
||||
<button class="hero-cta" @onclick="OnCtaClick">@CtaText</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@code {
|
||||
[Parameter] public string Title { get; set; } = "";
|
||||
[Parameter] public string Subtitle { get; set; } = "";
|
||||
[Parameter] public string CtaText { get; set; } = "";
|
||||
[Parameter] public EventCallback OnCtaClick { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
### Tailwind CSS
|
||||
|
||||
From design tokens to Tailwind config:
|
||||
|
||||
```javascript
|
||||
// tailwind.config.js
|
||||
module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'bg-page': '#0A0A0B',
|
||||
'bg-surface': '#1A1A1C',
|
||||
'text-primary': '#FFFFFF',
|
||||
'orange-primary': '#FF5C00',
|
||||
},
|
||||
spacing: {
|
||||
'1': '4px',
|
||||
'2': '8px',
|
||||
'4': '16px',
|
||||
'6': '24px',
|
||||
},
|
||||
borderRadius: {
|
||||
'sm': '6px',
|
||||
'md': '10px',
|
||||
'lg': '16px',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```jsx
|
||||
// Component with Tailwind
|
||||
<section className="flex flex-col items-center px-30 py-20 bg-bg-page">
|
||||
<div className="max-w-3xl text-center">
|
||||
<h1 className="text-5xl font-bold text-text-primary mb-4">
|
||||
{title}
|
||||
</h1>
|
||||
<p className="text-lg text-text-secondary mb-8">
|
||||
{subtitle}
|
||||
</p>
|
||||
<button className="px-8 py-4 bg-orange-primary text-text-primary rounded-md hover:bg-orange-light">
|
||||
{ctaText}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
## Conversion Tools
|
||||
|
||||
### Automated Conversion Script
|
||||
|
||||
```javascript
|
||||
import fs from 'fs';
|
||||
|
||||
function convertPencilToHTML(pencilFile) {
|
||||
const data = JSON.parse(fs.readFileSync(pencilFile, 'utf-8'));
|
||||
|
||||
// Extract main page
|
||||
const mainPage = data.children[0];
|
||||
|
||||
// Generate HTML
|
||||
const html = convertElement(mainPage);
|
||||
|
||||
// Generate CSS
|
||||
const css = generateCSSForAll(mainPage);
|
||||
|
||||
// Generate design tokens
|
||||
const tokens = generateTokensCSS(data.variables);
|
||||
|
||||
return {
|
||||
html,
|
||||
css: `${tokens}\n\n${css}`,
|
||||
tokens: data.variables
|
||||
};
|
||||
}
|
||||
|
||||
// Example usage
|
||||
const result = convertPencilToHTML('design.pen');
|
||||
fs.writeFileSync('output.html', result.html);
|
||||
fs.writeFileSync('output.css', result.css);
|
||||
console.log('✅ Conversion complete!');
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Navigation Bar
|
||||
|
||||
```html
|
||||
<nav class="navbar">
|
||||
<div class="navbar-logo">
|
||||
<img src="logo.svg" alt="Logo" />
|
||||
</div>
|
||||
<ul class="navbar-menu">
|
||||
<li><a href="#home">Home</a></li>
|
||||
<li><a href="#features">Features</a></li>
|
||||
<li><a href="#pricing">Pricing</a></li>
|
||||
</ul>
|
||||
<button class="navbar-cta">Sign Up</button>
|
||||
</nav>
|
||||
```
|
||||
|
||||
```css
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 120px;
|
||||
background: var(--bg-surface);
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.navbar-menu a {
|
||||
color: var(--text-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
```
|
||||
|
||||
### Card Component
|
||||
|
||||
```html
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<i class="lucide-star"></i>
|
||||
<h3>Feature Title</h3>
|
||||
</div>
|
||||
<p class="card-description">
|
||||
Feature description text goes here.
|
||||
</p>
|
||||
<a href="#" class="card-link">Learn More →</a>
|
||||
</div>
|
||||
```
|
||||
|
||||
```css
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 24px;
|
||||
background: var(--bg-surface);
|
||||
border-radius: var(--radius-md);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.card-description {
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.card-link {
|
||||
color: var(--orange-primary);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- Back to [Pencil Design Skill](../SKILL.md)
|
||||
- [File Format Reference](./FILE_FORMAT.md)
|
||||
- [Build System Reference](./BUILD_SYSTEM.md)
|
||||
- [Atomic Design Reference](./ATOMIC_DESIGN.md)
|
||||
- [Tailwind Design System](../tailwind-design-system/SKILL.md)
|
||||
- [React UI Components](../react-ui-components/SKILL.md)
|
||||
- [Blazor Theme Patterns](../blazor-theme-patterns/SKILL.md)
|
||||
425
.agent/skills/pencil-design/references/FILE_FORMAT.md
Normal file
425
.agent/skills/pencil-design/references/FILE_FORMAT.md
Normal file
@@ -0,0 +1,425 @@
|
||||
# Pencil File Format Reference
|
||||
|
||||
Complete technical reference for `.pen` file structure, element types, and properties.
|
||||
|
||||
## JSON Structure
|
||||
|
||||
### Root Object
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "2.6",
|
||||
"children": [ /* Page frames and components */ ],
|
||||
"variables": { /* Design tokens */ }
|
||||
}
|
||||
```
|
||||
|
||||
## Element Types
|
||||
|
||||
### Frame
|
||||
|
||||
Container for layouts and grouping.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "unique_id",
|
||||
"name": "Frame Name",
|
||||
"width": 400,
|
||||
"height": 300,
|
||||
"fill": "#FFFFFF",
|
||||
"layout": "vertical",
|
||||
"gap": 16,
|
||||
"padding": [16, 24],
|
||||
"justifyContent": "center",
|
||||
"alignItems": "center",
|
||||
"children": [],
|
||||
"reusable": true
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Type | Values | Description |
|
||||
|----------|------|--------|-------------|
|
||||
| `layout` | string | `vertical`, `horizontal`, `none` | Flexbox direction |
|
||||
| `gap` | number | pixels | Spacing between children |
|
||||
| `padding` | number/array | `16` or `[vertical, horizontal]` | Internal padding |
|
||||
| `justifyContent` | string | `start`, `center`, `end`, `space_between` | Main axis alignment |
|
||||
| `alignItems` | string | `start`, `center`, `end` | Cross axis alignment |
|
||||
| `reusable` | boolean | `true`/`false` | Can be referenced by other elements |
|
||||
|
||||
### Text
|
||||
|
||||
Text element with typography properties.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "text",
|
||||
"id": "text_id",
|
||||
"name": "Label",
|
||||
"content": "Hello World",
|
||||
"fontSize": 16,
|
||||
"fontFamily": "Roboto",
|
||||
"fontWeight": "500",
|
||||
"fill": "$text-primary",
|
||||
"textAlign": "left"
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Type | Values | Description |
|
||||
|----------|------|--------|-------------|
|
||||
| `content` | string | Any text | Text content |
|
||||
| `fontSize` | number | pixels | Font size |
|
||||
| `fontFamily` | string | Font name | e.g., "Roboto", "Inter" |
|
||||
| `fontWeight` | string | `"400"`, `"500"`, `"600"`, `"700"` | Font weight |
|
||||
| `fill` | string | Color/variable | Text color |
|
||||
| `textAlign` | string | `left`, `center`, `right` | Text alignment |
|
||||
|
||||
### Rectangle
|
||||
|
||||
Rectangular shape.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "rectangle",
|
||||
"id": "rect_id",
|
||||
"name": "Box",
|
||||
"width": 200,
|
||||
"height": 100,
|
||||
"fill": "$bg-surface",
|
||||
"cornerRadius": 10
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `cornerRadius` | number | Border radius in pixels |
|
||||
| `fill` | string/object | Solid color, variable, or gradient |
|
||||
|
||||
### Ellipse
|
||||
|
||||
Circle or ellipse shape.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "ellipse",
|
||||
"id": "ellipse_id",
|
||||
"name": "Circle",
|
||||
"width": 48,
|
||||
"height": 48,
|
||||
"fill": "$orange-primary"
|
||||
}
|
||||
```
|
||||
|
||||
### Icon Font
|
||||
|
||||
Icon from Lucide icon set.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "icon_font",
|
||||
"id": "icon_id",
|
||||
"name": "Search Icon",
|
||||
"iconFontName": "search",
|
||||
"iconFontFamily": "Lucide",
|
||||
"fill": "$text-secondary",
|
||||
"fontSize": 20
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Description |
|
||||
|----------|-------------|
|
||||
| `iconFontName` | Lucide icon name (e.g., "search", "user", "menu") |
|
||||
| `iconFontFamily` | Always "Lucide" |
|
||||
|
||||
### Ref (Component Instance)
|
||||
|
||||
Reference to a reusable component in the same file.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "ref",
|
||||
"ref": "component_id",
|
||||
"name": "myButton",
|
||||
"descendants": {
|
||||
"child_element_id": {
|
||||
"content": "Click Me"
|
||||
}
|
||||
},
|
||||
"x": 100,
|
||||
"y": 200
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Description |
|
||||
|----------|-------------|
|
||||
| `ref` | ID of the component being referenced |
|
||||
| `descendants` | Property overrides for child elements |
|
||||
|
||||
**Descendants structure:**
|
||||
```json
|
||||
"descendants": {
|
||||
"element_id_to_override": {
|
||||
"property_name": "new_value"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Component Ref (External Reference)
|
||||
|
||||
Custom type for build system - references components from external files.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "component_ref",
|
||||
"component": "ui-kit#Button/Primary",
|
||||
"name": "myButton",
|
||||
"overrides": {
|
||||
"label_id": {
|
||||
"content": "Click Me"
|
||||
}
|
||||
},
|
||||
"x": 100,
|
||||
"y": 200
|
||||
}
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> `component_ref` is processed by the build system and converted to standard `ref` type during build.
|
||||
|
||||
## Size Properties
|
||||
|
||||
### Fixed Size
|
||||
|
||||
```json
|
||||
{
|
||||
"width": 400,
|
||||
"height": 200
|
||||
}
|
||||
```
|
||||
|
||||
### Fill Container
|
||||
|
||||
```json
|
||||
{
|
||||
"width": "fill_container", // 100% of parent width
|
||||
"height": 200
|
||||
}
|
||||
```
|
||||
|
||||
## Fill Properties
|
||||
|
||||
### Solid Color
|
||||
|
||||
```json
|
||||
{
|
||||
"fill": "#FF5C00"
|
||||
}
|
||||
```
|
||||
|
||||
### Design Token Variable
|
||||
|
||||
```json
|
||||
{
|
||||
"fill": "$bg-page"
|
||||
}
|
||||
```
|
||||
|
||||
### Gradient
|
||||
|
||||
```json
|
||||
{
|
||||
"fill": {
|
||||
"type": "gradient",
|
||||
"colors": [
|
||||
{"color": "#FF5C00", "position": 0},
|
||||
{"color": "#FF8A4C", "position": 1}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Design Tokens / Variables
|
||||
|
||||
Variables are defined in the root `variables` object:
|
||||
|
||||
```json
|
||||
"variables": {
|
||||
"bg-page": {
|
||||
"type": "color",
|
||||
"value": "#0A0A0B"
|
||||
},
|
||||
"text-primary": {
|
||||
"type": "color",
|
||||
"value": "#FFFFFF"
|
||||
},
|
||||
"orange-primary": {
|
||||
"type": "color",
|
||||
"value": "#FF5C00"
|
||||
},
|
||||
"font-primary": {
|
||||
"type": "string",
|
||||
"value": "Roboto"
|
||||
},
|
||||
"space-4": {
|
||||
"type": "number",
|
||||
"value": 16
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Variable Types
|
||||
|
||||
| Type | Purpose | Example Value |
|
||||
|------|---------|---------------|
|
||||
| `color` | Colors | `"#FF5C00"` |
|
||||
| `string` | Font names, text | `"Roboto"` |
|
||||
| `number` | Spacing, sizes | `16` |
|
||||
|
||||
### Using Variables in Elements
|
||||
|
||||
Reference variables with `$` prefix:
|
||||
|
||||
```json
|
||||
{
|
||||
"fill": "$bg-page",
|
||||
"fontSize": "$text-lg",
|
||||
"fontFamily": "$font-primary"
|
||||
}
|
||||
```
|
||||
|
||||
### Common Token Categories
|
||||
|
||||
| Category | Prefix | Examples |
|
||||
|----------|--------|----------|
|
||||
| **Background** | `bg-` | `$bg-page`, `$bg-surface`, `$bg-elevated` |
|
||||
| **Text** | `text-` | `$text-primary`, `$text-secondary`, `$text-tertiary` |
|
||||
| **Border** | `border-` | `$border-default`, `$border-subtle` |
|
||||
| **Brand Colors** | `{color}-` | `$orange-primary`, `$green-success`, `$red-error` |
|
||||
| **Typography Sizes** | `text-` | `$text-xs`, `$text-sm`, `$text-base`, `$text-lg` |
|
||||
| **Spacing** | `space-` | `$space-2`, `$space-4`, `$space-6` |
|
||||
| **Border Radius** | `radius-` | `$radius-sm`, `$radius-md`, `$radius-lg` |
|
||||
| **Breakpoints** | `breakpoint-` | `$breakpoint-mobile`, `$breakpoint-tablet` |
|
||||
|
||||
## Component System
|
||||
|
||||
### Defining Reusable Components
|
||||
|
||||
Set `reusable: true` on frames to make them reusable:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "frame",
|
||||
"id": "btn_primary_001",
|
||||
"name": "Button/Primary",
|
||||
"reusable": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "btn_label_001",
|
||||
"content": "Button",
|
||||
"fontSize": 14
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Using Internal References
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "ref",
|
||||
"ref": "btn_primary_001",
|
||||
"name": "submitButton",
|
||||
"descendants": {
|
||||
"btn_label_001": {
|
||||
"content": "Submit"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using External References (Build System)
|
||||
|
||||
In source files, use `component_ref`:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "component_ref",
|
||||
"component": "ui-kit#Button/Primary",
|
||||
"name": "submitButton",
|
||||
"overrides": {
|
||||
"btn_label_001": {
|
||||
"content": "Submit"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
During build, this becomes:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "ref",
|
||||
"ref": "btn_primary_001",
|
||||
"descendants": {
|
||||
"btn_label_001": {
|
||||
"content": "Submit"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Property Reference
|
||||
|
||||
### Common Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `type` | string | Element type |
|
||||
| `id` | string | Unique identifier |
|
||||
| `name` | string | Display name |
|
||||
| `x` | number | X position |
|
||||
| `y` | number | Y position |
|
||||
| `width` | number/string | Width in pixels or "fill_container" |
|
||||
| `height` | number | Height in pixels |
|
||||
| `fill` | string/object | Background/text color |
|
||||
| `opacity` | number | 0.0 to 1.0 |
|
||||
| `visible` | boolean | Visibility |
|
||||
| `locked` | boolean | Whether element is locked |
|
||||
|
||||
### Layout-Specific Properties
|
||||
|
||||
| Property | Applies To | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `layout` | frame | Layout direction |
|
||||
| `gap` | frame | Spacing between children |
|
||||
| `padding` | frame | Internal padding |
|
||||
| `justifyContent` | frame | Main axis alignment |
|
||||
| `alignItems` | frame | Cross axis alignment |
|
||||
|
||||
### Text-Specific Properties
|
||||
|
||||
| Property | Description |
|
||||
|----------|-------------|
|
||||
| `content` | Text content |
|
||||
| `fontSize` | Font size in pixels |
|
||||
| `fontFamily` | Font name |
|
||||
| `fontWeight` | Font weight |
|
||||
| `textAlign` | Text alignment |
|
||||
| `letterSpacing` | Letter spacing |
|
||||
| `lineHeight` | Line height |
|
||||
|
||||
### Shape-Specific Properties
|
||||
|
||||
| Property | Applies To | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `cornerRadius` | rectangle | Border radius |
|
||||
| `stroke` | rectangle, ellipse | Stroke color |
|
||||
| `strokeWidth` | rectangle, ellipse | Stroke width |
|
||||
|
||||
## Resources
|
||||
|
||||
- Back to [Pencil Design Skill](../SKILL.md)
|
||||
- [Build System Reference](./BUILD_SYSTEM.md)
|
||||
- [Atomic Design Reference](./ATOMIC_DESIGN.md)
|
||||
- [Code Conversion Reference](./CODE_CONVERSION.md)
|
||||
Reference in New Issue
Block a user