ccf_gpui_widgets/widgets/selection.rs
1//! Selection item trait for generic selection widgets
2//!
3//! This module defines the `SelectionItem` trait used by RadioGroup, SegmentedControl,
4//! TabBar, and SidebarNav widgets. It also provides `StringItem` as a simple default
5//! implementation for string-based selections.
6//!
7//! # Example: Custom Selection Type
8//!
9//! ```ignore
10//! use ccf_gpui_widgets::widgets::SelectionItem;
11//! use gpui::*;
12//!
13//! #[derive(Clone, PartialEq)]
14//! enum Size {
15//! Small,
16//! Medium,
17//! Large,
18//! }
19//!
20//! impl SelectionItem for Size {
21//! fn label(&self) -> SharedString {
22//! match self {
23//! Size::Small => "Small".into(),
24//! Size::Medium => "Medium".into(),
25//! Size::Large => "Large".into(),
26//! }
27//! }
28//!
29//! fn id(&self) -> ElementId {
30//! match self {
31//! Size::Small => "size_small".into(),
32//! Size::Medium => "size_medium".into(),
33//! Size::Large => "size_large".into(),
34//! }
35//! }
36//! }
37//! ```
38
39use gpui::{ElementId, SharedString};
40
41/// Trait for items that can be displayed in selection widgets
42///
43/// Implement this trait to use custom types with RadioGroup, SegmentedControl,
44/// TabBar, and SidebarNav widgets.
45///
46/// # Required Methods
47///
48/// - `label()`: Display text for the item
49/// - `id()`: Unique element ID for the item (used for click handling and GPUI rendering)
50pub trait SelectionItem: Clone + PartialEq + 'static {
51 /// The display label for this item
52 fn label(&self) -> SharedString;
53
54 /// A unique element ID for this item (used for click handling)
55 fn id(&self) -> ElementId;
56}
57
58/// A simple string-based selection item
59///
60/// Use this type for simple string selections without defining a custom enum.
61/// The label and id are both derived from the string value.
62///
63/// # Example
64///
65/// ```ignore
66/// use ccf_gpui_widgets::widgets::{RadioGroup, StringItem};
67///
68/// let radio = cx.new(|cx| {
69/// RadioGroup::new(
70/// vec![
71/// StringItem::new("small"),
72/// StringItem::new("medium"),
73/// StringItem::new("large"),
74/// ],
75/// StringItem::new("medium"),
76/// cx,
77/// )
78/// });
79/// ```
80#[derive(Clone, PartialEq, Debug)]
81pub struct StringItem {
82 value: String,
83}
84
85impl StringItem {
86 /// Create a new StringItem from a string value
87 pub fn new(value: impl Into<String>) -> Self {
88 Self {
89 value: value.into(),
90 }
91 }
92
93 /// Get the underlying string value
94 pub fn value(&self) -> &str {
95 &self.value
96 }
97
98 /// Consume and return the underlying string value
99 pub fn into_value(self) -> String {
100 self.value
101 }
102}
103
104impl SelectionItem for StringItem {
105 fn label(&self) -> SharedString {
106 self.value.clone().into()
107 }
108
109 fn id(&self) -> ElementId {
110 // Create stable ID from value - lowercase with underscores
111 let id_str = format!("item_{}", self.value.to_lowercase().replace(' ', "_"));
112 ElementId::Name(id_str.into())
113 }
114}
115
116impl From<String> for StringItem {
117 fn from(value: String) -> Self {
118 Self::new(value)
119 }
120}
121
122impl From<&str> for StringItem {
123 fn from(value: &str) -> Self {
124 Self::new(value)
125 }
126}
127
128impl std::fmt::Display for StringItem {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 write!(f, "{}", self.value)
131 }
132}