Skip to content

feat: introduced 'tab' component for more organized UI (fixes issue #520) #667

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions examples/iris/hello.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
get_df,
plotly,
sidebar,
tab,
table,
text,
)
Expand All @@ -29,6 +30,14 @@
# Load the CSV
df = get_df("iris_csv")

tab(
label="Data Views",
tabs=[
{"title": "Intro", "components": [text("Welcome to the Iris app.")]},
{"title": "Table", "components": [table(df)]},
],
)

# 1. Scatter plot - Sepal Length vs Sepal Width
text(
"## Sepal Length vs Sepal Width \n This scatter plot shows the relationship between sepal length and sepal width for different iris species. We can see that Setosa is well-separated from the other two species, while Versicolor and Virginica show some overlap."
Expand Down
170 changes: 170 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slider": "^1.2.2",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-tooltip": "^1.1.6",
"@shadcn/ui": "^0.0.4",
"@tailwindcss/typography": "^0.5.10",
Expand Down
59 changes: 50 additions & 9 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,20 +76,61 @@ const App = () => {
}

try {
const updatedRows = components.rows.map((row) =>
row.map((component) => {
if (!component || !component.id) return component;
const tabComponentRegistry = {
ids: new Set(),
content: new Set(),
};

components.rows.forEach((row) => {
row.forEach((component) => {
if (component?.type === 'tab' && component.tabs) {
component.tabs.forEach((tab) => {
(tab.components || []).forEach((tabComponent) => {
if (tabComponent?.id) {
tabComponentRegistry.ids.add(tabComponent.id);
}
if (typeof tabComponent === 'string') {
tabComponentRegistry.content.add(tabComponent.trim());
}
const content =
tabComponent?.content?.trim() ||
tabComponent?.markdown?.trim() ||
(typeof tabComponent?.value === 'string' ? tabComponent.value.trim() : '');
if (content) {
tabComponentRegistry.content.add(content);
}
});
});
}
});
});

const currentState = comm.getComponentState(component.id);
return {
const updatedRows = components.rows.map((row) =>
row
.filter((component) => {
if (!component) return false;
if (component.type === 'tab') return true;

const content =
component.content?.trim() ||
component.markdown?.trim() ||
(typeof component.value === 'string' ? component.value.trim() : '');

return !(
(component.id && tabComponentRegistry.ids.has(component.id)) ||
(content && tabComponentRegistry.content.has(content))
);
})
.map((component) => ({
...component,
value: currentState !== undefined ? currentState : component.value,
value: component?.id
? (comm.getComponentState(component.id) ?? component.value)
: component.value,
error: null,
};
})
}))
);

console.log('[App] Updating components with:', { rows: updatedRows });
console.log('[App] Final filtered components:', { rows: updatedRows });
setAreComponentsLoading(false);
setComponents({ rows: updatedRows });
setError(null);
Expand Down
88 changes: 88 additions & 0 deletions frontend/src/components.css
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,94 @@
@apply transition-all duration-200 ease-in-out p-0 opacity-100;
}

/* Tab Widget Styles */
.tab-widget-container {
@apply w-full mb-6;
}

.tab-widget-header {
@apply font-semibold text-lg mb-3;
}

.tab-list {
@apply flex space-x-2 mb-3;
}

.tab-trigger {
@apply px-4 py-2 text-sm font-medium rounded-md transition-colors;
@apply bg-muted text-muted-foreground hover:bg-muted/80;
@apply data-[state=active]:bg-primary data-[state=active]:text-primary-foreground;
}

.tab-content {
@apply mt-4 outline-none;
}

.tab-content-container {
@apply w-full;
contain: content;
overflow: hidden;
}

.tab-content-inner {
@apply p-4 bg-background rounded-lg border border-border;
}

/* Ensure tab content is properly contained */
.tab-content > .dynamiccomponent-container {
@apply mt-0;
}

/* Prevent double rendering of components meant for tabs */
.dynamiccomponent-container:has(+ .tab-widget-container) {
@apply hidden;
contain: content;
}

/* Special case for when tab is the only component */
.dynamiccomponent-container:has(
> .dynamiccomponent-row > .dynamiccomponent-component > .tab-widget-container
) {
@apply block;
}

/* Loading state for tabs */
.tab-loading {
@apply flex items-center justify-center h-32;
}

.tab-loading-spinner {
@apply animate-spin rounded-full h-8 w-8 border-b-2 border-primary;
}

.tab-loading-text {
@apply mt-2 text-sm text-muted-foreground;
}

/* Empty tab state */
.tab-empty {
@apply flex items-center justify-center h-32 text-sm text-muted-foreground;
}

/* Error state for tabs */
.tab-error {
@apply p-4 bg-destructive/10 text-destructive rounded-md;
}

/* Tab content transitions */
.tab-content[data-state='inactive'] {
@apply hidden;
}

.tab-content[data-state='active'] {
@apply block;
}

/* Ensure tab content doesn't affect layout when hidden */
.tab-content-hidden {
@apply absolute opacity-0 pointer-events-none;
}

.spinner-container {
@apply flex flex-col items-center justify-center gap-3 p-4;
}
Expand Down
Loading