diff --git a/Cargo.lock b/Cargo.lock index 011f970283f8f7bf19a98d4c47e571f47382c3a9..addc8d2d8c8c6a0ca46ad4d25ca4941afcfc83ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10831,6 +10831,7 @@ dependencies = [ "serde_json", "settings", "settings_ui", + "smallvec", "theme", "ui", "util", diff --git a/crates/onboarding_ui/Cargo.toml b/crates/onboarding_ui/Cargo.toml index 61ee08fc2e53956be1245017a4a48ab10c9906b4..fc903dbaff664bdcafe6a961bdb016cce55af7d1 100644 --- a/crates/onboarding_ui/Cargo.toml +++ b/crates/onboarding_ui/Cargo.toml @@ -34,6 +34,7 @@ util.workspace = true vim_mode_setting.workspace = true workspace.workspace = true zed_actions.workspace = true +smallvec.workspace = true [dev-dependencies] editor = { workspace = true, features = ["test-support"] } diff --git a/crates/onboarding_ui/src/components/mod.rs b/crates/onboarding_ui/src/components/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..707b2d2ea41ce975f566caa313aba92b7db367ac --- /dev/null +++ b/crates/onboarding_ui/src/components/mod.rs @@ -0,0 +1 @@ +mod selectable_tile; diff --git a/crates/onboarding_ui/src/components/selectable_tile.rs b/crates/onboarding_ui/src/components/selectable_tile.rs new file mode 100644 index 0000000000000000000000000000000000000000..6c58c806af593108d5d7df125e4a49805f2f7469 --- /dev/null +++ b/crates/onboarding_ui/src/components/selectable_tile.rs @@ -0,0 +1,170 @@ +use component::{example_group_with_title, single_example}; +use gpui::{ClickEvent, transparent_black}; +use smallvec::SmallVec; +use ui::{prelude::*, utils::CornerSolver}; + +#[derive(IntoElement, RegisterComponent)] +pub struct SelectableTile { + id: ElementId, + width: DefiniteLength, + height: DefiniteLength, + parent_focused: bool, + selected: bool, + children: SmallVec<[AnyElement; 2]>, + on_click: Option>, +} + +impl SelectableTile { + pub fn new(id: impl Into) -> Self { + Self { + id: id.into(), + width: px(120.).into(), + height: px(120.).into(), + parent_focused: false, + selected: false, + children: SmallVec::new(), + on_click: None, + } + } + + pub fn w(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + pub fn h(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } + + pub fn parent_focused(mut self, focused: bool) -> Self { + self.parent_focused = focused; + self + } + + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } + + pub fn on_click( + mut self, + handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static, + ) -> Self { + self.on_click = Some(Box::new(handler)); + self + } +} + +impl RenderOnce for SelectableTile { + fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + let ring_corner_radius = px(8.); + let ring_width = px(1.); + let padding = px(2.); + let content_border_width = px(0.); + let content_border_radius = CornerSolver::child_radius( + ring_corner_radius, + ring_width, + padding, + content_border_width, + ); + + let mut element = h_flex() + .id(self.id) + .w(self.width) + .h(self.height) + .overflow_hidden() + .rounded(ring_corner_radius) + .border(ring_width) + .border_color(if self.selected && self.parent_focused { + cx.theme().colors().border + } else if self.selected { + cx.theme().status().info + } else { + transparent_black() + }) + .p(padding) + .child( + h_flex() + .size_full() + .rounded(content_border_radius) + .items_center() + .justify_center() + .shadow_hairline() + .bg(cx.theme().colors().surface_background) + .children(self.children), + ); + + if let Some(on_click) = self.on_click { + element = element.on_click(move |event, window, cx| { + on_click(event, window, cx); + }); + } + + element + } +} + +impl ParentElement for SelectableTile { + fn extend(&mut self, elements: impl IntoIterator) { + self.children.extend(elements) + } +} + +impl Component for SelectableTile { + fn scope() -> ComponentScope { + ComponentScope::Layout + } + + fn preview(_window: &mut Window, _cx: &mut App) -> Option { + let states = example_group_with_title( + "SelectableTile States", + vec![ + single_example( + "Default", + SelectableTile::new("default") + .w(px(120.)) + .h(px(120.)) + .parent_focused(false) + .selected(false) + .child( + div() + .p_4() + .child(Icon::new(IconName::Check).size(IconSize::Medium)), + ) + .into_any_element(), + ), + single_example( + "Selected", + SelectableTile::new("selected") + .w(px(120.)) + .h(px(120.)) + .parent_focused(false) + .selected(true) + .child( + div() + .p_4() + .child(Icon::new(IconName::Check).size(IconSize::Medium)), + ) + .into_any_element(), + ), + single_example( + "Selected & Parent Focused", + SelectableTile::new("selected_focused") + .w(px(120.)) + .h(px(120.)) + .parent_focused(true) + .selected(true) + .child( + div() + .p_4() + .child(Icon::new(IconName::Check).size(IconSize::Medium)), + ) + .into_any_element(), + ), + ], + ); + + Some(v_flex().p_4().gap_4().child(states).into_any_element()) + } +} diff --git a/crates/onboarding_ui/src/onboarding_ui.rs b/crates/onboarding_ui/src/onboarding_ui.rs index 01b8770e51a805e52164ef3810f083843b760ce2..0a8b23255c31114acf682b852ac7974d9a3a3bbc 100644 --- a/crates/onboarding_ui/src/onboarding_ui.rs +++ b/crates/onboarding_ui/src/onboarding_ui.rs @@ -1,4 +1,5 @@ #![allow(unused, dead_code)] +mod components; mod juicy_button; mod persistence; mod theme_preview;