Skip to content

[FEATURE] Introduce tab() Component for Multi-Section UI Navigation #520

Open
@amrutha97

Description

@amrutha97

Goal
Add a tab() component that enables developers to organize UI content into labeled tabs within their Preswald apps—ideal for sectioning long dashboards, multiple views, or split data insights.


📌 Motivation

Many dashboards and tools created with Preswald contain multiple logical sections (e.g., charts, tables, filters). Scrolling through all at once creates cognitive overload. A tab() component provides:

  • Better user experience through organized navigation
  • A way to switch between related views in-place
  • Clean separation of logic (e.g., Overview vs. Details)

This aligns with the existing layout vision (e.g., size, collapsible()), and will significantly improve interface design for real-world dashboards.


✅ Acceptance Criteria

  • Add tab() component to preswald/interfaces/components.py
  • Frontend implementation using ShadCN’s Tabs from @/components/ui/tabs.tsx
  • Create TabWidget.jsx to render tabs and dynamic child content
  • Register component in DynamicComponents.jsx
  • Tab component should accept:
    • label: str – Label for the tab container
    • tabs: list[dict] – Each item with title: str, components: list
    • size: float = 1.0 – For layout sizing
  • Ensure tabs support rendering other registered components (e.g., text, plotly, table) inside them
  • Fully documented in SDK

🛠 Implementation Plan

1. Backend – Component Definition

In preswald/interfaces/components.py:

def tab(
    label: str,
    tabs: list[dict],
    size: float = 1.0,
) -> None:
    service = PreswaldService.get_instance()
    component_id = f"tab-{hashlib.md5(label.encode()).hexdigest()[:8]}"
    
    component = {
        "type": "tab",
        "id": component_id,
        "label": label,
        "size": size,
        "tabs": tabs,
    }

    service.append_component(component)

Register it in __init__.py:

from .components import tab

2. Frontend – UI Component

Create: frontend/src/components/widgets/TabWidget.jsx

import React from 'react';
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
import { Card } from '@/components/ui/card';

const TabWidget = ({ _label, tabs = [] }) => {
  const [activeTab, setActiveTab] = React.useState(tabs?.[0]?.title || '');

  return (
    <Card className="mb-4 p-4 rounded-2xl shadow-md">
      <h2 className="font-semibold text-lg mb-2">{_label}</h2>
      <Tabs value={activeTab} onValueChange={setActiveTab}>
        <TabsList className="mb-2 flex space-x-2">
          {tabs.map((tab) => (
            <TabsTrigger key={tab.title} value={tab.title}>
              {tab.title}
            </TabsTrigger>
          ))}
        </TabsList>
        {tabs.map((tab) => (
          <TabsContent key={tab.title} value={tab.title}>
            {/* Render nested components */}
            {tab.components?.map((child, i) => (
              <div key={i}>{window.renderDynamicComponent(child)}</div>
            ))}
          </TabsContent>
        ))}
      </Tabs>
    </Card>
  );
};

export default TabWidget;

3. DynamicComponents.jsx – Register tab

import TabWidget from '@/components/widgets/TabWidget';

case 'tab':
  return (
    <TabWidget
      {...commonProps}
      _label={component.label}
      tabs={component.tabs}
    />
  );

Ensure renderDynamicComponent() is accessible globally or passed through props to render children.


🧪 Testing Plan

  • Add a test in examples/iris/hello.py:
from preswald import tab, text, table, get_df, connect

connect()
df = get_df("sample_csv")

tab(
    label="Data Views",
    tabs=[
        {"title": "Intro", "components": [text("Welcome to the Iris app.")]},
        {"title": "Table", "components": [table(df)]},
    ]
)
  • Run with preswald run and confirm:
    • Tab titles appear
    • Content switches correctly
    • Styling matches Tailwind theme

🧾 SDK Usage Example

tab(
    label="Navigation",
    tabs=[
        {"title": "Overview", "components": [text("Summary goes here")]},
        {"title": "Data", "components": [table(df)]},
        {"title": "Chart", "components": [plotly(fig)]}
    ]
)

📚 Docs To Update

  • /docs/sdk/tab.mdx – Full parameters and example
  • /docs/layout/guide.mdx – Add layout pattern for tab()

🧩 Files Involved

  • preswald/interfaces/components.py
  • frontend/src/components/widgets/TabWidget.jsx
  • frontend/src/components/ui/tabs.tsx
  • DynamicComponents.jsx

💡 Future Ideas

  • Allow dynamic content loading (lazy tabs)
  • Optional icon support per tab
  • Persist selected tab state across sessions

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions