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