feat: Add component library build functionality to support atomic design workflow and update related documentation.

This commit is contained in:
Ho Ngoc Hai
2026-01-31 20:28:28 +07:00
parent db73b82ea5
commit 468d93a8b3
7 changed files with 4739 additions and 4509 deletions

View File

@@ -308,6 +308,7 @@ open tPOS.pen
| Command | Purpose |
|---------|---------|
| `python3 scripts/build.py --library` | Build Component Lib |
| `python3 scripts/build.py -m` | Monolithic build |
| `python3 scripts/build.py` | Standard build |
| `python3 scripts/build.py -m --output name.pen` | Custom output |

View File

@@ -299,110 +299,22 @@ mkdir -p src/foundations src/atoms src/molecules src/organisms src/pages
### Step 3: Extract to Atomic Files
```python
import json
import os
The `pencil-design` skill now includes an automated extraction tool.
# 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')
```bash
# Run the extraction script
python3 scripts/extract-atomic.py
```
This script will:
1. Read `src/components/tPOS-ui-kit.pen` (or configured library)
2. Extract design tokens to `src/foundations/design-tokens.pen`
3. Categorize components into `src/atoms`, `src/molecules`, `src/organisms`
4. Generate showcase wrappers for each file
**Customization**:
Edit `scripts/extract-atomic.py` to adjust categorization logic (regex patterns) if your component names differ.
### Step 4: Create Showcase Files
```python
@@ -528,11 +440,11 @@ src/
## 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)
2. **Build Components**: Edit atomic files with proper naming (Atom/Button/Primary/Default)
3. **Update Library**: Run `python3 scripts/build.py --library` to compile atoms
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
5. **Test in Pages**: Reference components in page files (from compiled library)
6. **Build Monolith**: Run `python3 scripts/build.py -m` for production output
## Component Naming

View File

@@ -86,6 +86,21 @@ python3 scripts/build.py -m --output myDesign.pen
}
```
### Component Library Build (Atomic Design)
Merges distributed atomic components into a single library file.
**Command:**
```bash
python3 scripts/build.py --library
python3 scripts/build.py -l
```
**Output:**
- **File**: `src/components/ui-kit.pen` (or configured path)
- **Contains**: All atoms, molecules, organisms merged into one file
- **Use case**: Generating the library to use during page design
### Standard Build
Builds individual pages with component injection.
@@ -262,7 +277,23 @@ jq '[.children[0].children[] | select(.reusable == true)] | length' tPOS.pen
## Workflows
### Development Cycle
### Atomic Design Workflow (Recommended)
```markdown
1. Layout & Components:
- Update atoms/molecules in `src/atoms/` etc.
- Run `python3 scripts/build.py --library` to update component library
2. Page Design:
- Open `src/pages/desktop-home.pen`
- Use components from the generated `src/components/ui-kit.pen`
3. Production Build:
- Run `python3 scripts/build.py -m`
- Output `tPOS.pen` contains everything
```
### Legacy/Mockup Workflow
```markdown
1. Design in modular files:

View File

