Claude-skill-registry gpui-component

Community UI component library for GPUI applications

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/gpui-component" ~/.claude/skills/majiayu000-claude-skill-registry-gpui-component && rm -rf "$T"
manifest: skills/data/gpui-component/SKILL.md
source content

gpui-component

A comprehensive UI component library for building desktop applications with GPUI. Provides 60+ cross-platform components inspired by macOS/Windows controls and shadcn/ui design.

Repository: https://github.com/longbridge/gpui-component Fork (script-kit-gpui): https://github.com/johnlindquist/gpui-component Docs: https://gpui-component.longbridge.xyz/

Installation

# In Cargo.toml - using the fork for set_selection() support
gpui-component = { git = "https://github.com/johnlindquist/gpui-component", package = "gpui-component" }

# Required for i18n support
rust-i18n = "3"

Initialization (REQUIRED)

use gpui_component::Root;

fn main() {
    let app = Application::new();
    app.run(move |cx| {
        // MUST be called before using any gpui-component features
        gpui_component::init(cx);
        
        // Open windows wrapped in Root...
    });
}

Available Components

Core UI

  • Button - Primary, secondary, ghost, link variants with icons
  • Input - Text input with validation, masks, placeholders
  • Checkbox - Checkable controls
  • Radio - Radio button groups
  • Switch - Toggle switches
  • Select - Dropdown selection
  • Slider - Range selection

Layout

  • Root - Theme/context provider wrapper (REQUIRED for all windows)
  • Dock - Panel arrangements with resizing
  • Accordion - Collapsible content sections
  • Collapsible - Show/hide content
  • Divider - Visual separators
  • GroupBox - Grouped content with border

Feedback

  • Notification - Toast notifications (Success, Warning, Error, Info)
  • Alert - Alert dialogs
  • Dialog - Modal dialogs
  • Sheet - Slide-in panels
  • Progress - Progress indicators
  • Spinner - Loading spinners
  • Skeleton - Loading placeholders

Data Display

  • Table - Virtualized tables (supports 200K+ rows)
  • List - Virtualized lists
  • Tree - Tree views
  • Badge - Status badges
  • Tag - Categorization tags
  • Avatar - User avatars
  • Rating - Star ratings

Navigation

  • Menu - Context menus, dropdowns
  • Breadcrumb - Navigation breadcrumbs
  • Tab - Tab navigation
  • Sidebar - Side navigation
  • Pagination - Page navigation

Content

  • Icon - Lucide icons (700+ icons)
  • Label - Text labels
  • Link - Hyperlinks
  • Kbd - Keyboard shortcuts display
  • Text - Markdown/HTML rendering
  • Chart - Data visualization

Advanced

  • ColorPicker - Color selection
  • DatePicker - Date/time selection
  • Popover - Floating content
  • Tooltip - Hover tooltips
  • Resizable - Resizable panels

Usage in script-kit-gpui

Root Wrapper Pattern

All windows MUST be wrapped in

Root
for theming and context:

use gpui_component::Root;

// Opening a window
let window: WindowHandle<Root> = cx.open_window(
    WindowOptions { ... },
    |window, cx| {
        let view = cx.new(|cx| MyApp::new(cx));
        cx.new(|cx| Root::new(view, window, cx))
    },
)?;

Input Component

The most commonly used component for text input:

use gpui_component::input::{Input, InputEvent, InputState};

// Create InputState
let input_state = cx.new(|cx| 
    InputState::new(window, cx)
        .placeholder("Enter text...")
);

// Subscribe to events
cx.subscribe_in(&input_state, window, |this, _, event: &InputEvent, window, cx| {
    match event {
        InputEvent::Focus => { /* input focused */ }
        InputEvent::Blur => { /* input blurred */ }
        InputEvent::Change => { 
            let value = this.input_state.read(cx).value();
        }
        InputEvent::PressEnter { secondary } => { /* enter pressed */ }
    }
});

// Render
Input::new(input_state.clone())
    .size(Size::Medium)
    .cleanable()  // show clear button

Code Editor Mode

For multi-line code editing:

let editor_state = cx.new(|cx| 
    InputState::new(window, cx)
        .code_editor(true)
        .soft_wrap(true)
        .show_line_numbers(true)
);

Notifications

Toast-style notifications:

use gpui_component::notification::{Notification, NotificationType};

let notification = Notification::new()
    .title("Success!")
    .description("Operation completed")
    .notification_type(NotificationType::Success);

// NotificationList handles display automatically when using Root

Button Component

use gpui_component::button::{Button, ButtonVariants};

Button::new("submit")
    .primary()
    .label("Submit")
    .icon(IconName::Check)
    .on_click(|_, window, cx| {
        // handle click
    })

Icons

Uses Lucide icon set:

use gpui_component::{Icon, IconName, IconNamed};

// As element
Icon::new(IconName::Search)
    .size(px(16.))
    .color(theme.foreground)

// In buttons
Button::new("search").icon(IconName::Search)

Theming Integration

Script-kit-gpui maps its theme to gpui-component's ThemeColor:

use gpui_component::theme::{Theme as GpuiTheme, ThemeColor, ThemeMode, ActiveTheme};

