move
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
---
|
||||
name: nicegui-ui-customization
|
||||
description: 'Design and implement production NiceGUI UIs with reusable components, Tailwind-first styling, event-driven interactions, and troubleshooting for uploads, state, and static assets. Use when building or refactoring NiceGUI pages and interaction flows.'
|
||||
argument-hint: 'What UI outcome should this workflow produce?'
|
||||
---
|
||||
|
||||
# NiceGUI UI Customization Workflow
|
||||
|
||||
Create, style, and ship production NiceGUI UI flows with a repeatable process. The workflow keeps structure in Python, favors Tailwind and Quasar APIs for styling, and uses event-driven interaction patterns over ad-hoc polling.
|
||||
|
||||
## When To Use
|
||||
|
||||
- Building a new NiceGUI page or dashboard
|
||||
- Refactoring a page into reusable components
|
||||
- Adding file upload, form submission, live status, or background-job UX
|
||||
- Troubleshooting race conditions, stale assets, or inconsistent state updates
|
||||
|
||||
## Target Outcome
|
||||
|
||||
Deliver a responsive, accessible UI flow that:
|
||||
|
||||
- keeps clear boundaries between page adapters, reusable components, and services
|
||||
- uses Tailwind-first styling with minimal custom CSS
|
||||
- updates UI through events and bindings
|
||||
- has validation, user feedback, and failure handling
|
||||
- passes a production-readiness check at the end
|
||||
|
||||
## Progressive Loading References
|
||||
|
||||
Load these references only when needed:
|
||||
|
||||
- Architecture and styling rules: [./references/architecture-and-styling.md](./references/architecture-and-styling.md)
|
||||
- Event and state interaction patterns: [./references/interaction-patterns.md](./references/interaction-patterns.md)
|
||||
- Troubleshooting and release gates: [./references/troubleshooting-and-quality-gates.md](./references/troubleshooting-and-quality-gates.md)
|
||||
|
||||
## Procedure
|
||||
|
||||
### 1. Define the UI Slice
|
||||
|
||||
- Capture the user-visible outcome for this task in one sentence.
|
||||
- Identify route-level page modules to touch.
|
||||
- Identify service operations needed by the UI.
|
||||
|
||||
Completion check:
|
||||
|
||||
- You can name the target page, component candidates, and service calls before coding.
|
||||
|
||||
### 2. Choose Component Extraction Strategy
|
||||
|
||||
Decision point:
|
||||
|
||||
- If a layout pattern appears in 2 or more pages, extract it to `ui/components/`.
|
||||
- If a pattern is page-specific, keep it in the page module.
|
||||
|
||||
Completion check:
|
||||
|
||||
- Reused UI patterns are encapsulated as callable components.
|
||||
|
||||
### 3. Build Responsive Layout First
|
||||
|
||||
- Use Tailwind utility classes for structure and spacing.
|
||||
- Use responsive breakpoints (`sm:`, `md:`, `lg:`).
|
||||
- Reserve `.style()` for dynamic values that cannot be expressed with classes.
|
||||
|
||||
Completion check:
|
||||
|
||||
- Layout works at mobile and desktop widths without custom CSS overrides.
|
||||
|
||||
### 4. Add Reactive State And Events
|
||||
|
||||
- Use bindable dataclasses for local page state.
|
||||
- Prefer event handlers (`on_click`, `on_upload`, etc.) over periodic polling.
|
||||
- Trigger explicit refreshes with `@ui.refreshable` where needed.
|
||||
|
||||
Decision point by interaction type:
|
||||
|
||||
- File upload: validate size/type, delegate storage to a service, notify success/failure.
|
||||
- Form submit: bind inputs to dataclass fields, validate in service layer, clear state on success.
|
||||
- Real-time status: use SSE or WebSocket for push updates.
|
||||
- Long jobs: run in background task, update status endpoint or stream.
|
||||
|
||||
Completion check:
|
||||
|
||||
- Every user action has explicit positive and negative feedback via `ui.notify()`.
|
||||
|
||||
### 5. Apply Styling Strategy
|
||||
|
||||
Preferred order:
|
||||
|
||||
1. Tailwind utility classes
|
||||
2. Quasar props
|
||||
3. Reusable styled component functions
|
||||
|
||||
Only if absolutely necessary:
|
||||
|
||||
- Load minimal custom CSS once at startup in `bootstrap.py`.
|
||||
- Keep custom CSS tokenized (variables) and documented.
|
||||
|
||||
Completion check:
|
||||
|
||||
- Styling is mostly class/props-driven and not dependent on scattered ad-hoc CSS.
|
||||
|
||||
### 6. Harden Against Common Failures
|
||||
|
||||
- Prevent duplicate submissions by disabling controls during in-flight operations.
|
||||
- Avoid overlapping timers for the same state target.
|
||||
- Serialize dependent updates (`await` service call before mutation/render).
|
||||
- Verify static mount paths and cache behavior for changed assets.
|
||||
|
||||
Completion check:
|
||||
|
||||
- Race conditions and stale asset symptoms are addressed with explicit safeguards.
|
||||
|
||||
### 7. Final Production Readiness Review
|
||||
|
||||
Pass all checks:
|
||||
|
||||
- Structure: pages, components, services follow one-way dependency flow.
|
||||
- Responsiveness: tested at small and large viewport widths.
|
||||
- Accessibility: labels, button text, and action visibility are clear.
|
||||
- Reliability: validation and exception paths produce user-facing notifications.
|
||||
- Maintainability: repeated UI patterns are extracted; business logic stays in services.
|
||||
|
||||
If any check fails, return to the relevant step and iterate.
|
||||
|
||||
## Completion Contract
|
||||
|
||||
This workflow is complete when:
|
||||
|
||||
- the page flow meets the target outcome
|
||||
- architecture boundaries are preserved
|
||||
- chosen interaction pattern is implemented with explicit success and failure feedback
|
||||
- troubleshooting checks pass
|
||||
- production-readiness gate passes
|
||||
@@ -0,0 +1,76 @@
|
||||
# Architecture and Styling Reference
|
||||
|
||||
## Project Boundaries
|
||||
|
||||
Use this dependency direction:
|
||||
|
||||
- pages import components and services
|
||||
- components contain presentation logic only
|
||||
- services contain business logic and do not import UI
|
||||
- static assets are mounted and loaded once at bootstrap
|
||||
|
||||
Suggested module split:
|
||||
|
||||
```text
|
||||
src/app/
|
||||
ui/pages/
|
||||
ui/components/
|
||||
ui/static/
|
||||
services/
|
||||
api/
|
||||
bootstrap.py
|
||||
```
|
||||
|
||||
## Component Extraction Rules
|
||||
|
||||
Extract to ui/components when a pattern appears in two or more pages.
|
||||
|
||||
Keep in-page if the layout is specific to a single route.
|
||||
|
||||
```python
|
||||
def card_section(title: str, content: str) -> ui.card:
|
||||
with ui.card().classes("w-full max-w-md") as card:
|
||||
ui.label(title).classes("text-lg font-bold")
|
||||
ui.label(content).classes("text-gray-600")
|
||||
return card
|
||||
```
|
||||
|
||||
## Tailwind-First Layout Pattern
|
||||
|
||||
Use Tailwind utility classes for structure and spacing.
|
||||
Use breakpoint classes for responsive behavior.
|
||||
Use .style() only for values that must be computed dynamically.
|
||||
|
||||
```python
|
||||
with ui.column().classes("w-full"):
|
||||
with ui.row().classes("w-full gap-4 flex-wrap sm:flex-nowrap"):
|
||||
ui.card().classes("flex-1 min-w-64")
|
||||
ui.card().classes("flex-1 min-w-64")
|
||||
```
|
||||
|
||||
## Styling Decision Order
|
||||
|
||||
1. Tailwind utility classes
|
||||
2. Quasar props
|
||||
3. Reusable styled component functions
|
||||
4. Minimal custom CSS loaded once at bootstrap (only when needed)
|
||||
|
||||
```python
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
app.mount("/static", StaticFiles(directory="src/app/static"), name="static")
|
||||
ui.add_css(open("src/app/static/css/base.css").read())
|
||||
```
|
||||
|
||||
## Static Asset Rules
|
||||
|
||||
- Keep custom CSS small and tokenized with variables.
|
||||
- Avoid per-page CSS injection.
|
||||
- Verify static mount paths and reverse proxy rewrites.
|
||||
|
||||
## Links
|
||||
|
||||
- NiceGUI elements: https://nicegui.io/documentation/element
|
||||
- NiceGUI binding: https://nicegui.io/documentation/section_binding_properties
|
||||
- Tailwind: https://tailwindcss.com/docs/utility-first
|
||||
- Quasar components: https://quasar.dev/vue-components
|
||||
@@ -0,0 +1,109 @@
|
||||
# Interaction Patterns Reference
|
||||
|
||||
## Reactive State
|
||||
|
||||
Use bindable dataclasses for local page state.
|
||||
|
||||
```python
|
||||
from dataclasses import field
|
||||
from nicegui import binding, ui
|
||||
|
||||
@binding.bindable_dataclass
|
||||
class PageState:
|
||||
selected_id: int | None = None
|
||||
items: list = field(default_factory=list)
|
||||
|
||||
state = PageState()
|
||||
ui.label().bind_text_from(state, "selected_id")
|
||||
```
|
||||
|
||||
## File Upload Pattern
|
||||
|
||||
- Validate extension and size before storing.
|
||||
- Delegate storage to a service method.
|
||||
- Notify success and failure explicitly.
|
||||
|
||||
```python
|
||||
async def handle_upload(e: ui.events.UploadEventArguments):
|
||||
try:
|
||||
if e.size > 10 * 1024 * 1024:
|
||||
raise ValueError("File too large")
|
||||
if not e.name.endswith(".pdf"):
|
||||
raise ValueError("Only PDF allowed")
|
||||
await file_service.store(e.content.read(), e.name)
|
||||
ui.notify(f"Uploaded: {e.name}", type="positive")
|
||||
except ValueError as err:
|
||||
ui.notify(str(err), type="negative")
|
||||
|
||||
ui.upload(on_upload=handle_upload, auto_upload=True)
|
||||
```
|
||||
|
||||
## Form Submission Pattern
|
||||
|
||||
- Bind UI inputs to dataclass fields.
|
||||
- Perform validation in the service layer.
|
||||
- Clear form state on success.
|
||||
|
||||
```python
|
||||
@binding.bindable_dataclass
|
||||
class FormData:
|
||||
name: str = ""
|
||||
email: str = ""
|
||||
|
||||
data = FormData()
|
||||
ui.input("Name").bind_value(data, "name")
|
||||
ui.input("Email").bind_value(data, "email")
|
||||
|
||||
async def on_submit():
|
||||
try:
|
||||
await user_service.create_user(name=data.name, email=data.email)
|
||||
ui.notify("User created", type="positive")
|
||||
data.name = data.email = ""
|
||||
except ValueError as err:
|
||||
ui.notify(str(err), type="negative")
|
||||
|
||||
ui.button("Submit").on_click(on_submit)
|
||||
```
|
||||
|
||||
## Real-Time Updates Decision
|
||||
|
||||
Use SSE for one-way status streaming.
|
||||
Use WebSocket for bidirectional messaging.
|
||||
|
||||
SSE endpoint example:
|
||||
|
||||
```python
|
||||
@app.get("/events/status")
|
||||
async def status_stream():
|
||||
async def gen():
|
||||
while True:
|
||||
yield f"data: {await get_status()}\\n\\n"
|
||||
await asyncio.sleep(1)
|
||||
return StreamingResponse(gen(), media_type="text/event-stream")
|
||||
```
|
||||
|
||||
## Background Work Pattern
|
||||
|
||||
- Start long jobs in FastAPI background tasks.
|
||||
- Expose status via endpoint or streaming channel.
|
||||
- Guard buttons against duplicate submissions during in-flight tasks.
|
||||
|
||||
## Explicit Refresh Pattern
|
||||
|
||||
Use @ui.refreshable and call refresh intentionally instead of polling unrelated state.
|
||||
|
||||
```python
|
||||
@ui.refreshable
|
||||
async def item_list():
|
||||
items = await service.list()
|
||||
for item in items:
|
||||
ui.label(item.name)
|
||||
|
||||
ui.button("Refresh").on_click(lambda: item_list.refresh())
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
- NiceGUI action events: https://nicegui.io/documentation/section_action_events
|
||||
- FastAPI SSE: https://fastapi.tiangolo.com/advanced/server-sent-events/
|
||||
- FastAPI WebSockets: https://fastapi.tiangolo.com/advanced/websockets/
|
||||
@@ -0,0 +1,39 @@
|
||||
# Troubleshooting and Quality Gates
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Upload Errors
|
||||
|
||||
- Validate extension and size before storage.
|
||||
- Catch expected exceptions and return negative notifications.
|
||||
- Log unexpected exceptions with request context.
|
||||
|
||||
### UI Race Conditions
|
||||
|
||||
- Disable triggering controls during async work.
|
||||
- Remove duplicate timers and listeners targeting the same state.
|
||||
- Ensure service call ordering is deterministic before render updates.
|
||||
|
||||
### Asset Caching
|
||||
|
||||
- Confirm static mount and proxy rewrite correctness.
|
||||
- Add cache-busting query strings for changed assets.
|
||||
- Avoid per-page CSS injection.
|
||||
|
||||
### Navigation and State Drift
|
||||
|
||||
- Avoid global mutable UI state.
|
||||
- Keep state request-scoped or service-managed.
|
||||
- Rehydrate page data during route load.
|
||||
|
||||
## Production Readiness Gate
|
||||
|
||||
Pass all checks before shipping:
|
||||
|
||||
- Structure: one-way dependencies between pages, components, and services.
|
||||
- Responsiveness: UI validated at both small and large viewport widths.
|
||||
- Accessibility: labels and actions are clear and readable.
|
||||
- Reliability: validation and exception paths surface user feedback.
|
||||
- Maintainability: repeated UI patterns are extracted; business logic remains in services.
|
||||
|
||||
If any check fails, return to the workflow step that owns that concern.
|
||||
Reference in New Issue
Block a user