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}