Description
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 topreswald/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 containertabs: list[dict]
– Each item withtitle: 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 fortab()
🧩 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