1//! # Component
2//!
3//! This module provides the Component trait, which is used to define
4//! components for visual testing and debugging.
5//!
6//! Additionally, it includes layouts for rendering component examples
7//! and example groups, as well as the distributed slice mechanism for
8//! registering components.
9
10mod component_layout;
11
12pub use component_layout::*;
13
14use std::sync::LazyLock;
15
16use collections::HashMap;
17use gpui::{AnyElement, App, SharedString, Window};
18use linkme::distributed_slice;
19use parking_lot::RwLock;
20use strum::{Display, EnumString};
21
22pub fn components() -> ComponentRegistry {
23 COMPONENT_DATA.read().clone()
24}
25
26pub fn init() {
27 let component_fns: Vec<_> = __ALL_COMPONENTS.iter().cloned().collect();
28 for f in component_fns {
29 f();
30 }
31}
32
33pub fn register_component<T: Component>() {
34 let id = T::id();
35 let metadata = ComponentMetadata {
36 id: id.clone(),
37 description: T::description().map(Into::into),
38 name: SharedString::new_static(T::name()),
39 preview: Some(T::preview),
40 scope: T::scope(),
41 sort_name: SharedString::new_static(T::sort_name()),
42 status: T::status(),
43 };
44
45 let mut data = COMPONENT_DATA.write();
46 data.components.insert(id, metadata);
47}
48
49#[distributed_slice]
50pub static __ALL_COMPONENTS: [fn()] = [..];
51
52pub static COMPONENT_DATA: LazyLock<RwLock<ComponentRegistry>> =
53 LazyLock::new(|| RwLock::new(ComponentRegistry::default()));
54
55#[derive(Default, Clone)]
56pub struct ComponentRegistry {
57 components: HashMap<ComponentId, ComponentMetadata>,
58}
59
60impl ComponentRegistry {
61 pub fn previews(&self) -> Vec<&ComponentMetadata> {
62 self.components
63 .values()
64 .filter(|c| c.preview.is_some())
65 .collect()
66 }
67
68 pub fn sorted_previews(&self) -> Vec<ComponentMetadata> {
69 let mut previews: Vec<ComponentMetadata> = self.previews().into_iter().cloned().collect();
70 previews.sort_by_key(|a| a.name());
71 previews
72 }
73
74 pub fn components(&self) -> Vec<&ComponentMetadata> {
75 self.components.values().collect()
76 }
77
78 pub fn sorted_components(&self) -> Vec<ComponentMetadata> {
79 let mut components: Vec<ComponentMetadata> =
80 self.components().into_iter().cloned().collect();
81 components.sort_by_key(|a| a.name());
82 components
83 }
84
85 pub fn component_map(&self) -> HashMap<ComponentId, ComponentMetadata> {
86 self.components.clone()
87 }
88
89 pub fn get(&self, id: &ComponentId) -> Option<&ComponentMetadata> {
90 self.components.get(id)
91 }
92
93 pub fn len(&self) -> usize {
94 self.components.len()
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Hash)]
99pub struct ComponentId(pub &'static str);
100
101#[derive(Clone)]
102pub struct ComponentMetadata {
103 id: ComponentId,
104 description: Option<SharedString>,
105 name: SharedString,
106 preview: Option<fn(&mut Window, &mut App) -> Option<AnyElement>>,
107 scope: ComponentScope,
108 sort_name: SharedString,
109 status: ComponentStatus,
110}
111
112impl ComponentMetadata {
113 pub fn id(&self) -> ComponentId {
114 self.id.clone()
115 }
116
117 pub fn description(&self) -> Option<SharedString> {
118 self.description.clone()
119 }
120
121 pub fn name(&self) -> SharedString {
122 self.name.clone()
123 }
124
125 pub fn preview(&self) -> Option<fn(&mut Window, &mut App) -> Option<AnyElement>> {
126 self.preview
127 }
128
129 pub fn scope(&self) -> ComponentScope {
130 self.scope.clone()
131 }
132
133 pub fn sort_name(&self) -> SharedString {
134 self.sort_name.clone()
135 }
136
137 pub fn scopeless_name(&self) -> SharedString {
138 self.name
139 .clone()
140 .split("::")
141 .last()
142 .unwrap_or(&self.name)
143 .to_string()
144 .into()
145 }
146
147 pub fn status(&self) -> ComponentStatus {
148 self.status.clone()
149 }
150}
151
152/// Implement this trait to define a UI component. This will allow you to
153/// derive `RegisterComponent` on it, in tutn allowing you to preview the
154/// contents of the preview fn in `workspace: open component preview`.
155///
156/// This can be useful for visual debugging and testing, documenting UI
157/// patterns, or simply showing all the variants of a component.
158///
159/// Generally you will want to implement at least `scope` and `preview`
160/// from this trait, so you can preview the component, and it will show up
161/// in a section that makes sense.
162pub trait Component {
163 /// The component's unique identifier.
164 ///
165 /// Used to access previews, or state for more
166 /// complex, stateful components.
167 fn id() -> ComponentId {
168 ComponentId(Self::name())
169 }
170 /// Returns the scope of the component.
171 ///
172 /// This scope is used to determine how components and
173 /// their previews are displayed and organized.
174 fn scope() -> ComponentScope {
175 ComponentScope::None
176 }
177 /// The ready status of this component.
178 ///
179 /// Use this to mark when components are:
180 /// - `WorkInProgress`: Still being designed or are partially implemented.
181 /// - `EngineeringReady`: Ready to be implemented.
182 /// - `Deprecated`: No longer recommended for use.
183 ///
184 /// Defaults to [`Live`](ComponentStatus::Live).
185 fn status() -> ComponentStatus {
186 ComponentStatus::Live
187 }
188 /// The name of the component.
189 ///
190 /// This name is used to identify the component
191 /// and is usually derived from the component's type.
192 fn name() -> &'static str {
193 std::any::type_name::<Self>()
194 }
195 /// Returns a name that the component should be sorted by.
196 ///
197 /// Implement this if the component should be sorted in an alternate order than its name.
198 ///
199 /// Example:
200 ///
201 /// For example, to group related components together when sorted:
202 ///
203 /// - Button -> ButtonA
204 /// - IconButton -> ButtonBIcon
205 /// - ToggleButton -> ButtonCToggle
206 ///
207 /// This naming scheme keeps these components together and allows them to /// be sorted in a logical order.
208 fn sort_name() -> &'static str {
209 Self::name()
210 }
211 /// An optional description of the component.
212 ///
213 /// This will be displayed in the component's preview. To show a
214 /// component's doc comment as it's description, derive `Documented`.
215 ///
216 /// Example:
217 ///
218 /// ```
219 /// /// This is a doc comment.
220 /// #[derive(Documented)]
221 /// struct MyComponent;
222 ///
223 /// impl MyComponent {
224 /// fn description() -> Option<&'static str> {
225 /// Some(Self::DOCS)
226 /// }
227 /// }
228 /// ```
229 ///
230 /// This will result in "This is a doc comment." being passed
231 /// to the component's description.
232 fn description() -> Option<&'static str> {
233 None
234 }
235 /// The component's preview.
236 ///
237 /// An element returned here will be shown in the component's preview.
238 ///
239 /// Useful component helpers:
240 /// - [`component::single_example`]
241 /// - [`component::component_group`]
242 /// - [`component::component_group_with_title`]
243 ///
244 /// Note: Any arbitrary element can be returned here.
245 ///
246 /// This is useful for displaying related UI to the component you are
247 /// trying to preview, such as a button that opens a modal or shows a
248 /// tooltip on hover, or a grid of icons showcasing all the icons available.
249 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
250 None
251 }
252}
253
254/// The ready status of this component.
255///
256/// Use this to mark when components are:
257/// - `WorkInProgress`: Still being designed or are partially implemented.
258/// - `EngineeringReady`: Ready to be implemented.
259/// - `Deprecated`: No longer recommended for use.
260///
261/// Defaults to [`Live`](ComponentStatus::Live).
262#[derive(Debug, Clone, PartialEq, Eq, Hash, Display, EnumString)]
263pub enum ComponentStatus {
264 #[strum(serialize = "Work In Progress")]
265 WorkInProgress,
266 #[strum(serialize = "Ready To Build")]
267 EngineeringReady,
268 Live,
269 Deprecated,
270}
271
272impl ComponentStatus {
273 pub fn description(&self) -> &str {
274 match self {
275 ComponentStatus::WorkInProgress => {
276 "These components are still being designed or refined. They shouldn't be used in the app yet."
277 }
278 ComponentStatus::EngineeringReady => {
279 "These components are design complete or partially implemented, and are ready for an engineer to complete their implementation."
280 }
281 ComponentStatus::Live => "These components are ready for use in the app.",
282 ComponentStatus::Deprecated => {
283 "These components are no longer recommended for use in the app, and may be removed in a future release."
284 }
285 }
286 }
287}
288
289#[derive(Debug, Clone, PartialEq, Eq, Hash, Display, EnumString)]
290pub enum ComponentScope {
291 Agent,
292 Collaboration,
293 #[strum(serialize = "Data Display")]
294 DataDisplay,
295 Editor,
296 #[strum(serialize = "Images & Icons")]
297 Images,
298 #[strum(serialize = "Forms & Input")]
299 Input,
300 #[strum(serialize = "Layout & Structure")]
301 Layout,
302 #[strum(serialize = "Loading & Progress")]
303 Loading,
304 Navigation,
305 #[strum(serialize = "Unsorted")]
306 None,
307 Notification,
308 #[strum(serialize = "Overlays & Layering")]
309 Overlays,
310 Status,
311 Typography,
312 #[strum(serialize = "Version Control")]
313 VersionControl,
314}