Skip to content
Merged
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
19 changes: 13 additions & 6 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,24 +88,31 @@
"group": "Examples",
"pages": [
{
"group": "User Interaction",
"group": "Interactivity",
"pages": [
"examples/reactive-binding",
"examples/conditional",
"examples/actions",
"examples/the-button",
"examples/threshold-meter",
"examples/file-upload"
"examples/threshold-meter"
]
},
{
"group": "Data Visualization",
"group": "Data & Visualization",
"pages": [
"examples/server-dashboard",
"examples/expense-tracker",
"examples/deploy-pipeline",
"examples/sales-dashboard",
"examples/cross-filter"
]
},
{
"group": "Mini Apps",
"group": "Apps",
"pages": [
"examples/todo"
"examples/todo",
"examples/dynamic-list",
"examples/file-upload"
]
}
]
Expand Down
172 changes: 172 additions & 0 deletions docs/examples/actions.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
---
title: Action Chains
description: Chain multiple actions from a single click.
icon: zap
mode: center
---

import { ComponentPreview } from '/snippets/component-preview.mdx'

Each button fires one or more actions in sequence. The counter increments, a toast appears, the progress bar fills, and a save badge replaces a button, all from declarative action lists.

<ComponentPreview json={{"view":{"cssClass":"pf-app-root max-w-none p-0","type":"Div","children":[{"cssClass":"w-full","type":"Card","children":[{"type":"CardHeader","children":[{"type":"CardTitle","content":"Actions Demo"},{"content":"Chain multiple actions from a single click.","type":"Muted"}]},{"type":"CardContent","children":[{"cssClass":"gap-4","type":"Column","children":[{"cssClass":"gap-2 items-center","type":"Row","children":[{"type":"Button","label":"Count + 1","variant":"default","size":"default","disabled":false,"onClick":{"action":"setState","key":"count","value":"{{ count + 1 }}"}},{"type":"Button","label":"Count + 10","variant":"secondary","size":"default","disabled":false,"onClick":[{"action":"setState","key":"count","value":"{{ count + 10 }}"},{"action":"showToast","message":"Jumped ahead by 10!"}]},{"type":"Button","label":"Reset","variant":"outline","size":"default","disabled":false,"onClick":[{"action":"setState","key":"count","value":0},{"action":"setState","key":"saved","value":false}]}]},{"type":"Progress","value":"{{ count }}","max":100.0,"variant":"{{ count >= 100 ? 'success' : 'default' }}","size":"default"},{"cssClass":"gap-2 items-center justify-between","type":"Row","children":[{"content":"Count: {{ count }}","type":"Text"},{"type":"Condition","cases":[{"when":"{{ saved }}","children":[{"type":"Badge","label":"Saved","variant":"success"}]}],"else":[{"type":"Button","label":"Save","variant":"default","size":"sm","disabled":false,"onClick":[{"action":"toggleState","key":"saved"},{"action":"showToast","message":"Saved at {{ count }}!"}]}]}]}]}]}]}]},"state":{"count":0,"saved":false}}} playground="ZnJvbSBwcmVmYWJfdWkgaW1wb3J0IFByZWZhYkFwcApmcm9tIHByZWZhYl91aS5hY3Rpb25zIGltcG9ydCBTZXRTdGF0ZSwgU2hvd1RvYXN0LCBUb2dnbGVTdGF0ZQpmcm9tIHByZWZhYl91aS5jb21wb25lbnRzIGltcG9ydCAoCiAgICBCYWRnZSwgQnV0dG9uLCBDYXJkLCBDYXJkQ29udGVudCwgQ2FyZEhlYWRlciwgQ2FyZFRpdGxlLAogICAgQ29sdW1uLCBNdXRlZCwgUHJvZ3Jlc3MsIFJvdywgVGV4dCwKKQpmcm9tIHByZWZhYl91aS5jb21wb25lbnRzLmNvbnRyb2xfZmxvdyBpbXBvcnQgRWxzZSwgSWYKZnJvbSBwcmVmYWJfdWkucnggaW1wb3J0IFJ4Cgpjb3VudCA9IFJ4KCJjb3VudCIpCnNhdmVkID0gUngoInNhdmVkIikKCndpdGggUHJlZmFiQXBwKHN0YXRlPXsiY291bnQiOiAwLCAic2F2ZWQiOiBGYWxzZX0sIGNzc19jbGFzcz0ibWF4LXctbm9uZSBwLTAiKToKICAgIHdpdGggQ2FyZChjc3NfY2xhc3M9InctZnVsbCIpOgogICAgICAgIHdpdGggQ2FyZEhlYWRlcigpOgogICAgICAgICAgICBDYXJkVGl0bGUoIkFjdGlvbnMgRGVtbyIpCiAgICAgICAgICAgIE11dGVkKCJDaGFpbiBtdWx0aXBsZSBhY3Rpb25zIGZyb20gYSBzaW5nbGUgY2xpY2suIikKICAgICAgICB3aXRoIENhcmRDb250ZW50KCk6CiAgICAgICAgICAgIHdpdGggQ29sdW1uKGdhcD00KToKICAgICAgICAgICAgICAgIHdpdGggUm93KGdhcD0yLCBhbGlnbj0iY2VudGVyIik6CiAgICAgICAgICAgICAgICAgICAgQnV0dG9uKCJDb3VudCArIDEiLCBvbl9jbGljaz1TZXRTdGF0ZSgiY291bnQiLCBjb3VudCArIDEpKQogICAgICAgICAgICAgICAgICAgIEJ1dHRvbigKICAgICAgICAgICAgICAgICAgICAgICAgIkNvdW50ICsgMTAiLCB2YXJpYW50PSJzZWNvbmRhcnkiLAogICAgICAgICAgICAgICAgICAgICAgICBvbl9jbGljaz1bCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBTZXRTdGF0ZSgiY291bnQiLCBjb3VudCArIDEwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNob3dUb2FzdCgiSnVtcGVkIGFoZWFkIGJ5IDEwISIpLAogICAgICAgICAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgICBCdXR0b24oCiAgICAgICAgICAgICAgICAgICAgICAgICJSZXNldCIsIHZhcmlhbnQ9Im91dGxpbmUiLAogICAgICAgICAgICAgICAgICAgICAgICBvbl9jbGljaz1bU2V0U3RhdGUoImNvdW50IiwgMCksIFNldFN0YXRlKCJzYXZlZCIsIEZhbHNlKV0sCiAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgUHJvZ3Jlc3MoCiAgICAgICAgICAgICAgICAgICAgdmFsdWU9Y291bnQsIG1heD0xMDAsCiAgICAgICAgICAgICAgICAgICAgdmFyaWFudD0oY291bnQgPj0gMTAwKS50aGVuKCJzdWNjZXNzIiwgImRlZmF1bHQiKSwKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgIHdpdGggUm93KGdhcD0yLCBhbGlnbj0iY2VudGVyIiwgY3NzX2NsYXNzPSJqdXN0aWZ5LWJldHdlZW4iKToKICAgICAgICAgICAgICAgICAgICBUZXh0KGYiQ291bnQ6IHtjb3VudH0iKQogICAgICAgICAgICAgICAgICAgIHdpdGggSWYoc2F2ZWQpOgogICAgICAgICAgICAgICAgICAgICAgICBCYWRnZSgiU2F2ZWQiLCB2YXJpYW50PSJzdWNjZXNzIikKICAgICAgICAgICAgICAgICAgICB3aXRoIEVsc2UoKToKICAgICAgICAgICAgICAgICAgICAgICAgQnV0dG9uKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIlNhdmUiLCBzaXplPSJzbSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBvbl9jbGljaz1bCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVG9nZ2xlU3RhdGUoInNhdmVkIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgU2hvd1RvYXN0KGYiU2F2ZWQgYXQge2NvdW50fSEiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgICAgICAgICAgICkK">
<CodeGroup>
```python icon="python"
from prefab_ui import PrefabApp
from prefab_ui.actions import SetState, ShowToast, ToggleState
from prefab_ui.components import (
Badge, Button, Card, CardContent, CardHeader, CardTitle,
Column, Muted, Progress, Row, Text,
)
from prefab_ui.components.control_flow import Else, If
from prefab_ui.rx import Rx

count = Rx("count")
saved = Rx("saved")

with PrefabApp(state={"count": 0, "saved": False}, css_class="max-w-none p-0"):
with Card(css_class="w-full"):
with CardHeader():
CardTitle("Actions Demo")
Muted("Chain multiple actions from a single click.")
with CardContent():
with Column(gap=4):
with Row(gap=2, align="center"):
Button("Count + 1", on_click=SetState("count", count + 1))
Button(
"Count + 10", variant="secondary",
on_click=[
SetState("count", count + 10),
ShowToast("Jumped ahead by 10!"),
],
)
Button(
"Reset", variant="outline",
on_click=[SetState("count", 0), SetState("saved", False)],
)
Progress(
value=count, max=100,
variant=(count >= 100).then("success", "default"),
)
with Row(gap=2, align="center", css_class="justify-between"):
Text(f"Count: {count}")
with If(saved):
Badge("Saved", variant="success")
with Else():
Button(
"Save", size="sm",
on_click=[
ToggleState("saved"),
ShowToast(f"Saved at {count}!"),
],
)
```
```json Protocol icon="brackets-curly"
{
"view": {
"cssClass": "pf-app-root max-w-none p-0",
"type": "Div",
"children": [
{
"cssClass": "w-full",
"type": "Card",
"children": [
{
"type": "CardHeader",
"children": [
{"type": "CardTitle", "content": "Actions Demo"},
{"content": "Chain multiple actions from a single click.", "type": "Muted"}
]
},
{
"type": "CardContent",
"children": [
{
"cssClass": "gap-4",
"type": "Column",
"children": [
{
"cssClass": "gap-2 items-center",
"type": "Row",
"children": [
{
"type": "Button",
"label": "Count + 1",
"variant": "default",
"size": "default",
"disabled": false,
"onClick": {"action": "setState", "key": "count", "value": "{{ count + 1 }}"}
},
{
"type": "Button",
"label": "Count + 10",
"variant": "secondary",
"size": "default",
"disabled": false,
"onClick": [
{"action": "setState", "key": "count", "value": "{{ count + 10 }}"},
{"action": "showToast", "message": "Jumped ahead by 10!"}
]
},
{
"type": "Button",
"label": "Reset",
"variant": "outline",
"size": "default",
"disabled": false,
"onClick": [
{"action": "setState", "key": "count", "value": 0},
{"action": "setState", "key": "saved", "value": false}
]
}
]
},
{
"type": "Progress",
"value": "{{ count }}",
"max": 100.0,
"variant": "{{ count >= 100 ? 'success' : 'default' }}",
"size": "default"
},
{
"cssClass": "gap-2 items-center justify-between",
"type": "Row",
"children": [
{"content": "Count: {{ count }}", "type": "Text"},
{
"type": "Condition",
"cases": [
{
"when": "{{ saved }}",
"children": [{"type": "Badge", "label": "Saved", "variant": "success"}]
}
],
"else": [
{
"type": "Button",
"label": "Save",
"variant": "default",
"size": "sm",
"disabled": false,
"onClick": [
{"action": "toggleState", "key": "saved"},
{"action": "showToast", "message": "Saved at {{ count }}!"}
]
}
]
}
]
}
]
}
]
}
]
}
]
},
"state": {"count": 0, "saved": false}
}
```
</CodeGroup>
</ComponentPreview>
147 changes: 147 additions & 0 deletions docs/examples/conditional.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
title: Conditional Rendering
description: Toggle between UI states with If/Else and reactive switches.
icon: toggle-on
mode: center
---

