Skip to content

[FEATURE] Implement collapsible() Component for UI Layout #682

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 2 commits 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
44 changes: 38 additions & 6 deletions docs/layout/guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ icon: "table-cells"
description: ""
---

# Using the `size` Parameter in Components
# Layout Tools and Techniques

Preswald offers several tools to help you control and organize your application's layout.

## Using the `size` Parameter in Components

The `size` parameter lets you control how much space a component occupies in a row, enabling you to create dynamic and responsive layouts.

---

## How the `size` Parameter Works
### How the `size` Parameter Works

- **Default Behavior**: All components have a default `size=1.0`, taking up the full width of the row.
- **Custom Sizes**: You can specify a `size` less than `1.0` to allow multiple components to share the same row.
Expand All @@ -19,9 +23,9 @@ The `size` parameter lets you control how much space a component occupies in a r

---

## Example: Multiple Components in One Row
### Example: Multiple Components in One Row

Heres an example of how to use the `size` parameter to arrange components in a row:
Here's an example of how to use the `size` parameter to arrange components in a row:

```python
from preswald import slider, button
Expand All @@ -35,7 +39,35 @@ submit_button = button("Submit", size=0.3)
threshold_slider = slider("Threshold", min_val=0.0, max_val=100.0, default=50.0, size=0.7)
```

---

- **Flexible Layouts**: Multiple components with smaller sizes can fit side by side in a single row.
- **Spacing Management**: Verify the combined sizes of all components in a row add up to `1.0` or less.

---

## Using the `collapsible` Component for Group Organization

The `collapsible` component helps you organize related UI elements into expandable/collapsible sections, improving the overall structure and readability of your application.

### Benefits of Using Collapsible Sections

- **Reduced Visual Clutter**: Hide optional or advanced controls until needed
- **Logical Grouping**: Group related inputs and outputs
- **Progressive Disclosure**: Implement step-by-step workflows
- **Better Mobile Experience**: Improve usability on smaller screens

### Example: Organizing Components with Collapsible

```python
from preswald import collapsible, slider, text, table

# Main data view (expanded by default)
collapsible("Data Overview")
table(my_dataframe)

# Advanced filters section (collapsed by default)
collapsible("Advanced Filters", open=False)
text("Adjust parameters to filter the data")
slider("Threshold", min_val=0, max_val=100, default=50)
```

See the [`collapsible` documentation](/sdk/collapsible) for more details on its usage and parameters.
65 changes: 65 additions & 0 deletions docs/sdk/collapsible.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
title: "collapsible"
icon: "chevron-down"
description: ""
---

```python
collapsible(
label: str,
open: bool = True,
size: float = 1.0,
) -> None:
```

The `collapsible` function creates an expandable/collapsible container that groups related UI components. This helps organize complex interfaces by reducing visual clutter and allowing users to focus on relevant content.

## Parameters

- **`label`** _(str)_: The title/header of the collapsible section.
- **`open`** _(bool)_: Whether the section is open (expanded) by default. Defaults to `True`.
- **`size`** _(float)_: _(Optional)_ The width of the component in a row. Defaults to `1.0` (full row). See the [Layout Guide](/layout/guide) for details.

<Frame>
<img
className="block dark:hidden"
src="/images/collapsible.png"
alt="Collapsible component"
/>
</Frame>

## Returns

- This component doesn't return a value. It's used for layout organization only.

## Usage Example

```python
from preswald import collapsible, slider, text

# Create a collapsible section for advanced filters
collapsible("Advanced Filters", open=False)

# All components below will be nested in the collapsible container
text("Adjust the parameters below to filter the data.")
slider("Sepal Width", min_val=0, max_val=10, default=5.5)
slider("Sepal Length", min_val=0, max_val=10, default=4.5)

# Create another section that's open by default
collapsible("Main Visualizations")
text("These are the primary visualizations for your data.")
# Add more components here...
```

### Key Features

1. **Organized Layout**: Group related components to create a cleaner, more structured interface.
2. **Reduced Visual Clutter**: Hide optional or advanced controls that aren't needed immediately.
3. **Improved User Experience**: Create step-by-step workflows or categorize UI elements by function.
4. **Responsive Design**: Helps make dense dashboards more manageable on smaller screens.

### Why Use `collapsible`?

The `collapsible` component is essential for building complex applications with many UI elements. By organizing components into expandable/collapsible sections, you can create more intuitive interfaces that guide users through your data application.