// Get current theme
let theme = cx.theme();
let colors = &theme.colors;

// Map custom colors
let mut theme_color = *ThemeColor::dark();
theme_color.background = hsla(...);
theme_color.foreground = hsla(...);
theme_color.accent = hsla(...);

// Apply globally
let theme = GpuiTheme::global_mut(cx);
theme.colors = theme_color;
theme.mode = ThemeMode::Dark;

Sizing

Components support consistent sizing:

use gpui_component::{Sizable, Size};

Input::new(state).size(Size::Small)   // xs, sm, md, lg
Button::new("btn").size(Size::Large)

Fork Modifications

The fork at

johnlindquist/gpui-component
adds two features for snippet/template support:

1.
set_selection()
- Programmatic Text Selection

// Select text range by byte offsets
input_state.update(cx, |state, cx| {
    state.set_selection(start_bytes, end_bytes, window, cx);
});

// Get current selection
let selection: Range<usize> = input_state.read(cx).selection();

Use case: Snippet tabstop navigation - select placeholder text when moving between tabstops.

2.
tab_navigation_mode
- Tab Key Propagation

let editor_state = cx.new(|cx| 
    InputState::new(window, cx)
        .tab_navigation(true)  // Tab/Shift+Tab propagate instead of indent
);

// Or set dynamically
input_state.update(cx, |state, _| {
    state.set_tab_navigation(true);
});

Use case: When in snippet mode, Tab navigates to next tabstop instead of inserting indentation.

Key Patterns

Window Lifecycle

// Store window handle
static MY_WINDOW: OnceLock<Mutex<Option<WindowHandle<Root>>>> = OnceLock::new();

// Open window
let handle = cx.open_window(..., |window, cx| {
    cx.new(|cx| Root::new(my_view, window, cx))
})?;

// Store handle
MY_WINDOW.get_or_init(|| Mutex::new(None))
    .lock().unwrap().replace(handle);

// Update window
if let Some(handle) = get_window_handle() {
    handle.update(cx, |root, window, cx| {
        // Access view through root
    }).ok();
}

Event Subscription Pattern

// Subscribe to entity events
let subscription = cx.subscribe_in(&entity, window, |this, _, event, window, cx| {
    // Handle event
});

// Store subscription to keep it alive
self.subscriptions.push(subscription);

Focus Management

// Focus input
input_state.update(cx, |state, cx| {
    state.focus(window, cx);
});

// Check focus
if input_state.read(cx).is_focused(window) { ... }

Anti-patterns

Missing Root Wrapper

// WRONG - gpui-component widgets won't theme correctly
cx.open_window(..., |window, cx| {
    cx.new(|_| MyApp::new())
});

// CORRECT - wrap in Root
cx.open_window(..., |window, cx| {
    let view = cx.new(|_| MyApp::new());
    cx.new(|cx| Root::new(view, window, cx))
});

Missing init() Call

// WRONG - components may not initialize properly
app.run(move |cx| {
    cx.open_window(...);
});

// CORRECT - call init before using components
app.run(move |cx| {
    gpui_component::init(cx);  // FIRST
    cx.open_window(...);
});

Forgetting to Store Subscriptions

// WRONG - subscription dropped immediately, events not received
cx.subscribe_in(&state, window, |_, _, _, _, _| { ... });

// CORRECT - store subscription
self.subscription = Some(cx.subscribe_in(&state, window, |_, _, _, _, _| { ... }));

Using Char Offsets for Selection

// WRONG - set_selection uses byte offsets, not char offsets
let char_pos = 5;
state.set_selection(char_pos, char_pos + 3, window, cx);

// CORRECT - convert char to byte offset
fn char_to_byte(text: &str, char_offset: usize) -> usize {
    text.char_indices()
        .nth(char_offset)
        .map(|(i, _)| i)
        .unwrap_or(text.len())
}
let start = char_to_byte(&text, 5);
let end = char_to_byte(&text, 8);
state.set_selection(start, end, window, cx);

Not Handling InputEvent Variants

// WRONG - only handling some events
match event {
    InputEvent::Change => { ... }
    _ => {}  // Missing Focus/Blur handling
}

// CORRECT - handle all relevant events
match event {
    InputEvent::Focus => { self.focused = true; cx.notify(); }
    InputEvent::Blur => { self.focused = false; cx.notify(); }
    InputEvent::Change => { self.handle_change(cx); }
    InputEvent::PressEnter { secondary } => { self.submit(cx); }
}

Common Imports

// Core
use gpui_component::Root;
use gpui_component::{Sizable, Size};

// Input
use gpui_component::input::{Input, InputEvent, InputState};
use gpui_component::input::{IndentInline, OutdentInline, Position};  // For code editor

// Button
use gpui_component::button::{Button, ButtonVariants};

// Icons
use gpui_component::{Icon, IconName, IconNamed};

// Notifications
use gpui_component::notification::{Notification, NotificationType};

// Theme
use gpui_component::theme::{ActiveTheme, Theme, ThemeColor, ThemeMode};

// Window utilities
use gpui_component::WindowExt;

Resources