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().gap_px().p_1().children(
 45            sorted_components
 46                .into_iter()
 47                .map(|component| self.render_sidebar_entry(&component, _cx)),
 48        )
 49    }
 50
 51    fn render_sidebar_entry(
 52        &self,
 53        component: &ComponentMetadata,
 54        _cx: &Context<Self>,
 55    ) -> impl IntoElement {
 56        h_flex()
 57            .w_40()
 58            .px_1p5()
 59            .py_1()
 60            .child(component.name().clone())
 61    }
 62
 63    fn render_preview(
 64        &self,
 65        component: &ComponentMetadata,
 66        window: &mut Window,
 67        cx: &Context<Self>,
 68    ) -> impl IntoElement {
 69        let name = component.name();
 70        let scope = component.scope();
 71
 72        let description = component.description();
 73
 74        v_group()
 75            .w_full()
 76            .gap_4()
 77            .p_8()
 78            .rounded_md()
 79            .child(
 80                v_flex()
 81                    .gap_1()
 82                    .child(
 83                        h_flex()
 84                            .gap_1()
 85                            .text_xl()
 86                            .child(div().child(name))
 87                            .when_some(scope, |this, scope| {
 88                                this.child(div().opacity(0.5).child(format!("({})", scope)))
 89                            }),
 90                    )
 91                    .when_some(description, |this, description| {
 92                        this.child(
 93                            div()
 94                                .text_ui_sm(cx)
 95                                .text_color(cx.theme().colors().text_muted)
 96                                .max_w(px(600.0))
 97                                .child(description),
 98                        )
 99                    }),
100            )
101            .when_some(component.preview(), |this, preview| {
102                this.child(preview(window, cx))
103            })
104            .into_any_element()
105    }
106
107    fn render_previews(&self, window: &mut Window, cx: &Context<Self>) -> impl IntoElement {
108        v_flex()
109            .id("component-previews")
110            .size_full()
111            .overflow_y_scroll()
112            .p_4()
113            .gap_2()
114            .children(
115                components()
116                    .all_previews_sorted()
117                    .iter()
118                    .map(|component| self.render_preview(component, window, cx)),
119            )
120    }
121}
122
123impl Render for ComponentPreview {
124    fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
125        h_flex()
126            .id("component-preview")
127            .key_context("ComponentPreview")
128            .items_start()
129            .overflow_hidden()
130            .size_full()
131            .max_h_full()
132            .track_focus(&self.focus_handle)
133            .px_2()
134            .bg(cx.theme().colors().editor_background)
135            .child(self.render_sidebar(window, cx))
136            .child(self.render_previews(window, cx))
137    }
138}
139
140impl EventEmitter<ItemEvent> for ComponentPreview {}
141
142impl Focusable for ComponentPreview {
143    fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
144        self.focus_handle.clone()
145    }
146}
147
148impl Item for ComponentPreview {
149    type Event = ItemEvent;
150
151    fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
152        Some("Component Preview".into())
153    }
154
155    fn telemetry_event_text(&self) -> Option<&'static str> {
156        None
157    }
158
159    fn show_toolbar(&self) -> bool {
160        false
161    }
162
163    fn clone_on_split(
164        &self,
165        _workspace_id: Option<WorkspaceId>,
166        _window: &mut Window,
167        cx: &mut Context<Self>,
168    ) -> Option<gpui::Entity<Self>>
169    where
170        Self: Sized,
171    {
172        Some(cx.new(Self::new))
173    }
174
175    fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
176        f(*event)
177    }
178}