Enhance your layout with the `collapsible` component! 🔽
71 changes: 9 additions & 62 deletions examples/iris/hello.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from preswald import (
chat,
collapsible,
# fastplotlib,
get_df,
plotly,
Expand All @@ -29,6 +30,9 @@
# Load the CSV
df = get_df("iris_csv")

# Add collapsible for Sepal visualizations
collapsible("Sepal Visualizations", open=True)

# 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 Expand Up @@ -68,6 +72,9 @@
fig5.update_layout(template="plotly_white")
plotly(fig5)

# Add collapsible for Petal visualizations
collapsible("Petal Visualizations", open=False)

# 4. Violin plot of Sepal Length by Species
text(
"## Sepal Length Distribution by Species \n The violin plot provides a better understanding of the distribution of sepal lengths within each species. We can see the density of values and how they vary across species."
Expand Down Expand Up @@ -96,68 +103,8 @@
fig10.update_layout(template="plotly_white")
plotly(fig10)

# # 6. Fastplotlib Examples
#
# # Retrieve client_id from component state
# client_id = service.get_component_state("client_id")
#
# sidebar(defaultopen=True)
# text("# Fastplotlib Examples")
#
# # 6.1. Simple Image Plot
# text("## Simple Image Plot")
# fig = fpl.Figure(size=(700, 560), canvas="offscreen")
# fig._client_id = client_id
# fig._label = "Simple Image Plot"
# data = iio.imread("images/logo.png")
# fig[0, 0].add_image(data)
# fastplotlib(fig)
#
# # 6.2. Line Plot
# text("## Line Plot")
# x = np.linspace(-1, 10, 100)
# y = np.sin(x)
# sine = np.column_stack([x, y])
# fig = fpl.Figure(size=(700, 560), canvas="offscreen")
# fig._client_id = client_id
# fig._label = "Line Plot"
# fig[0, 0].add_line(data=sine, colors="w")
# fastplotlib(fig)
#
# # 6.3. Line Plot with Color Maps
# text("## Line Plot ColorMap")
# fig = fpl.Figure(size=(700, 560), canvas="offscreen")
# fig._client_id = client_id
# fig._label = "Line Plot Color Map"
# xs = np.linspace(-10, 10, 100)
# ys = np.sin(xs)
# sine = np.dstack([xs, ys])[0]
# ys = np.cos(xs) - 5
# cosine = np.dstack([xs, ys])[0]
#
# sine_graphic = fig[0, 0].add_line(
# data=sine, thickness=10, cmap="plasma", cmap_transform=sine[:, 1]
# )
# labels = [0] * 25 + [5] * 10 + [1] * 35 + [2] * 30
# cosine_graphic = fig[0, 0].add_line(
# data=cosine, thickness=10, cmap="tab10", cmap_transform=labels
# )
# fastplotlib(fig)
#
# # 6.4. Scatter Plot from Iris dataset
# text("## Scatter Plot")
# x = df["sepal.length"].tolist()
# y = df["petal.width"].tolist()
# variety = df["variety"].tolist()
# data = np.column_stack((x, y))
# color_map = {"Setosa": "yellow", "Versicolor": "cyan", "Virginica": "magenta"}
# colors = [color_map[v] for v in variety]
#
# fig = fpl.Figure(size=(700, 560), canvas="offscreen")
# fig._client_id = client_id
# fig._label = "Scatter Plot"
# fig[0, 0].add_scatter(data=data, sizes=4, colors=colors)
# fastplotlib(fig)
# Add collapsible for data view
collapsible("Dataset View", open=False)

# Show the first 10 rows of the dataset
text(
Expand Down
2 changes: 1 addition & 1 deletion frontend/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
}
86 changes: 38 additions & 48 deletions frontend/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
import js from '@eslint/js'
import globals from 'globals'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import importPlugin from 'eslint-plugin-import'

import js from '@eslint/js';
import importPlugin from 'eslint-plugin-import';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import globals from 'globals';

export default [
{
ignores: [
'dist',
'node_modules',
'*.config.js',
]
ignores: ['dist', 'node_modules', '*.config.js'],
},
{
files: ['**/*.{js,jsx}'],
extends: [
'prettier'
],
extends: ['prettier'],
languageOptions: {
ecmaVersion: 2020,
globals: {
Expand Down Expand Up @@ -51,48 +44,45 @@ export default [
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
}],
'no-console': ['warn', { allow: ['warn', 'error'] }],
// Disable ESLint's import ordering to let Prettier handle it
'import/order': 'off',
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
}],
'no-console': ['warn', { allow: ['warn', 'error'] }],
'import/order': ['error', {
groups: [
'builtin',
'external',
'internal',
['parent', 'sibling'],
'index',
],
'newlines-between': 'always',
pathGroups: [
{ pattern: '^react', group: 'external', position: 'before' },
{ pattern: '^@/components/(.*)$', group: 'internal', position: 'before' },
{ pattern: '^@/(.*)$', group: 'internal' },
],
alphabetize: {
order: 'asc',
caseInsensitive: true,
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', ['parent', 'sibling'], 'index'],
'newlines-between': 'always',
pathGroups: [
{ pattern: '^react', group: 'external', position: 'before' },
{ pattern: '^@/components/(.*)$', group: 'internal', position: 'before' },
{ pattern: '^@/(.*)$', group: 'internal' },
],
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
},
}],
],
},
},
]
];
2 changes: 1 addition & 1 deletion frontend/postcss.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ export default {
tailwindcss: {},
autoprefixer: {},
},
}
};
13 changes: 13 additions & 0 deletions frontend/src/components/DynamicComponents.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import BigNumberWidget from './widgets/BigNumberWidget';
import ButtonWidget from './widgets/ButtonWidget';
import ChatWidget from './widgets/ChatWidget';
import CheckboxWidget from './widgets/CheckboxWidget';
import CollapsibleWidget from './widgets/CollapsibleWidget';
import DAGVisualizationWidget from './widgets/DAGVisualizationWidget';
import DataVisualizationWidget from './widgets/DataVisualizationWidget';
import FastplotlibWidget from './widgets/FastplotlibWidget';
Expand Down Expand Up @@ -74,6 +75,18 @@ const MemoizedComponent = memo(
case 'sidebar':
return <SidebarWidget defaultOpen={component.defaultopen} branding={component.branding} />;

case 'collapsible':
return (
<CollapsibleWidget
key={componentKey}
{...props}
_label={component.label}
_open={component.open}
>
{props.children}
</CollapsibleWidget>
);

case 'button':
return (
<ButtonWidget
Expand Down
Loading