feat: Add GPUI component library

Add core UI components for TaskWarrior GUI application:

Components:
- Input: Text input with autocomplete suggestions, keyboard navigation
- Button: Multiple variants (Primary, Secondary, Ghost, Danger, Success,
  Text)
- DropdownButton: Compound button with dropdown menu
- Icon: SVG icons with customizable sizes
- Label: Simple text display
- Panel: Container with optional title
- List: List container
- Modal: Modal dialog
- Divider: Visual separator

Infrastructure:
- Theme system with dark/light modes (Catppuccin colors)
- App module with window management
This commit is contained in:
Ignacio Perez
2025-12-24 19:21:04 -03:00
parent 8490fb03f5
commit 7c1824dcf7
14 changed files with 2198 additions and 87 deletions
+82
View File
@@ -0,0 +1,82 @@
use gpui::prelude::*;
use std::sync::Arc;
use crate::components::{
self,
input::{Input, Suggestion},
};
pub(crate) struct App {
input: gpui::Entity<Input>,
current_text: gpui::SharedString,
}
impl gpui::Render for App {
fn render(
&mut self,
_window: &mut gpui::Window,
_cx: &mut gpui::Context<Self>,
) -> impl gpui::IntoElement {
components::panel::Panel::new()
.child(self.input.clone())
.child(components::label::Label::new(format!(
"Text: {}",
&self.current_text
)))
}
}
impl App {
pub fn run() -> () {
let app = gpui::Application::new();
app.run(|app: &mut gpui::App| {
app.set_global(crate::theme::Theme::dark());
app.open_window(
gpui::WindowOptions::default(),
|_window: &mut gpui::Window, app: &mut gpui::App| {
app.new(|cx: &mut gpui::Context<'_, App>| {
let input = cx.new(|cx| {
Input::new("input", cx, "Type here...").with_suggest(Arc::new(|text| {
let suggestions = vec![
("project:", "Filter by project"),
("+work", "Tag: work"),
("+personal", "Tag: personal"),
("due:today", "Tasks for today"),
("due:tomorrow", "Tasks for tomorrow"),
("priority:H", "Priority high"),
("priority:M", "Priority medium"),
("priority:L", "Priority low"),
];
suggestions
.into_iter()
.filter(|(insert, _)| {
text.is_empty()
|| insert.to_lowercase().contains(&text.to_lowercase())
})
.map(|(insert, label)| {
Suggestion::new(format!("{} - {}", insert, label), insert)
})
.collect()
}))
});
cx.observe(&input, |this, input, cx| {
let value = input.read(cx).value().to_string();
this.current_text = value.into();
cx.notify();
})
.detach();
App {
input,
current_text: "".into(),
}
})
},
)
.unwrap();
});
}
}