import { ComponentPreview } from '/snippets/component-preview.mdx'

Flip a switch and the UI changes. `If` and `Else` render different components based on reactive state, with no server round-trip.

<ComponentPreview json={{"view":{"cssClass":"w-full","type":"Card","children":[{"type":"CardHeader","children":[{"type":"CardTitle","content":"Preferences"}]},{"type":"CardContent","children":[{"cssClass":"gap-4","type":"Column","children":[{"name":"dark_mode","value":false,"type":"Switch","label":"Dark Mode","size":"default","disabled":false,"required":false},{"name":"notifications","value":true,"type":"Switch","label":"Notifications","size":"default","disabled":false,"required":false},{"type":"Condition","cases":[{"when":"{{ dark_mode }}","children":[{"type":"Alert","variant":"info","children":[{"type":"AlertTitle","content":"Dark Mode"},{"type":"AlertDescription","content":"Your eyes will thank you."}]}]}],"else":[{"type":"Alert","variant":"warning","children":[{"type":"AlertTitle","content":"Light Mode"},{"type":"AlertDescription","content":"Living dangerously, I see."}]}]},{"type":"Condition","cases":[{"when":"{{ notifications }}","children":[{"cssClass":"text-sm text-muted-foreground","content":"You'll receive alerts for all events.","type":"Text"}]}],"else":[{"cssClass":"text-sm text-muted-foreground","content":"Notifications are off. You're on your own.","type":"Text"}]}]}]}]},"state":{"dark_mode":false,"notifications":true}}} playground="ZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cyBpbXBvcnQgKAogICAgQWxlcnQsIEFsZXJ0RGVzY3JpcHRpb24sIEFsZXJ0VGl0bGUsCiAgICBDYXJkLCBDYXJkQ29udGVudCwgQ2FyZEhlYWRlciwgQ2FyZFRpdGxlLAogICAgQ29sdW1uLCBTd2l0Y2gsIFRleHQsCikKZnJvbSBwcmVmYWJfdWkuY29tcG9uZW50cy5jb250cm9sX2Zsb3cgaW1wb3J0IEVsc2UsIElmCmZyb20gcHJlZmFiX3VpLnJ4IGltcG9ydCBSeAoKZGFya19tb2RlID0gUngoImRhcmtfbW9kZSIpCm5vdGlmaWNhdGlvbnMgPSBSeCgibm90aWZpY2F0aW9ucyIpCgp3aXRoIENhcmQoY3NzX2NsYXNzPSJ3LWZ1bGwiKToKICAgIHdpdGggQ2FyZEhlYWRlcigpOgogICAgICAgIENhcmRUaXRsZSgiUHJlZmVyZW5jZXMiKQogICAgd2l0aCBDYXJkQ29udGVudCgpOgogICAgICAgIHdpdGggQ29sdW1uKGdhcD00KToKICAgICAgICAgICAgU3dpdGNoKGxhYmVsPSJEYXJrIE1vZGUiLCBuYW1lPSJkYXJrX21vZGUiLCB2YWx1ZT1GYWxzZSkKICAgICAgICAgICAgU3dpdGNoKGxhYmVsPSJOb3RpZmljYXRpb25zIiwgbmFtZT0ibm90aWZpY2F0aW9ucyIsIHZhbHVlPVRydWUpCiAgICAgICAgICAgIHdpdGggSWYoZGFya19tb2RlKToKICAgICAgICAgICAgICAgIHdpdGggQWxlcnQodmFyaWFudD0iaW5mbyIpOgogICAgICAgICAgICAgICAgICAgIEFsZXJ0VGl0bGUoIkRhcmsgTW9kZSIpCiAgICAgICAgICAgICAgICAgICAgQWxlcnREZXNjcmlwdGlvbigiWW91ciBleWVzIHdpbGwgdGhhbmsgeW91LiIpCiAgICAgICAgICAgIHdpdGggRWxzZSgpOgogICAgICAgICAgICAgICAgd2l0aCBBbGVydCh2YXJpYW50PSJ3YXJuaW5nIik6CiAgICAgICAgICAgICAgICAgICAgQWxlcnRUaXRsZSgiTGlnaHQgTW9kZSIpCiAgICAgICAgICAgICAgICAgICAgQWxlcnREZXNjcmlwdGlvbigiTGl2aW5nIGRhbmdlcm91c2x5LCBJIHNlZS4iKQogICAgICAgICAgICB3aXRoIElmKG5vdGlmaWNhdGlvbnMpOgogICAgICAgICAgICAgICAgVGV4dCgKICAgICAgICAgICAgICAgICAgICAiWW91J2xsIHJlY2VpdmUgYWxlcnRzIGZvciBhbGwgZXZlbnRzLiIsCiAgICAgICAgICAgICAgICAgICAgY3NzX2NsYXNzPSJ0ZXh0LXNtIHRleHQtbXV0ZWQtZm9yZWdyb3VuZCIsCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgIHdpdGggRWxzZSgpOgogICAgICAgICAgICAgICAgVGV4dCgKICAgICAgICAgICAgICAgICAgICAiTm90aWZpY2F0aW9ucyBhcmUgb2ZmLiBZb3UncmUgb24geW91ciBvd24uIiwKICAgICAgICAgICAgICAgICAgICBjc3NfY2xhc3M9InRleHQtc20gdGV4dC1tdXRlZC1mb3JlZ3JvdW5kIiwKICAgICAgICAgICAgICAgICkK">
<CodeGroup>
```python icon="python"
from prefab_ui.components import (
Alert, AlertDescription, AlertTitle,
Card, CardContent, CardHeader, CardTitle,
Column, Switch, Text,
)
from prefab_ui.components.control_flow import Else, If
from prefab_ui.rx import Rx

dark_mode = Rx("dark_mode")
notifications = Rx("notifications")

with Card(css_class="w-full"):
with CardHeader():
CardTitle("Preferences")
with CardContent():
with Column(gap=4):
Switch(label="Dark Mode", name="dark_mode", value=False)
Switch(label="Notifications", name="notifications", value=True)
with If(dark_mode):
with Alert(variant="info"):
AlertTitle("Dark Mode")
AlertDescription("Your eyes will thank you.")
with Else():
with Alert(variant="warning"):
AlertTitle("Light Mode")
AlertDescription("Living dangerously, I see.")
with If(notifications):
Text(
"You'll receive alerts for all events.",
css_class="text-sm text-muted-foreground",
)
with Else():
Text(
"Notifications are off. You're on your own.",
css_class="text-sm text-muted-foreground",
)
```
```json Protocol icon="brackets-curly"
{
"view": {
"cssClass": "w-full",
"type": "Card",
"children": [
{
"type": "CardHeader",
"children": [{"type": "CardTitle", "content": "Preferences"}]
},
{
"type": "CardContent",
"children": [
{
"cssClass": "gap-4",
"type": "Column",
"children": [
{
"name": "dark_mode",
"value": false,
"type": "Switch",
"label": "Dark Mode",
"size": "default",
"disabled": false,
"required": false
},
{
"name": "notifications",
"value": true,
"type": "Switch",
"label": "Notifications",
"size": "default",
"disabled": false,
"required": false
},
{
"type": "Condition",
"cases": [
{
"when": "{{ dark_mode }}",
"children": [
{
"type": "Alert",
"variant": "info",
"children": [
{"type": "AlertTitle", "content": "Dark Mode"},
{"type": "AlertDescription", "content": "Your eyes will thank you."}
]
}
]
}
],
"else": [
{
"type": "Alert",
"variant": "warning",
"children": [
{"type": "AlertTitle", "content": "Light Mode"},
{"type": "AlertDescription", "content": "Living dangerously, I see."}
]
}
]
},
{
"type": "Condition",
"cases": [
{
"when": "{{ notifications }}",
"children": [
{
"cssClass": "text-sm text-muted-foreground",
"content": "You'll receive alerts for all events.",
"type": "Text"
}
]
}
],
"else": [
{
"cssClass": "text-sm text-muted-foreground",
"content": "Notifications are off. You're on your own.",
"type": "Text"
}
]
}
]
}
]
}
]
},
"state": {"dark_mode": false, "notifications": true}
}
```
</CodeGroup>
</ComponentPreview>
228 changes: 203 additions & 25 deletions docs/examples/cross-filter.mdx

Large diffs are not rendered by default.

Loading