use gpui::{ AnyElement, App, IntoElement, Pixels, RenderOnce, SharedString, Window, div, pattern_slash, prelude::*, px, rems, }; use theme::ActiveTheme; /// A single example of a component. #[derive(IntoElement)] pub struct ComponentExample { pub variant_name: SharedString, pub description: Option, pub element: AnyElement, pub width: Option, } impl RenderOnce for ComponentExample { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { div() .pt_2() .map(|this| { if let Some(width) = self.width { this.w(width) } else { this.w_full() } }) .flex() .flex_col() .gap_3() .child( div() .flex() .flex_col() .child( div() .child(self.variant_name.clone()) .text_size(rems(1.0)) .text_color(cx.theme().colors().text), ) .when_some(self.description, |this, description| { this.child( div() .text_size(rems(0.875)) .text_color(cx.theme().colors().text_muted) .child(description.clone()), ) }), ) .child( div() .min_h(px(100.)) .w_full() .p_8() .flex() .items_center() .justify_center() .rounded_xl() .border_1() .border_color(cx.theme().colors().border.opacity(0.5)) .bg(pattern_slash( cx.theme().colors().surface_background.opacity(0.25), 12.0, 12.0, )) .child(self.element), ) .into_any_element() } } impl ComponentExample { pub fn new(variant_name: impl Into, element: AnyElement) -> Self { Self { variant_name: variant_name.into(), element, description: None, width: None, } } pub fn description(mut self, description: impl Into) -> Self { self.description = Some(description.into()); self } pub fn width(mut self, width: Pixels) -> Self { self.width = Some(width); self } } /// A group of component examples. #[derive(IntoElement)] pub struct ComponentExampleGroup { pub title: Option, pub examples: Vec, pub width: Option, pub grow: bool, pub vertical: bool, } impl RenderOnce for ComponentExampleGroup { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { div() .flex_col() .text_sm() .text_color(cx.theme().colors().text_muted) .map(|this| { if let Some(width) = self.width { this.w(width) } else { this.w_full() } }) .when_some(self.title, |this, title| { this.gap_4().child( div() .flex() .items_center() .gap_3() .mt_4() .mb_1() .child( div() .flex_none() .text_size(px(10.)) .child(title.to_uppercase()), ) .child( div() .h_px() .w_full() .flex_1() .bg(cx.theme().colors().border), ), ) }) .child( div() .flex() .flex_col() .items_start() .w_full() .gap_6() .children(self.examples) .into_any_element(), ) .into_any_element() } } impl ComponentExampleGroup { pub fn new(examples: Vec) -> Self { Self { title: None, examples, width: None, grow: false, vertical: false, } } pub fn with_title(title: impl Into, examples: Vec) -> Self { Self { title: Some(title.into()), examples, width: None, grow: false, vertical: false, } } pub fn width(mut self, width: Pixels) -> Self { self.width = Some(width); self } pub fn grow(mut self) -> Self { self.grow = true; self } pub fn vertical(mut self) -> Self { self.vertical = true; self } } pub fn single_example( variant_name: impl Into, example: AnyElement, ) -> ComponentExample { ComponentExample::new(variant_name, example) } pub fn empty_example(variant_name: impl Into) -> ComponentExample { ComponentExample::new(variant_name, div().w_full().text_center().items_center().text_xs().opacity(0.4).child("This space is intentionally left blank. It indicates a case that should render nothing.").into_any_element()) } pub fn example_group(examples: Vec) -> ComponentExampleGroup { ComponentExampleGroup::new(examples) } pub fn example_group_with_title( title: impl Into, examples: Vec, ) -> ComponentExampleGroup { ComponentExampleGroup::with_title(title, examples) }