@@ -226,7 +226,18 @@ Edit `pencil.config.json`:
### Workflow
1. **Edit Components**: Open individual files in `src/atoms/...`, `src/molecules/...`, etc.
2. **Build**: Run `python3 scripts/build.py -m`. The build system automatically scans these directories.
3. **Migrate**: Use `python3 scripts/extract-atomic.py` to convert legacy libraries.
1. **Edit Atomic Components**: Update files in `src/atoms/...`, `src/molecules/...`, etc.
2. **Build Library**: Run `python3 scripts/build.py --library`. This compiles all atomic components into `src/components/tPOS-ui-kit.pen`.
3. **Design Pages**: Open your page files (e.g., `src/pages/desktop.pen`) in Pencil. They reference components from `tPOS-ui-kit.pen`.
4. **Build Monolith**: Run `python3 scripts/build.py --monolithic`. This merges pages and the library into the final `tPOS.pen` for sharing/verification.
### Build Commands
```bash
# Step 2: Build Component Library
python3 scripts/build.py --library
# Step 4: Build Monolithic File
python3 scripts/build.py --monolithic
```
```

View File

@@ -306,6 +306,94 @@ class PencilBuilder:
print(f"📊 Total frames: {len(monolithic['children'])}")
print(f"🎨 Design tokens: {len(variables)}")
def build_library(self, output_path: str = None) -> None:
"""Build merged component library from Atomic Design files"""
print("🔨 Building Component Library")
print("=" * 50)
components = []
variables = {}
# 1. Load Variables from Foundations
src_dir = self.project_root / self.config['sourceDir']
tokens_path = src_dir / 'foundations/design-tokens.pen'
if tokens_path.exists():
with open(tokens_path, 'r') as f:
data = json.load(f)
variables = data.get('variables', {})
print(f"✓ Loaded design tokens from {tokens_path.name}")
else:
print(f"⚠️ Design tokens not found at {tokens_path}")
# 2. Collect Components from Atomic Dirs
atomic_dirs = ['atoms', 'molecules', 'organisms']
total_found = 0
for atom_dir in atomic_dirs:
dir_path = src_dir / atom_dir
if not dir_path.exists():
continue
print(f"Scanning {atom_dir}...")
# Sorting ensures deterministic order
for pen_file in sorted(dir_path.glob('*.pen')):
with open(pen_file, 'r') as f:
data = json.load(f)
# Extract children (components/showcases)
file_children = data.get('children', [])
# If the file contains a "Showcase" frame wrapping everything,
# we ideally want to extract the inner reusable components if we want a clean library.
# BUT, users might want the showcase organization in the library too.
# Let's flatten showcases for the library so components are cleaner?
# Actually, keeping showcases/groups is better for Pencil's sidebar organization.
# Update: Ensure all top-level children have unique IDs if possible?
# Pencil IDs are UUIDs mostly, should be fine.
components.extend(file_children)
total_found += len(file_children)
print(f" ✓ Added contents of {pen_file.name}")
# 3. Create Library File
library_file = {
"version": "2.6",
"children": [
{
"type": "frame",
"name": "aPOS Component Library", # Main page name
"width": 1440,
"fill": "$bg-page",
"layout": "vertical",
"gap": 40,
"padding": 40,
"children": components
}
],
"variables": variables
}
# Determine output path
if output_path:
out_file = self.project_root / output_path
else:
out_file = self.project_root / self.config['componentLibrary']
# Ensure directory exists
out_file.parent.mkdir(parents=True, exist_ok=True)
with open(out_file, 'w', encoding='utf-8') as f:
json.dump(library_file, f, indent=2)
print()
print("=" * 50)
print(f"✅ Library build complete!")
print(f"📄 Output: {out_file.absolute()}")
print(f"🧩 Components: {total_found}")
print(f"🎨 Tokens: {len(variables)}")
def build_all(self) -> None:
"""Build all pages"""
print("🔨 Pencil Design Build System")
@@ -361,15 +449,11 @@ def main():
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Build individual pages with component linking
python build.py
# 1. Build Component Library (from Atomic Source)
python build.py --library
# Build monolithic file (merge all pages)
# 2. Build Monolithic File (Pages + Library)
python build.py --monolithic
python build.py --mode monolithic
# Build monolithic with custom output name
python build.py --monolithic --output myDesign.pen
"""
)
@@ -379,17 +463,22 @@ Examples:
help='Build monolithic file (merge all pages and component library)'
)
parser.add_argument(
'--library', '--lib', '-l',
action='store_true',
help='Build component library file from Atomic Design sources'
)
parser.add_argument(
'--mode',
choices=['standard', 'monolithic'],
choices=['standard', 'monolithic', 'library'],
default='standard',
help='Build mode: standard (separate pages) or monolithic (merged file)'
help='Build mode'
)
parser.add_argument(
'--output', '-o',
default='tPOS.pen',
help='Output filename for monolithic build (default: tPOS.pen)'
help='Output filename'
)
args = parser.parse_args()
@@ -397,8 +486,12 @@ Examples:
builder = PencilBuilder()
# Determine build mode
if args.monolithic or args.mode == 'monolithic':
builder.build_monolithic(output_filename=args.output)
if args.library or args.mode == 'library':
builder.build_library(output_path=args.output)
elif args.monolithic or args.mode == 'monolithic':
# Default output for monolithic is tPOS.pen, but allow override
output = args.output if args.output else "tPOS.pen"
builder.build_monolithic(output_filename=output)
else:
builder.build_all()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff