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}