component_preview.rs

  1//! # Component Preview
  2//!
  3//! A view for exploring Zed components.
  4
  5use component::{components, ComponentMetadata};
  6use gpui::{prelude::*, App, EventEmitter, FocusHandle, Focusable, Window};
  7use ui::prelude::*;
  8
  9use workspace::{item::ItemEvent, Item, Workspace, WorkspaceId};
 10
 11pub fn init(cx: &mut App) {
 12    cx.observe_new(|workspace: &mut Workspace, _, _cx| {
 13        workspace.register_action(
 14            |workspace, _: &workspace::OpenComponentPreview, window, cx| {
 15                let component_preview = cx.new(ComponentPreview::new);
 16                workspace.add_item_to_active_pane(
 17                    Box::new(component_preview),
 18                    None,
 19                    true,
 20                    window,
 21                    cx,
 22                )
 23            },
 24        );
 25    })
 26    .detach();
 27}
 28
 29struct ComponentPreview {
 30    focus_handle: FocusHandle,
 31}
 32
 33impl ComponentPreview {
 34    pub fn new(cx: &mut Context<Self>) -> Self {
 35        Self {
 36            focus_handle: cx.focus_handle(),
 37        }
 38    }
 39
 40    fn render_sidebar(&self, _window: &Window, _cx: &Context<Self>) -> impl IntoElement {
 41        let components = components().all_sorted();
 42        let sorted_components = components.clone();
 43
 44        v_flex()
 45            .max_w_48()
 46            .gap_px()
 47            .p_1()
 48            .children(
 49                sorted_components
 50                    .into_iter()
 51                    .map(|component| self.render_sidebar_entry(&component, _cx)),
 52            )
 53            .child(
 54                Label::new("These will be clickable once the layout is moved to a gpui::List.")
 55                    .color(Color::Muted)
 56                    .size(LabelSize::XSmall)
 57                    .italic(),
 58            )
 59    }
 60
 61    fn render_sidebar_entry(
 62        &self,
 63        component: &ComponentMetadata,
 64        _cx: &Context<Self>,
 65    ) -> impl IntoElement {
 66        h_flex()
 67            .w_40()
 68            .px_1p5()
 69            .py_0p5()
 70            .text_sm()
 71            .child(component.name().clone())
 72    }
 73
 74    fn render_preview(
 75        &self,
 76        component: &ComponentMetadata,
 77        window: &mut Window,
 78        cx: &Context<Self>,
 79    ) -> impl IntoElement {
 80        let name = component.name();
 81        let scope = component.scope();
 82
 83        let description = component.description();
 84
 85        v_flex()
 86            .border_b_1()
 87            .border_color(cx.theme().colors().border)
 88            .w_full()
 89            .gap_3()
 90            .py_6()
 91            .child(
 92                v_flex()
 93                    .gap_1()
 94                    .child(
 95                        h_flex()
 96                            .gap_1()
 97                            .text_2xl()
 98                            .child(div().child(name))
 99                            .when_some(scope, |this, scope| {
100                                this.child(div().opacity(0.5).child(format!("({})", scope)))
101                            }),
102                    )
103                    .when_some(description, |this, description| {
104                        this.child(
105                            div()
106                                .text_ui_sm(cx)
107                                .text_color(cx.theme().colors().text_muted)
108                                .max_w(px(600.0))
109                                .child(description),
110                        )
111                    }),
112            )
113            .when_some(component.preview(), |this, preview| {
114                this.child(preview(window, cx))
115            })
116            .into_any_element()
117    }
118
119    fn render_previews(&self, window: &mut Window, cx: &Context<Self>) -> impl IntoElement {
120        v_flex()
121            .id("component-previews")
122            .size_full()
123            .overflow_y_scroll()
124            .p_4()
125            .gap_4()
126            .children(
127                components()
128                    .all_previews_sorted()
129                    .iter()
130                    .map(|component| self.render_preview(component, window, cx)),
131            )
132    }
133}
134
135impl Render for ComponentPreview {
136    fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
137        h_flex()
138            .id("component-preview")
139            .key_context("ComponentPreview")
140            .items_start()
141            .overflow_hidden()
142            .size_full()
143            .max_h_full()
144            .track_focus(&self.focus_handle)
145            .px_2()
146            .bg(cx.theme().colors().editor_background)
147            .child(self.render_sidebar(window, cx))
148            .child(self.render_previews(window, cx))
149    }
150}
151
152impl EventEmitter<ItemEvent> for ComponentPreview {}
153
154impl Focusable for ComponentPreview {
155    fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
156        self.focus_handle.clone()
157    }
158}
159
160impl Item for ComponentPreview {
161    type Event = ItemEvent;
162
163    fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
164        Some("Component Preview".into())
165    }
166
167    fn telemetry_event_text(&self) -> Option<&'static str> {
168        None
169    }
170
171    fn show_toolbar(&self) -> bool {
172        false
173    }
174
175    fn clone_on_split(
176        &self,
177        _workspace_id: Option<WorkspaceId>,
178        _window: &mut Window,
179        cx: &mut Context<Self>,
180    ) -> Option<gpui::Entity<Self>>
181    where
182        Self: Sized,
183    {
184        Some(cx.new(Self::new))
185    }
186
187    fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
188        f(*event)
189    }
190}