**Bug Fixes:** - Fixed duplicate js_append_child calls causing DOM element re-insertion - Elements were being appended twice: once by self-append logic, once by parent loop - This was breaking CSS transitions on vertical tabs (elements moved in DOM) - Removed self-append logic from _emit_with() and _emit_yield_component() **Refactoring:** - Created reusable HTab and VTab components for horizontal/vertical tabs - Reduced repetitive tab code from 9 lines per tab to 1 line - Improved code maintainability and reusability - WASM bundle size reduced from 5987 bytes to 4690 bytes (22% reduction!) **Result:** - CSS transitions now work smoothly on vertical tabs ✓ - All 55 tests passing ✓ - Cleaner, more composable tab components ✓ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
171 lines
6.1 KiB
Python
171 lines
6.1 KiB
Python
"""
|
|
Tabs component - Beautiful multi-panel tab switching with proper composition.
|
|
|
|
Demonstrates:
|
|
- Component composition (TabMenu, TabPane)
|
|
- Match/case for clean panel switching
|
|
- Conditional CSS classes for active state
|
|
- All logic in WASM, minimal syntax
|
|
"""
|
|
|
|
from ritz.syntax import *
|
|
|
|
|
|
# ===== Reusable Tab Components =====
|
|
|
|
@component
|
|
def HTab(label, active, onclick):
|
|
"""Horizontal tab (Li > Button structure).
|
|
|
|
Args:
|
|
label: String - tab label text
|
|
active: Boolean - whether this tab is active
|
|
onclick: Function - click handler
|
|
"""
|
|
with Li(class_='tab active' if active else 'tab'):
|
|
with Button(onclick=onclick):
|
|
yield label
|
|
|
|
|
|
@component
|
|
def VTab(label, active, onclick):
|
|
"""Vertical tab button.
|
|
|
|
Args:
|
|
label: String - tab label text
|
|
active: Boolean - whether this tab is active
|
|
onclick: Function - click handler
|
|
"""
|
|
with Button(class_='vertical-tab active' if active else 'vertical-tab', onclick=onclick):
|
|
yield label
|
|
|
|
|
|
@component
|
|
def VerticalTabs():
|
|
"""Vertical tabs for settings categories."""
|
|
setting_tab = signal(0) # 0 = General, 1 = Privacy, 2 = Advanced
|
|
|
|
def set_setting(value):
|
|
setting_tab = value
|
|
|
|
with Div(class_="vertical-tabs"):
|
|
# Vertical tab menu - using reusable VTab component!
|
|
with Div(class_="vertical-tab-menu"):
|
|
yield VTab(label="⚙️ General", active=setting_tab == 0, onclick=lambda: set_setting(0))
|
|
yield VTab(label="🔒 Privacy", active=setting_tab == 1, onclick=lambda: set_setting(1))
|
|
yield VTab(label="🚀 Advanced", active=setting_tab == 2, onclick=lambda: set_setting(2))
|
|
|
|
# Vertical tab content
|
|
with Div(class_="vertical-tab-content"):
|
|
match setting_tab:
|
|
case 0:
|
|
with Div(class_="setting-pane"):
|
|
with H3():
|
|
yield "⚙️ General Settings"
|
|
with P():
|
|
yield "Language: English"
|
|
with P():
|
|
yield "Time Zone: UTC"
|
|
|
|
case 1:
|
|
with Div(class_="setting-pane"):
|
|
with H3():
|
|
yield "🔒 Privacy Settings"
|
|
with P():
|
|
yield "Data Collection: Disabled"
|
|
with P():
|
|
yield "Analytics: Opt-out"
|
|
|
|
case 2:
|
|
with Div(class_="setting-pane"):
|
|
with H3():
|
|
yield "🚀 Advanced Settings"
|
|
with P():
|
|
yield "Debug Mode: Off"
|
|
with P():
|
|
yield "Experimental Features: Disabled"
|
|
|
|
case _:
|
|
with Div(class_="setting-pane"):
|
|
with P():
|
|
yield "Invalid setting"
|
|
|
|
|
|
@component
|
|
def Tabs(orientation=0): # 0 = horizontal, 1 = vertical
|
|
"""Main tabs container - orchestrates menu and panes."""
|
|
tab_index = signal(0) # Track which tab is active: 0 = Home, 1 = Profile, 2 = Settings
|
|
|
|
def set_tab(value):
|
|
tab_index = value
|
|
|
|
with Div(class_="tabs-container"):
|
|
with H1():
|
|
yield "Tab Component Demo"
|
|
|
|
with P(class_="subtitle"):
|
|
yield "Beautiful composition with TabMenu and TabPane components"
|
|
|
|
# Tab menu - using reusable HTab component!
|
|
with Ul(class_="tab-menu"):
|
|
yield HTab(label="🏠 Home", active=tab_index == 0, onclick=lambda: set_tab(0))
|
|
yield HTab(label="👤 Profile", active=tab_index == 1, onclick=lambda: set_tab(1))
|
|
yield HTab(label="⚙️ Settings", active=tab_index == 2, onclick=lambda: set_tab(2))
|
|
|
|
# Tab panes - match/case for clean switching
|
|
with Div(class_="tab-content"):
|
|
match tab_index:
|
|
case 0:
|
|
# Home pane
|
|
with Div(class_="tab-pane home"):
|
|
with H2():
|
|
yield "🏠 Welcome Home"
|
|
with P():
|
|
yield "This is your home dashboard."
|
|
with P():
|
|
yield "All your important info at a glance."
|
|
with Ul():
|
|
with Li():
|
|
yield "✓ Recent activity"
|
|
with Li():
|
|
yield "✓ Quick actions"
|
|
with Li():
|
|
yield "✓ Summary stats"
|
|
|
|
case 1:
|
|
# Profile pane
|
|
with Div(class_="tab-pane profile"):
|
|
with H2():
|
|
yield "👤 Your Profile"
|
|
with P():
|
|
yield "Manage your account settings."
|
|
with Div(class_="profile-info"):
|
|
with P():
|
|
yield "Name: Ritz User"
|
|
with P():
|
|
yield "Email: user@ritz.dev"
|
|
with P():
|
|
yield "Member since: 2025"
|
|
|
|
case 2:
|
|
# Settings pane with NESTED vertical tabs!
|
|
with Div(class_="tab-pane settings"):
|
|
with H2():
|
|
yield "⚙️ Settings"
|
|
with P():
|
|
yield "Configure your preferences using the categories below:"
|
|
|
|
# ← NESTED COMPONENT HERE!
|
|
yield VerticalTabs()
|
|
|
|
case _:
|
|
# Error fallback
|
|
with Div(class_="tab-pane error"):
|
|
with P():
|
|
yield "⚠️ Invalid tab selected"
|
|
|
|
# Debug footer
|
|
with Div(class_="debug-footer"):
|
|
with P():
|
|
yield f"Active: {tab_index}"
|