Files
gh-geoffjay-claude-plugins-…/commands/gpui-component.md
2025-11-29 18:28:12 +08:00

594 lines
15 KiB
Markdown

---
name: gpui-component
description: Generate reusable GPUI components with proper typing, state management, styling, and documentation
---
# GPUI Component Generator
Generate reusable, well-structured GPUI components with proper typing, state management, styling, and comprehensive documentation.
## Arguments
- `$1`: Component name (required) - Name of the component in PascalCase (e.g., "Button", "DataTable", "SearchInput")
- `$2`: Component type (optional) - Either "stateless" (default) or "stateful"
## Workflow
### 1. Gather Component Requirements
Ask user for:
- Component purpose and description
- Props/configuration needed
- Whether component needs internal state
- Event handlers required
- Styling requirements
- Accessibility needs
### 2. Generate Component Struct
Create component struct based on type:
#### Stateless Component
```rust
use gpui::*;
/// A reusable button component
///
/// # Examples
///
/// ```
/// Button::new("Click me")
/// .on_click(|cx| {
/// println!("Clicked!");
/// })
/// ```
pub struct Button {
label: String,
variant: ButtonVariant,
disabled: bool,
on_click: Option<Box<dyn Fn(&mut WindowContext)>>,
}
#[derive(Clone, Copy, PartialEq)]
pub enum ButtonVariant {
Primary,
Secondary,
Destructive,
Ghost,
}
impl Button {
pub fn new(label: impl Into<String>) -> Self {
Self {
label: label.into(),
variant: ButtonVariant::Primary,
disabled: false,
on_click: None,
}
}
pub fn variant(mut self, variant: ButtonVariant) -> Self {
self.variant = variant;
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn on_click(mut self, handler: impl Fn(&mut WindowContext) + 'static) -> Self {
self.on_click = Some(Box::new(handler));
self
}
}
```
#### Stateful Component
```rust
use gpui::*;
/// A search input with autocomplete
pub struct SearchInput {
query: String,
suggestions: Vec<String>,
selected_index: Option<usize>,
on_search: Option<Box<dyn Fn(&str, &mut WindowContext)>>,
}
impl SearchInput {
pub fn new() -> Self {
Self {
query: String::new(),
suggestions: Vec::new(),
selected_index: None,
on_search: None,
}
}
pub fn on_search(mut self, handler: impl Fn(&str, &mut WindowContext) + 'static) -> Self {
self.on_search = Some(Box::new(handler));
self
}
fn handle_input(&mut self, value: String, cx: &mut ViewContext<Self>) {
self.query = value;
self.update_suggestions(cx);
cx.notify();
}
fn update_suggestions(&mut self, cx: &mut ViewContext<Self>) {
// Update suggestions based on query
if let Some(handler) = &self.on_search {
handler(&self.query, cx);
}
}
fn handle_key_down(&mut self, event: &KeyDownEvent, cx: &mut ViewContext<Self>) {
match event.key.as_str() {
"ArrowDown" => {
self.selected_index = Some(
self.selected_index
.map(|i| (i + 1).min(self.suggestions.len() - 1))
.unwrap_or(0)
);
cx.notify();
}
"ArrowUp" => {
self.selected_index = self.selected_index
.and_then(|i| i.checked_sub(1));
cx.notify();
}
"Enter" => {
if let Some(index) = self.selected_index {
self.query = self.suggestions[index].clone();
self.suggestions.clear();
cx.notify();
}
}
_ => {}
}
}
}
```
### 3. Implement Element/Render Traits
#### Stateless Component Render
```rust
impl Render for Button {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let theme = cx.global::<Theme>();
let (bg_color, text_color, hover_color) = match self.variant {
ButtonVariant::Primary => (
theme.primary,
theme.primary_foreground,
theme.primary_hover,
),
ButtonVariant::Secondary => (
theme.secondary,
theme.secondary_foreground,
theme.secondary_hover,
),
ButtonVariant::Destructive => (
theme.destructive,
theme.destructive_foreground,
theme.destructive_hover,
),
ButtonVariant::Ghost => (
hsla(0.0, 0.0, 0.0, 0.0),
theme.foreground,
theme.muted,
),
};
div()
.px_4()
.py_2()
.bg(bg_color)
.text_color(text_color)
.rounded_md()
.font_medium()
.when(!self.disabled, |this| {
this.cursor_pointer()
.hover(|this| this.bg(hover_color))
})
.when(self.disabled, |this| {
this.opacity(0.5)
.cursor_not_allowed()
})
.when_some(self.on_click.take(), |this, handler| {
this.on_click(move |_, cx| {
if !self.disabled {
handler(cx);
}
})
})
.child(self.label.clone())
}
}
```
#### Stateful Component Render
```rust
impl Render for SearchInput {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let theme = cx.global::<Theme>();
div()
.flex()
.flex_col()
.relative()
.child(
div()
.flex()
.items_center()
.px_3()
.py_2()
.bg(theme.background)
.border_1()
.border_color(theme.border)
.rounded_md()
.child(
input()
.flex_1()
.placeholder("Search...")
.value(&self.query)
.on_input(cx.listener(|this, value, cx| {
this.handle_input(value, cx);
}))
.on_key_down(cx.listener(|this, event, cx| {
this.handle_key_down(event, cx);
}))
)
)
.when(!self.suggestions.is_empty(), |this| {
this.child(
div()
.absolute()
.top_full()
.left_0()
.right_0()
.mt_1()
.bg(theme.background)
.border_1()
.border_color(theme.border)
.rounded_md()
.shadow_lg()
.max_h_64()
.overflow_y_auto()
.children(
self.suggestions.iter().enumerate().map(|(i, suggestion)| {
div()
.px_3()
.py_2()
.cursor_pointer()
.when(self.selected_index == Some(i), |this| {
this.bg(theme.accent)
})
.hover(|this| this.bg(theme.muted))
.child(suggestion.as_str())
})
)
)
})
}
}
```
### 4. Add State Management
For stateful components:
```rust
impl SearchInput {
pub fn set_suggestions(&mut self, suggestions: Vec<String>, cx: &mut ViewContext<Self>) {
self.suggestions = suggestions;
self.selected_index = None;
cx.notify();
}
pub fn clear(&mut self, cx: &mut ViewContext<Self>) {
self.query.clear();
self.suggestions.clear();
self.selected_index = None;
cx.notify();
}
pub fn query(&self) -> &str {
&self.query
}
}
```
### 5. Generate Styling
Create styled variants and theme integration:
```rust
// Component-specific theme
pub struct ButtonTheme {
pub primary_bg: Hsla,
pub primary_fg: Hsla,
pub primary_hover: Hsla,
pub secondary_bg: Hsla,
pub secondary_fg: Hsla,
pub secondary_hover: Hsla,
pub border_radius: Pixels,
pub padding_x: Pixels,
pub padding_y: Pixels,
}
impl ButtonTheme {
pub fn from_app_theme(theme: &AppTheme) -> Self {
Self {
primary_bg: theme.colors.primary,
primary_fg: theme.colors.primary_foreground,
primary_hover: theme.colors.primary_hover,
secondary_bg: theme.colors.secondary,
secondary_fg: theme.colors.secondary_foreground,
secondary_hover: theme.colors.secondary_hover,
border_radius: px(6.0),
padding_x: px(16.0),
padding_y: px(8.0),
}
}
}
```
### 6. Create Documentation
Generate comprehensive documentation:
```rust
//! Button Component
//!
//! A flexible, accessible button component with multiple variants and states.
//!
//! # Features
//!
//! - Multiple variants (Primary, Secondary, Destructive, Ghost)
//! - Disabled state support
//! - Customizable click handlers
//! - Full keyboard accessibility
//! - Theme integration
//!
//! # Examples
//!
//! ## Basic Usage
//!
//! ```rust
//! let button = Button::new("Click me")
//! .on_click(|cx| {
//! println!("Button clicked!");
//! });
//! ```
//!
//! ## With Variants
//!
//! ```rust
//! let primary = Button::new("Primary").variant(ButtonVariant::Primary);
//! let secondary = Button::new("Secondary").variant(ButtonVariant::Secondary);
//! let destructive = Button::new("Delete").variant(ButtonVariant::Destructive);
//! ```
//!
//! ## Disabled State
//!
//! ```rust
//! let button = Button::new("Disabled")
//! .disabled(true);
//! ```
//!
//! # Accessibility
//!
//! - Supports keyboard navigation (Enter/Space to activate)
//! - Proper ARIA attributes
//! - Focus indicators
//! - Disabled state communicated to screen readers
```
### 7. Provide Usage Examples
Create example usage code:
```rust
// examples/button_example.rs
use gpui::*;
fn main() {
App::new("com.example.button-demo", |cx| {
cx.open_window(
WindowOptions::default(),
|cx| cx.new_view(|cx| ButtonDemo::new(cx))
)
}).run();
}
struct ButtonDemo;
impl ButtonDemo {
fn new(cx: &mut ViewContext<Self>) -> Self {
Self
}
}
impl Render for ButtonDemo {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div()
.flex()
.flex_col()
.gap_4()
.p_8()
.child(
Button::new("Primary Button")
.variant(ButtonVariant::Primary)
.on_click(|cx| {
println!("Primary clicked!");
})
)
.child(
Button::new("Secondary Button")
.variant(ButtonVariant::Secondary)
.on_click(|cx| {
println!("Secondary clicked!");
})
)
.child(
Button::new("Destructive Button")
.variant(ButtonVariant::Destructive)
.on_click(|cx| {
println!("Destructive clicked!");
})
)
.child(
Button::new("Disabled Button")
.disabled(true)
)
}
}
```
### 8. Add Component Tests
Generate tests for the component:
```rust
#[cfg(test)]
mod tests {
use super::*;
#[gpui::test]
fn test_button_creation() {
App::test(|cx| {
let button = Button::new("Test");
assert_eq!(button.label, "Test");
assert_eq!(button.variant, ButtonVariant::Primary);
assert!(!button.disabled);
});
}
#[gpui::test]
fn test_button_click() {
App::test(|cx| {
let clicked = Rc::new(RefCell::new(false));
let clicked_clone = clicked.clone();
let button = Button::new("Test")
.on_click(move |_| {
*clicked_clone.borrow_mut() = true;
});
// Simulate click
if let Some(handler) = button.on_click {
handler(cx);
}
assert!(*clicked.borrow());
});
}
}
```
### 9. Generate Component Module
Create module file with exports:
```rust
// src/ui/components/button/mod.rs
mod button;
mod theme;
pub use button::{Button, ButtonVariant};
pub use theme::ButtonTheme;
```
### 10. Provide Integration Instructions
Output integration guide:
```
✓ Created Button component
Files created:
- src/ui/components/button/button.rs
- src/ui/components/button/theme.rs
- src/ui/components/button/mod.rs
- examples/button_example.rs
- tests/button_test.rs
Next steps:
1. Add to your components module:
In src/ui/components/mod.rs:
pub mod button;
pub use button::Button;
2. Use in your views:
use crate::ui::components::Button;
Button::new("Click me")
.variant(ButtonVariant::Primary)
.on_click(|cx| {
// Handle click
})
3. Run example:
cargo run --example button_example
4. Run tests:
cargo test button
Documentation: See generated component docs for full API
```
## Component Types
### Stateless Components
- No internal state
- Pure rendering based on props
- Examples: Button, Icon, Label
### Stateful Components
- Internal state management
- User input handling
- Examples: Input, SearchBox, Dropdown
### Container Components
- Manage child components
- State coordination
- Examples: Form, List, Tabs
### Composite Components
- Combine multiple components
- Complex functionality
- Examples: DataTable, Dialog, Wizard
## Best Practices
- Builder pattern for configuration
- Theme integration
- Accessibility attributes
- Comprehensive documentation
- Usage examples
- Unit tests
- Type safety
- Error handling
## Example Usage
```bash
# Generate stateless component
/gpui-component Button
# Generate stateful component
/gpui-component SearchInput stateful
# Generate with custom requirements
/gpui-component DataTable stateful --with-examples --with-tests
```